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
|
@@ -7,13 +7,17 @@ export function normalizeCatalogDir(directory) {
|
|
|
7
7
|
return directory.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
8
8
|
}
|
|
9
9
|
/** Untitled workshop saves land here so Run can see them without a new catalog edit. */
|
|
10
|
-
export function defaultWorkshopPackageDirectory(pipelineId) {
|
|
10
|
+
export function defaultWorkshopPackageDirectory(pipelineId, catalogRootKind) {
|
|
11
11
|
const slug = (pipelineId ?? "")
|
|
12
12
|
.toLowerCase()
|
|
13
13
|
.replace(/[^a-z0-9]+/g, "-")
|
|
14
14
|
.replace(/^-+|-+$/g, "")
|
|
15
15
|
.slice(0, 40);
|
|
16
|
-
|
|
16
|
+
const id = slug || "untitled";
|
|
17
|
+
if (catalogRootKind === "workspace") {
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
return `${WORKSHOP_PACKAGE_ROOT}/${id}`;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
23
|
* Catalog root that covers this package directory.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TaskFile } from "../types/task.js";
|
|
2
|
+
import { type ResolveCatalogRootsOptions } from "./resolveCatalogRoots.js";
|
|
3
|
+
export type TaskDetail = TaskFile & {
|
|
4
|
+
path: string;
|
|
5
|
+
};
|
|
6
|
+
export type TaskDetailWithRoot = TaskDetail & {
|
|
7
|
+
project_root: string;
|
|
8
|
+
};
|
|
9
|
+
export type FindTaskInCatalogMultiResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
task: TaskDetailWithRoot;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
status: 404;
|
|
15
|
+
error: string;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
status: 400;
|
|
19
|
+
error: string;
|
|
20
|
+
code: "unknown_project_root";
|
|
21
|
+
} | {
|
|
22
|
+
ok: false;
|
|
23
|
+
status: 409;
|
|
24
|
+
error: string;
|
|
25
|
+
code: "ambiguous_task";
|
|
26
|
+
project_roots: string[];
|
|
27
|
+
};
|
|
28
|
+
export declare function findTaskInProjectRoot(projectRoot: string, taskId: string): Promise<TaskDetail | null>;
|
|
29
|
+
export declare function findTaskInCatalogMulti(options: ResolveCatalogRootsOptions & {
|
|
30
|
+
taskId: string;
|
|
31
|
+
projectRootFilter?: string;
|
|
32
|
+
}): Promise<FindTaskInCatalogMultiResult>;
|
|
33
|
+
export declare function findTaskInCatalog(cwd: string, taskId: string): Promise<TaskDetail | null>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getCatalogScanPaths } from "./browseCatalog.js";
|
|
3
|
+
import { loadTaskOutcome } from "./loadTask.js";
|
|
4
|
+
import { listTasksMultiProject } from "./multiProjectCatalog.js";
|
|
5
|
+
import { catalogContextFromStageflow } from "./resolveCatalogContext.js";
|
|
6
|
+
import { findCatalogRoot, } from "./resolveCatalogRoots.js";
|
|
7
|
+
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
8
|
+
export async function findTaskInProjectRoot(projectRoot, taskId) {
|
|
9
|
+
const ctx = catalogContextFromStageflow(await resolveStageflowContext(projectRoot));
|
|
10
|
+
const scanPaths = await getCatalogScanPaths(ctx);
|
|
11
|
+
if (!scanPaths || ctx.projectRoot === null) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const root = ctx.projectRoot;
|
|
15
|
+
for (const filePath of scanPaths.taskPaths) {
|
|
16
|
+
const outcome = await loadTaskOutcome(filePath);
|
|
17
|
+
if (!outcome.ok || outcome.value.id !== taskId) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...outcome.value,
|
|
22
|
+
path: path.relative(root, filePath).replace(/\\/g, "/"),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
export async function findTaskInCatalogMulti(options) {
|
|
28
|
+
const { taskId, projectRootFilter, ...rootOptions } = options;
|
|
29
|
+
const listed = await listTasksMultiProject({
|
|
30
|
+
...rootOptions,
|
|
31
|
+
projectRootFilter,
|
|
32
|
+
});
|
|
33
|
+
if (projectRootFilter !== undefined &&
|
|
34
|
+
listed.root_errors.some((e) => e.code === "unknown_project_root")) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
status: 400,
|
|
38
|
+
error: listed.root_errors[0].message,
|
|
39
|
+
code: "unknown_project_root",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const matches = listed.items.filter((t) => t.id === taskId);
|
|
43
|
+
if (matches.length === 0) {
|
|
44
|
+
return { ok: false, status: 404, error: `Task not found: ${taskId}` };
|
|
45
|
+
}
|
|
46
|
+
const projectRoots = [...new Set(matches.map((m) => m.project_root))];
|
|
47
|
+
if (projectRoots.length > 1) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
status: 409,
|
|
51
|
+
error: `Task id is ambiguous across catalog roots: ${taskId}`,
|
|
52
|
+
code: "ambiguous_task",
|
|
53
|
+
project_roots: projectRoots.sort((a, b) => a.localeCompare(b)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const wireRoot = projectRoots[0];
|
|
57
|
+
const catalogRoot = findCatalogRoot(listed.roots, wireRoot);
|
|
58
|
+
if (catalogRoot === undefined) {
|
|
59
|
+
return { ok: false, status: 404, error: `Task not found: ${taskId}` };
|
|
60
|
+
}
|
|
61
|
+
const detail = await findTaskInProjectRoot(catalogRoot.path, taskId);
|
|
62
|
+
if (!detail) {
|
|
63
|
+
return { ok: false, status: 404, error: `Task not found: ${taskId}` };
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, task: { ...detail, project_root: wireRoot } };
|
|
66
|
+
}
|
|
67
|
+
export async function findTaskInCatalog(cwd, taskId) {
|
|
68
|
+
return findTaskInProjectRoot(cwd, taskId);
|
|
69
|
+
}
|
|
@@ -9,6 +9,14 @@ export declare const KNOWN_STAGEFLOW_ENV_KEYS: Set<string>;
|
|
|
9
9
|
export type CallerQuotaConfig = {
|
|
10
10
|
maxConcurrent: number;
|
|
11
11
|
};
|
|
12
|
+
export declare const DEFAULT_BROWSER_DIALOG_TIMEOUT_SECONDS = 60;
|
|
13
|
+
export type BrowserContainerConfig = {
|
|
14
|
+
image: string;
|
|
15
|
+
shmSize: string;
|
|
16
|
+
memory: string;
|
|
17
|
+
pidsLimit: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const DEFAULT_BROWSER_CONTAINER_IMAGE = "stageflow-browser-sandbox:local";
|
|
12
20
|
export type HostConfig = {
|
|
13
21
|
maxConcurrentRuns: number;
|
|
14
22
|
maxConcurrentRunsPerProject: number | undefined;
|
|
@@ -22,6 +30,18 @@ export type HostConfig = {
|
|
|
22
30
|
callers: Record<string, CallerQuotaConfig>;
|
|
23
31
|
/** Domains no browser stage may use (`browser.blocked_sites` in config.yaml). */
|
|
24
32
|
browserBlockedSites: string[];
|
|
33
|
+
/** Chrome launch arguments (`browser.launch_args`); empty when unset. */
|
|
34
|
+
browserLaunchArgs: string[];
|
|
35
|
+
/** Explicit Chrome path (`browser.executable_path`); undefined when unset. */
|
|
36
|
+
browserExecutablePath: string | undefined;
|
|
37
|
+
/** Seconds an unanswered live view page dialog waits before the Host dismisses it (`browser.dialog_timeout_seconds`). */
|
|
38
|
+
browserDialogTimeoutSeconds: number;
|
|
39
|
+
/** Where shared profile browsers run (`browser.host`): this machine, or one container per run. */
|
|
40
|
+
browserHost: "local" | "container";
|
|
41
|
+
/** Per-owner adapter override (`browser.owner_hosts`, owner scope to host); scopes absent here use `browserHost`. */
|
|
42
|
+
browserOwnerHosts: Record<string, "local" | "container">;
|
|
43
|
+
/** Container host settings (`browser.container`); defaults apply when unset. */
|
|
44
|
+
browserContainer: BrowserContainerConfig;
|
|
25
45
|
/** Absolute path of config.yaml when loaded; undefined if absent. */
|
|
26
46
|
configFilePath: string | undefined;
|
|
27
47
|
warnings: string[];
|
|
@@ -45,6 +65,7 @@ export type HostConfigPublicEcho = {
|
|
|
45
65
|
callers: Record<string, CallerQuotaConfig>;
|
|
46
66
|
secrets: HostConfigSecretsEcho;
|
|
47
67
|
};
|
|
68
|
+
export declare function browserHostForOwner(config: Pick<HostConfig, "browserHost" | "browserOwnerHosts">, scope: string): "local" | "container";
|
|
48
69
|
/**
|
|
49
70
|
* Assemble HostConfig once at boot. Precedence: overrides (CLI flags) > env > file > default.
|
|
50
71
|
*/
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { globalStageflowHome } from "../project/globalHome.js";
|
|
5
|
+
import { validateProfileScope } from "../browser/profileStore.js";
|
|
5
6
|
import { readSecretFromEnvOrFile } from "./secretFromEnvOrFile.js";
|
|
6
7
|
export class HostConfigError extends Error {
|
|
7
8
|
code;
|
|
@@ -16,7 +17,9 @@ export class HostConfigError extends Error {
|
|
|
16
17
|
/** Env keys that are known STAGEFLOW_* (or dynamic families) so unknown-key check accepts them. */
|
|
17
18
|
export const KNOWN_STAGEFLOW_ENV_KEYS = new Set([
|
|
18
19
|
"STAGEFLOW_HOME",
|
|
20
|
+
"STAGEFLOW_CREDENTIAL_HOME",
|
|
19
21
|
"STAGEFLOW_BIND",
|
|
22
|
+
"STAGEFLOW_ALLOW_UNAUTHENTICATED_BIND",
|
|
20
23
|
"STAGEFLOW_ALLOWED_HOSTS",
|
|
21
24
|
"STAGEFLOW_CONTROL_TOKEN",
|
|
22
25
|
"STAGEFLOW_CONTROL_TOKEN_FILE",
|
|
@@ -40,6 +43,7 @@ export const KNOWN_STAGEFLOW_ENV_KEYS = new Set([
|
|
|
40
43
|
"STAGEFLOW_LOG_MAX_LINE_BYTES",
|
|
41
44
|
"STAGEFLOW_MCP_STATELESS",
|
|
42
45
|
"STAGEFLOW_DISK_WARN_BYTES",
|
|
46
|
+
"STAGEFLOW_ENSURE_PROJECT_ROOTS",
|
|
43
47
|
"STAGEFLOW_MIN_FREE_DISK_BYTES",
|
|
44
48
|
"STAGEFLOW_BARE_CACHE_TTL_MS",
|
|
45
49
|
"STAGEFLOW_SLIM_ARTIFACT_MAX_BYTES",
|
|
@@ -82,6 +86,11 @@ const FILE_KEY_TO_FIELD = {
|
|
|
82
86
|
callers: "callers",
|
|
83
87
|
browser: "browser",
|
|
84
88
|
};
|
|
89
|
+
export const DEFAULT_BROWSER_DIALOG_TIMEOUT_SECONDS = 60;
|
|
90
|
+
export const DEFAULT_BROWSER_CONTAINER_IMAGE = "stageflow-browser-sandbox:local";
|
|
91
|
+
export function browserHostForOwner(config, scope) {
|
|
92
|
+
return config.browserOwnerHosts[scope] ?? config.browserHost;
|
|
93
|
+
}
|
|
85
94
|
const DEFAULT_MAX_CONCURRENT = 3;
|
|
86
95
|
const DEFAULT_MAX_QUEUED = 32;
|
|
87
96
|
function isKnownStageflowEnvKey(key) {
|
|
@@ -303,16 +312,113 @@ export function loadHostConfig(options) {
|
|
|
303
312
|
callers = coerceCallers(file.values.callers, "callers");
|
|
304
313
|
}
|
|
305
314
|
let browserBlockedSites = [];
|
|
315
|
+
let browserLaunchArgs = [];
|
|
316
|
+
let browserExecutablePath;
|
|
317
|
+
let browserDialogTimeoutSeconds = DEFAULT_BROWSER_DIALOG_TIMEOUT_SECONDS;
|
|
318
|
+
let browserHost = "local";
|
|
319
|
+
const browserOwnerHosts = {};
|
|
320
|
+
const browserContainer = {
|
|
321
|
+
image: DEFAULT_BROWSER_CONTAINER_IMAGE,
|
|
322
|
+
shmSize: "1g",
|
|
323
|
+
memory: "1g",
|
|
324
|
+
pidsLimit: 512,
|
|
325
|
+
};
|
|
306
326
|
if (file.values.browser !== undefined) {
|
|
307
327
|
const raw = file.values.browser;
|
|
308
328
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
309
329
|
throw new HostConfigError(`Invalid value for browser`, "config_invalid", "browser");
|
|
310
330
|
}
|
|
311
331
|
for (const sub of Object.keys(raw)) {
|
|
312
|
-
if (sub !== "blocked_sites") {
|
|
332
|
+
if (sub !== "blocked_sites" && sub !== "launch_args" && sub !== "executable_path" && sub !== "dialog_timeout_seconds" && sub !== "host" && sub !== "container" && sub !== "owner_hosts") {
|
|
313
333
|
throw new HostConfigError(`Unknown key "${sub}" under browser`, "config_unknown_key", `browser.${sub}`);
|
|
314
334
|
}
|
|
315
335
|
}
|
|
336
|
+
const args = raw.launch_args;
|
|
337
|
+
if (args !== undefined && args !== null) {
|
|
338
|
+
if (!Array.isArray(args) || !args.every((a) => typeof a === "string")) {
|
|
339
|
+
throw new HostConfigError("Invalid value for browser.launch_args; expected a list of strings", "config_invalid", "browser.launch_args");
|
|
340
|
+
}
|
|
341
|
+
for (const arg of args) {
|
|
342
|
+
if (arg.trim() === "" || /[,\r\n]/.test(arg)) {
|
|
343
|
+
throw new HostConfigError(`Invalid item ${JSON.stringify(arg)} in browser.launch_args; each item must be non-empty with no comma or newline`, "config_invalid", "browser.launch_args");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
browserLaunchArgs = args.map((a) => a.trim());
|
|
347
|
+
}
|
|
348
|
+
const exe = raw.executable_path;
|
|
349
|
+
if (exe !== undefined && exe !== null) {
|
|
350
|
+
if (typeof exe !== "string" ||
|
|
351
|
+
exe.trim() === "" ||
|
|
352
|
+
!(path.posix.isAbsolute(exe.trim()) || path.win32.isAbsolute(exe.trim()))) {
|
|
353
|
+
throw new HostConfigError("Invalid value for browser.executable_path; expected a non-empty absolute path", "config_invalid", "browser.executable_path");
|
|
354
|
+
}
|
|
355
|
+
browserExecutablePath = exe.trim();
|
|
356
|
+
}
|
|
357
|
+
const dialogTimeout = raw.dialog_timeout_seconds;
|
|
358
|
+
if (dialogTimeout !== undefined && dialogTimeout !== null) {
|
|
359
|
+
if (typeof dialogTimeout !== "number" ||
|
|
360
|
+
!Number.isInteger(dialogTimeout) ||
|
|
361
|
+
dialogTimeout < 1) {
|
|
362
|
+
throw new HostConfigError("Invalid value for browser.dialog_timeout_seconds; expected a positive integer", "config_invalid", "browser.dialog_timeout_seconds");
|
|
363
|
+
}
|
|
364
|
+
browserDialogTimeoutSeconds = dialogTimeout;
|
|
365
|
+
}
|
|
366
|
+
const host = raw.host;
|
|
367
|
+
if (host !== undefined && host !== null) {
|
|
368
|
+
if (host !== "local" && host !== "container") {
|
|
369
|
+
throw new HostConfigError('Invalid value for browser.host; expected "local" or "container"', "config_invalid", "browser.host");
|
|
370
|
+
}
|
|
371
|
+
browserHost = host;
|
|
372
|
+
}
|
|
373
|
+
const ownerHosts = raw.owner_hosts;
|
|
374
|
+
if (ownerHosts !== undefined && ownerHosts !== null) {
|
|
375
|
+
if (typeof ownerHosts !== "object" || Array.isArray(ownerHosts)) {
|
|
376
|
+
throw new HostConfigError("Invalid value for browser.owner_hosts", "config_invalid", "browser.owner_hosts");
|
|
377
|
+
}
|
|
378
|
+
for (const [scope, value] of Object.entries(ownerHosts)) {
|
|
379
|
+
try {
|
|
380
|
+
validateProfileScope(scope);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
throw new HostConfigError(`Invalid owner scope "${scope}" in browser.owner_hosts`, "config_invalid", "browser.owner_hosts");
|
|
384
|
+
}
|
|
385
|
+
if (value !== "local" && value !== "container") {
|
|
386
|
+
throw new HostConfigError(`Invalid value for browser.owner_hosts.${scope}; expected "local" or "container"`, "config_invalid", `browser.owner_hosts.${scope}`);
|
|
387
|
+
}
|
|
388
|
+
browserOwnerHosts[scope] = value;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const container = raw.container;
|
|
392
|
+
if (container !== undefined && container !== null) {
|
|
393
|
+
if (typeof container !== "object" || Array.isArray(container)) {
|
|
394
|
+
throw new HostConfigError("Invalid value for browser.container", "config_invalid", "browser.container");
|
|
395
|
+
}
|
|
396
|
+
const fields = container;
|
|
397
|
+
for (const sub of Object.keys(fields)) {
|
|
398
|
+
if (!["image", "shm_size", "memory", "pids_limit"].includes(sub)) {
|
|
399
|
+
throw new HostConfigError(`Unknown key "${sub}" under browser.container`, "config_unknown_key", `browser.container.${sub}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const text = (key, pattern, target) => {
|
|
403
|
+
const value = fields[key];
|
|
404
|
+
if (value === undefined || value === null)
|
|
405
|
+
return;
|
|
406
|
+
if (typeof value !== "string" || !pattern.test(value.trim())) {
|
|
407
|
+
throw new HostConfigError(`Invalid value for browser.container.${key}`, "config_invalid", `browser.container.${key}`);
|
|
408
|
+
}
|
|
409
|
+
browserContainer[target] = value.trim();
|
|
410
|
+
};
|
|
411
|
+
text("image", /^[A-Za-z0-9][A-Za-z0-9._\-/:@]*$/, "image");
|
|
412
|
+
text("shm_size", /^[1-9][0-9]*[bkmg]?$/i, "shmSize");
|
|
413
|
+
text("memory", /^[1-9][0-9]*[bkmg]?$/i, "memory");
|
|
414
|
+
const pids = fields.pids_limit;
|
|
415
|
+
if (pids !== undefined && pids !== null) {
|
|
416
|
+
if (typeof pids !== "number" || !Number.isInteger(pids) || pids < 1) {
|
|
417
|
+
throw new HostConfigError("Invalid value for browser.container.pids_limit; expected a positive integer", "config_invalid", "browser.container.pids_limit");
|
|
418
|
+
}
|
|
419
|
+
browserContainer.pidsLimit = pids;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
316
422
|
const sites = raw.blocked_sites;
|
|
317
423
|
if (sites !== undefined) {
|
|
318
424
|
browserBlockedSites = coerceFileStringList(sites, "browser.blocked_sites")
|
|
@@ -336,6 +442,12 @@ export function loadHostConfig(options) {
|
|
|
336
442
|
readToken,
|
|
337
443
|
callers,
|
|
338
444
|
browserBlockedSites,
|
|
445
|
+
browserLaunchArgs,
|
|
446
|
+
browserExecutablePath,
|
|
447
|
+
browserDialogTimeoutSeconds,
|
|
448
|
+
browserHost,
|
|
449
|
+
browserOwnerHosts,
|
|
450
|
+
browserContainer,
|
|
339
451
|
configFilePath: configFilePath !== undefined && existsSync(configFilePath)
|
|
340
452
|
? configFilePath
|
|
341
453
|
: undefined,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CreateTaskResult } from "./createTask.js";
|
|
2
|
+
export declare const GITHUB_REPO_PATTERN: RegExp;
|
|
3
|
+
export type ImportTaskInput = {
|
|
4
|
+
repo: string;
|
|
5
|
+
number: number;
|
|
6
|
+
directory: string;
|
|
7
|
+
};
|
|
8
|
+
export type ImportTaskParseError = {
|
|
9
|
+
ok: false;
|
|
10
|
+
status: 400;
|
|
11
|
+
error: string;
|
|
12
|
+
};
|
|
13
|
+
export type IssueFetch = (url: string, init: {
|
|
14
|
+
headers: Record<string, string>;
|
|
15
|
+
}) => Promise<Response>;
|
|
16
|
+
export declare function parseImportTaskBody(body: unknown): ImportTaskInput | ImportTaskParseError;
|
|
17
|
+
export declare function slugIssueTitle(title: string, issueNumber: number): string;
|
|
18
|
+
export declare function taskIdWithSuffix(baseId: string, suffixNumber: number): string;
|
|
19
|
+
export declare function importTaskFromIssue(rawProjectRoot: string, input: ImportTaskInput, fetchImpl?: IssueFetch): Promise<CreateTaskResult>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { STAGE_ID_PATTERN } from "./createStage.js";
|
|
4
|
+
import { createTask, findTaskIdCollision, resolveTaskDirectory, validateTaskId, } from "./createTask.js";
|
|
5
|
+
export const GITHUB_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
export function parseImportTaskBody(body) {
|
|
10
|
+
if (!isPlainObject(body)) {
|
|
11
|
+
return { ok: false, status: 400, error: "Request body must be an object" };
|
|
12
|
+
}
|
|
13
|
+
if (typeof body.repo !== "string" || !GITHUB_REPO_PATTERN.test(body.repo)) {
|
|
14
|
+
return { ok: false, status: 400, error: "repo must be a GitHub owner/repo" };
|
|
15
|
+
}
|
|
16
|
+
if (typeof body.number !== "number" || !Number.isInteger(body.number) || body.number <= 0) {
|
|
17
|
+
return { ok: false, status: 400, error: "number must be a positive integer" };
|
|
18
|
+
}
|
|
19
|
+
if (typeof body.directory !== "string" || body.directory.trim().length === 0) {
|
|
20
|
+
return { ok: false, status: 400, error: "directory is required" };
|
|
21
|
+
}
|
|
22
|
+
const directory = body.directory.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
23
|
+
if (directory.length === 0) {
|
|
24
|
+
return { ok: false, status: 400, error: "directory is required" };
|
|
25
|
+
}
|
|
26
|
+
return { repo: body.repo, number: body.number, directory };
|
|
27
|
+
}
|
|
28
|
+
export function slugIssueTitle(title, issueNumber) {
|
|
29
|
+
const fallback = `issue-${issueNumber}`;
|
|
30
|
+
let slug = title
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
33
|
+
.replace(/^-+|-+$/g, "")
|
|
34
|
+
.replace(/^[^a-z]+/, "")
|
|
35
|
+
.replace(/^-+|-+$/g, "");
|
|
36
|
+
if (slug.length > 64) {
|
|
37
|
+
slug = slug.slice(0, 64).replace(/-+$/g, "");
|
|
38
|
+
}
|
|
39
|
+
if (!slug || !STAGE_ID_PATTERN.test(slug))
|
|
40
|
+
return fallback;
|
|
41
|
+
return slug;
|
|
42
|
+
}
|
|
43
|
+
export function taskIdWithSuffix(baseId, suffixNumber) {
|
|
44
|
+
const suffix = `-${suffixNumber}`;
|
|
45
|
+
let stem = baseId;
|
|
46
|
+
if (stem.length + suffix.length > 64) {
|
|
47
|
+
stem = stem.slice(0, 64 - suffix.length).replace(/-+$/g, "");
|
|
48
|
+
}
|
|
49
|
+
if (!stem || !STAGE_ID_PATTERN.test(stem))
|
|
50
|
+
stem = "issue";
|
|
51
|
+
const candidate = `${stem}${suffix}`;
|
|
52
|
+
if (candidate.length <= 64 && STAGE_ID_PATTERN.test(candidate))
|
|
53
|
+
return candidate;
|
|
54
|
+
return `issue${suffix}`;
|
|
55
|
+
}
|
|
56
|
+
async function allocateTaskId(projectRoot, directory, baseId) {
|
|
57
|
+
for (let n = 1; n <= 100; n += 1) {
|
|
58
|
+
const id = n === 1 ? baseId : taskIdWithSuffix(baseId, n);
|
|
59
|
+
const idError = validateTaskId(id);
|
|
60
|
+
if (idError)
|
|
61
|
+
continue;
|
|
62
|
+
const targetPath = path.join(directory, `${id}.task.yaml`);
|
|
63
|
+
const collision = await findTaskIdCollision(projectRoot, id, targetPath);
|
|
64
|
+
if (!collision)
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function githubHeaders() {
|
|
70
|
+
const headers = {
|
|
71
|
+
Accept: "application/vnd.github+json",
|
|
72
|
+
"User-Agent": "stageflow",
|
|
73
|
+
};
|
|
74
|
+
const token = process.env.GITHUB_TOKEN;
|
|
75
|
+
if (typeof token === "string" && token.length > 0) {
|
|
76
|
+
headers.Authorization = `Bearer ${token}`;
|
|
77
|
+
}
|
|
78
|
+
return headers;
|
|
79
|
+
}
|
|
80
|
+
export async function importTaskFromIssue(rawProjectRoot, input, fetchImpl = (url, init) => fetch(url, init)) {
|
|
81
|
+
if (!GITHUB_REPO_PATTERN.test(input.repo)) {
|
|
82
|
+
return { ok: false, status: 400, error: "repo must be a GitHub owner/repo" };
|
|
83
|
+
}
|
|
84
|
+
if (!Number.isInteger(input.number) || input.number <= 0) {
|
|
85
|
+
return { ok: false, status: 400, error: "number must be a positive integer" };
|
|
86
|
+
}
|
|
87
|
+
const projectRoot = await realpath(rawProjectRoot).catch(() => rawProjectRoot);
|
|
88
|
+
const directory = resolveTaskDirectory(projectRoot, input.directory);
|
|
89
|
+
if (!directory) {
|
|
90
|
+
return { ok: false, status: 400, error: "directory must be inside the project root" };
|
|
91
|
+
}
|
|
92
|
+
const url = `https://api.github.com/repos/${input.repo}/issues/${input.number}`;
|
|
93
|
+
let response;
|
|
94
|
+
try {
|
|
95
|
+
response = await fetchImpl(url, { headers: githubHeaders() });
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
99
|
+
return { ok: false, status: 502, error: message };
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 404) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
status: 404,
|
|
105
|
+
error: `GitHub issue not found: ${input.repo}#${input.number}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
return { ok: false, status: 502, error: `GitHub request failed (${response.status})` };
|
|
110
|
+
}
|
|
111
|
+
let payload;
|
|
112
|
+
try {
|
|
113
|
+
payload = await response.json();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { ok: false, status: 502, error: "GitHub response was not JSON" };
|
|
117
|
+
}
|
|
118
|
+
if (!isPlainObject(payload) || typeof payload.title !== "string" || payload.title.length === 0) {
|
|
119
|
+
return { ok: false, status: 502, error: "GitHub issue is missing a title" };
|
|
120
|
+
}
|
|
121
|
+
const context = typeof payload.body === "string" ? payload.body : "";
|
|
122
|
+
const baseId = slugIssueTitle(payload.title, input.number);
|
|
123
|
+
const id = await allocateTaskId(projectRoot, directory, baseId);
|
|
124
|
+
if (!id) {
|
|
125
|
+
return { ok: false, status: 409, error: "Unable to allocate a task id" };
|
|
126
|
+
}
|
|
127
|
+
return createTask(projectRoot, {
|
|
128
|
+
directory: input.directory,
|
|
129
|
+
id,
|
|
130
|
+
goal: payload.title,
|
|
131
|
+
...(context.length > 0 ? { context } : {}),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type InstallSkillParseError = {
|
|
2
|
+
ok: false;
|
|
3
|
+
status: 400;
|
|
4
|
+
error: string;
|
|
5
|
+
};
|
|
6
|
+
export type ParsedInstallSkillRequest = {
|
|
7
|
+
command: string;
|
|
8
|
+
};
|
|
9
|
+
export type ParsedSkillsAddCommand = {
|
|
10
|
+
source: string;
|
|
11
|
+
skillName?: string;
|
|
12
|
+
};
|
|
13
|
+
export type SkillCloneFn = (cloneUrl: string, destDir: string) => Promise<void>;
|
|
14
|
+
export type InstallSkillFromCommandResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
skill_name: string;
|
|
17
|
+
dest_path: string;
|
|
18
|
+
project_root: string;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: string;
|
|
22
|
+
doctorFailed?: boolean;
|
|
23
|
+
status?: 400 | 422;
|
|
24
|
+
};
|
|
25
|
+
export declare function parseSkillsAddCommand(command: string): ParsedSkillsAddCommand | InstallSkillParseError;
|
|
26
|
+
export declare function parseInstallSkillBody(body: unknown): ParsedInstallSkillRequest | InstallSkillParseError;
|
|
27
|
+
export type LocatedSkill = {
|
|
28
|
+
name: string;
|
|
29
|
+
root: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function locateSkillDirectory(checkoutRoot: string, skillName?: string): Promise<LocatedSkill>;
|
|
32
|
+
export declare function resolveGitCloneUrl(source: string): string;
|
|
33
|
+
export declare function defaultCloneSkillRepo(cloneUrl: string, destDir: string): Promise<void>;
|
|
34
|
+
export declare function installSkillFromCommand(input: {
|
|
35
|
+
projectRoot: string;
|
|
36
|
+
command: string;
|
|
37
|
+
cloneRepo?: SkillCloneFn;
|
|
38
|
+
}): Promise<InstallSkillFromCommandResult>;
|
|
39
|
+
/** Test helper: build a file: command for a local skill tree. */
|
|
40
|
+
export declare function skillsAddFileCommand(fromPath: string, skillName?: string): string;
|