stageflow 0.2.0 → 0.8.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 +179 -14
- package/dist/agent/activity.d.ts +12 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +15 -7
- package/dist/agent/piAdapter.d.ts +18 -1
- package/dist/agent/piAdapter.js +115 -45
- package/dist/agent/port.d.ts +7 -0
- package/dist/agent/port.js +3 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -0
- package/dist/cli/artifactCommand.d.ts +10 -0
- package/dist/cli/artifactCommand.js +120 -0
- package/dist/cli/envelopeCommand.d.ts +32 -0
- package/dist/cli/envelopeCommand.js +285 -0
- package/dist/cli/exportRunCommand.d.ts +10 -0
- package/dist/cli/exportRunCommand.js +150 -0
- package/dist/cli/handoffFormat.d.ts +22 -0
- package/dist/cli/handoffFormat.js +61 -0
- package/dist/cli/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +7 -1
- package/dist/cli/runCommand.js +83 -11
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -22
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +88 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
- package/dist/config/normalizePipelineStageEntry.js +210 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +4 -0
- package/dist/config/resolveForkEmitContext.js +34 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +86 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.d.ts +2 -0
- package/dist/envelope/check.js +58 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/catalogTools.d.ts +3 -0
- package/dist/mcp/catalogTools.js +187 -0
- package/dist/mcp/controlTools.d.ts +3 -0
- package/dist/mcp/controlTools.js +147 -0
- package/dist/mcp/deps.d.ts +11 -0
- package/dist/mcp/deps.js +1 -0
- package/dist/mcp/projectRun.d.ts +1 -37
- package/dist/mcp/projectRun.js +1 -57
- package/dist/mcp/resources.d.ts +5 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/server.d.ts +16 -3
- package/dist/mcp/server.js +159 -3
- package/dist/mcp/toolResults.d.ts +7 -0
- package/dist/mcp/toolResults.js +6 -0
- package/dist/mcp/tools.d.ts +2 -7
- package/dist/mcp/tools.js +52 -86
- package/dist/mcp/waitRun.d.ts +46 -0
- package/dist/mcp/waitRun.js +146 -0
- package/dist/mcp/waitingGates.d.ts +12 -0
- package/dist/mcp/waitingGates.js +83 -0
- package/dist/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -0
- package/dist/projection/projectRun.d.ts +44 -0
- package/dist/projection/projectRun.js +71 -0
- package/dist/prompt/priorEnvelope.d.ts +1 -1
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/paths.js +2 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
- package/dist/runstore/pipelineDagSnapshot.js +90 -1
- package/dist/runstore/port.d.ts +22 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +25 -4
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- package/dist/runstore/stageInstanceId.d.ts +4 -0
- package/dist/runstore/stageInstanceId.js +23 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
- package/dist/runstore/syntheticStageSnapshot.js +2 -1
- package/dist/runstore/trackProjection.js +28 -12
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +1 -1
- package/dist/runtime/cloneSchedule.d.ts +18 -0
- package/dist/runtime/cloneSchedule.js +233 -0
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +8 -2
- package/dist/runtime/pipelineScheduler.js +215 -39
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +31 -10
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +18 -7
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +13 -10
- package/dist/server/bootstrap.d.ts +32 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/createHttpHost.d.ts +34 -0
- package/dist/server/createHttpHost.js +70 -0
- package/dist/server/http.d.ts +6 -13
- package/dist/server/http.js +297 -352
- package/dist/server/mcpHost.d.ts +22 -0
- package/dist/server/mcpHost.js +21 -0
- package/dist/server/operatorResults.d.ts +13 -0
- package/dist/server/operatorResults.js +22 -0
- package/dist/tools/emitStageEnvelope.d.ts +12 -1
- package/dist/tools/emitStageEnvelope.js +27 -1
- package/dist/types/envelope.d.ts +3 -0
- package/dist/types/forkChoice.d.ts +30 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +59 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +8 -5
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +26 -0
- package/skills/stageflow/scripts/detect-host.mjs +54 -0
- package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
- package/skills/stageflow-author/SKILL.md +58 -0
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
- package/skills/stageflow-author/references/catalog-mapping.md +131 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
- package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
- package/skills/stageflow-author/references/validate-and-report.md +38 -0
- package/skills/stageflow-delegate/SKILL.md +18 -0
- package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
- package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
- package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
- package/skills/stageflow-run/SKILL.md +151 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
- package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
- package/skills/stageflow-session-capture/SKILL.md +94 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
- package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
- package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
- package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
- package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
- package/skills/stageflow-setup/SKILL.md +110 -0
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
package/dist/server/http.js
CHANGED
|
@@ -1,54 +1,21 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
1
|
import { readFile, access } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { localhostHostValidation, localhostOriginValidation, } from "@modelcontextprotocol/node";
|
|
7
4
|
import { getCredentialSourceSettings, setCredentialSource, } from "../agent/providerAuth.js";
|
|
8
5
|
import { handleProviderRoutes, providerAuthErrorBody, } from "./providerRoutes.js";
|
|
9
6
|
import { createPipeline, parseCreatePipelineBody } from "../config/createPipeline.js";
|
|
10
7
|
import { createStage, parseCreateStageBody } from "../config/createStage.js";
|
|
11
|
-
import {
|
|
8
|
+
import { browseCatalog } from "../config/browseCatalog.js";
|
|
12
9
|
import { listExtensions } from "../config/listExtensions.js";
|
|
13
10
|
import { listSkills } from "../config/listSkills.js";
|
|
14
11
|
import { readRunArtifact } from "../mcp/readArtifact.js";
|
|
15
|
-
import {
|
|
16
|
-
import { createRunStore } from "../runstore/createStore.js";
|
|
17
|
-
import { RunManager, } from "../runtime/runManager.js";
|
|
12
|
+
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
18
13
|
import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount, } from "../runtime/settingsFile.js";
|
|
19
14
|
import { isTaskFile } from "../runtime/taskInput.js";
|
|
20
15
|
import { parseAskOperatorAnswer } from "../tools/askOperator.js";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
res.writeHead(status, {
|
|
25
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
26
|
-
"Content-Length": Buffer.byteLength(payload),
|
|
27
|
-
});
|
|
28
|
-
res.end(payload);
|
|
29
|
-
}
|
|
30
|
-
function startFailureBody(result) {
|
|
31
|
-
const { ok: _ok, status: _status, reason, ...rest } = result;
|
|
32
|
-
return { error: reason, ...rest };
|
|
33
|
-
}
|
|
34
|
-
function inferRetryStageErrorCode(reason) {
|
|
35
|
-
if (/retry already in progress/i.test(reason))
|
|
36
|
-
return "retry_in_progress";
|
|
37
|
-
if (/waiting for input/i.test(reason))
|
|
38
|
-
return "hitl_not_retriable";
|
|
39
|
-
if (/already has active orchestration/i.test(reason))
|
|
40
|
-
return "run_not_retryable";
|
|
41
|
-
if (/run is not failed/i.test(reason))
|
|
42
|
-
return "run_not_retryable";
|
|
43
|
-
if (/stage is not failed/i.test(reason))
|
|
44
|
-
return "stage_not_failed";
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
function retryStageFailureBody(result) {
|
|
48
|
-
const { ok: _ok, status: _status, reason, ...rest } = result;
|
|
49
|
-
const code = inferRetryStageErrorCode(reason);
|
|
50
|
-
return { error: reason, ...(code ? { code } : {}), ...rest };
|
|
51
|
-
}
|
|
16
|
+
import { bootstrapStageflowHost, } from "./bootstrap.js";
|
|
17
|
+
import { createHttpHost, DEFAULT_PORT, json, } from "./createHttpHost.js";
|
|
18
|
+
import { mapRetryStageFailure, mapStartFailure } from "./operatorResults.js";
|
|
52
19
|
function textPlain(res, status, body) {
|
|
53
20
|
res.writeHead(status, {
|
|
54
21
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -202,360 +169,338 @@ export function defaultUiDistDir() {
|
|
|
202
169
|
return path.resolve(here, "../ui");
|
|
203
170
|
}
|
|
204
171
|
export async function startUiServer(options) {
|
|
205
|
-
const cwd = options.cwd ?? process.cwd();
|
|
206
|
-
const agentDir = options.agentDir ?? getAgentDir();
|
|
207
|
-
const rootDir = options.rootDir ?? cwd;
|
|
208
172
|
const host = options.host ?? "127.0.0.1";
|
|
209
173
|
const port = options.port ?? DEFAULT_PORT;
|
|
210
|
-
const providerAuthContext = options.providerAuthContext;
|
|
211
174
|
const uiDistDir = options.uiDistDir ?? defaultUiDistDir();
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const pathname = url.pathname;
|
|
229
|
-
if (pathname === "/mcp") {
|
|
230
|
-
if (!validateMcpHost(req, res) || !validateMcpOrigin(req, res)) {
|
|
231
|
-
return;
|
|
175
|
+
const boot = await bootstrapStageflowHost(options);
|
|
176
|
+
const { manager, store, cwd, agentDir } = boot;
|
|
177
|
+
const providerAuthContext = boot.providerAuthContext;
|
|
178
|
+
return createHttpHost({
|
|
179
|
+
boot,
|
|
180
|
+
host,
|
|
181
|
+
port,
|
|
182
|
+
routes: async ({ req, res, url, pathname, method }) => {
|
|
183
|
+
if (isMutatingApi(method, pathname)) {
|
|
184
|
+
if (!assertLoopbackHttpAccess(req, res)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (isCredentialMutatingApi(method, pathname) &&
|
|
188
|
+
!assertCredentialMutatingOrigin(req, res)) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
232
191
|
}
|
|
233
192
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (!res.headersSent) {
|
|
238
|
-
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
239
|
-
res.end(err instanceof Error ? err.message : String(err));
|
|
193
|
+
if (method === "GET" && pathname === "/api/runs") {
|
|
194
|
+
json(res, 200, { runs: await store.listRuns() });
|
|
195
|
+
return true;
|
|
240
196
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return;
|
|
197
|
+
const artifactMatch = pathname.match(/^\/api\/runs\/([^/]+)\/artifact$/);
|
|
198
|
+
if (method === "GET" && artifactMatch) {
|
|
199
|
+
const runId = decodeURIComponent(artifactMatch[1] ?? "");
|
|
200
|
+
const artifactPath = url.searchParams.get("path");
|
|
201
|
+
if (artifactPath === null || artifactPath.trim().length === 0) {
|
|
202
|
+
json(res, 400, { error: "path query parameter is required" });
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const content = await readRunArtifact(store, runId, artifactPath);
|
|
207
|
+
textPlain(res, 200, content);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
211
|
+
if (message === "Artifact path denied") {
|
|
212
|
+
json(res, 403, { error: message });
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
const notFound = message.startsWith("Run not found") ||
|
|
216
|
+
message.startsWith("Artifact not found") ||
|
|
217
|
+
/no such|not found/i.test(message);
|
|
218
|
+
json(res, notFound ? 404 : 400, { error: message });
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (method === "GET" && pathname.startsWith("/api/runs/")) {
|
|
223
|
+
const rest = pathname.slice("/api/runs/".length);
|
|
224
|
+
if (rest && !rest.includes("/")) {
|
|
225
|
+
try {
|
|
226
|
+
json(res, 200, await store.readRun(decodeURIComponent(rest)));
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
json(res, 404, {
|
|
230
|
+
error: err instanceof Error ? err.message : String(err),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
265
235
|
}
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
236
|
+
if (method === "POST" && pathname === "/api/runs") {
|
|
237
|
+
const body = (await readJsonBody(req));
|
|
238
|
+
if (typeof body.pipeline !== "string" ||
|
|
239
|
+
!body.pipeline.trim() ||
|
|
240
|
+
body.task === undefined) {
|
|
241
|
+
json(res, 400, { error: "task and pipeline are required" });
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (typeof body.task !== "string" && !isTaskFile(body.task)) {
|
|
245
|
+
json(res, 400, {
|
|
246
|
+
error: "task must be a path string or TaskFile object (id and goal required)",
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
const result = await manager.startRun({
|
|
251
|
+
task: body.task,
|
|
252
|
+
pipeline: body.pipeline.trim(),
|
|
253
|
+
});
|
|
254
|
+
if (!result.ok) {
|
|
255
|
+
json(res, result.status ?? 500, mapStartFailure(result));
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
json(res, 202, { runId: result.runId });
|
|
259
|
+
return true;
|
|
269
260
|
}
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
261
|
+
if (method === "POST" && pathname.match(/^\/api\/runs\/[^/]+\/rerun$/)) {
|
|
262
|
+
const runId = decodeURIComponent(pathname.split("/")[3] ?? "");
|
|
263
|
+
const result = await manager.rerun(runId);
|
|
264
|
+
if (!result.ok) {
|
|
265
|
+
json(res, result.status ?? 500, mapStartFailure(result));
|
|
266
|
+
return true;
|
|
275
267
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
/no such|not found/i.test(message);
|
|
279
|
-
json(res, notFound ? 404 : 400, { error: message });
|
|
268
|
+
json(res, 202, { runId: result.runId });
|
|
269
|
+
return true;
|
|
280
270
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
271
|
+
const answerMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/answer$/);
|
|
272
|
+
if (method === "POST" && answerMatch) {
|
|
273
|
+
const runId = decodeURIComponent(answerMatch[1] ?? "");
|
|
274
|
+
const stageId = decodeURIComponent(answerMatch[2] ?? "");
|
|
275
|
+
let answer;
|
|
286
276
|
try {
|
|
287
|
-
|
|
277
|
+
answer = parseAskOperatorAnswer(await readJsonBody(req));
|
|
288
278
|
}
|
|
289
279
|
catch (err) {
|
|
290
|
-
json(res,
|
|
280
|
+
json(res, 400, {
|
|
291
281
|
error: err instanceof Error ? err.message : String(err),
|
|
292
282
|
});
|
|
283
|
+
return true;
|
|
293
284
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
body.task === undefined) {
|
|
302
|
-
json(res, 400, { error: "task and pipeline are required" });
|
|
303
|
-
return;
|
|
285
|
+
const result = await manager.deliverAnswer(runId, stageId, answer);
|
|
286
|
+
if (!result.ok) {
|
|
287
|
+
json(res, result.status, { error: result.reason });
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
json(res, 202, { ok: true });
|
|
291
|
+
return true;
|
|
304
292
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
293
|
+
const retryMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/retry$/);
|
|
294
|
+
if (method === "POST" && retryMatch) {
|
|
295
|
+
await readJsonBody(req);
|
|
296
|
+
const runId = decodeURIComponent(retryMatch[1] ?? "");
|
|
297
|
+
const stageId = decodeURIComponent(retryMatch[2] ?? "");
|
|
298
|
+
const result = await manager.retryStage(runId, stageId);
|
|
299
|
+
if (!result.ok) {
|
|
300
|
+
json(res, result.status ?? 500, mapRetryStageFailure(result));
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
json(res, 202, {
|
|
304
|
+
runId: result.runId,
|
|
305
|
+
stageId: result.stageId,
|
|
306
|
+
attemptIndex: result.attemptIndex,
|
|
308
307
|
});
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
const result = await manager.startRun({
|
|
312
|
-
task: body.task,
|
|
313
|
-
pipeline: body.pipeline.trim(),
|
|
314
|
-
});
|
|
315
|
-
if (!result.ok) {
|
|
316
|
-
json(res, result.status ?? 500, startFailureBody(result));
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
json(res, 202, { runId: result.runId });
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (method === "POST" && pathname.match(/^\/api\/runs\/[^/]+\/rerun$/)) {
|
|
323
|
-
const runId = decodeURIComponent(pathname.split("/")[3] ?? "");
|
|
324
|
-
const result = await manager.rerun(runId);
|
|
325
|
-
if (!result.ok) {
|
|
326
|
-
json(res, result.status ?? 500, startFailureBody(result));
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
json(res, 202, { runId: result.runId });
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const answerMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/answer$/);
|
|
333
|
-
if (method === "POST" && answerMatch) {
|
|
334
|
-
const runId = decodeURIComponent(answerMatch[1] ?? "");
|
|
335
|
-
const stageId = decodeURIComponent(answerMatch[2] ?? "");
|
|
336
|
-
let answer;
|
|
337
|
-
try {
|
|
338
|
-
answer = parseAskOperatorAnswer(await readJsonBody(req));
|
|
308
|
+
return true;
|
|
339
309
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
310
|
+
const abandonMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/abandon$/);
|
|
311
|
+
if (method === "POST" && abandonMatch) {
|
|
312
|
+
await readJsonBody(req);
|
|
313
|
+
const runId = decodeURIComponent(abandonMatch[1] ?? "");
|
|
314
|
+
const stageId = decodeURIComponent(abandonMatch[2] ?? "");
|
|
315
|
+
const result = await manager.abandonStage(runId, stageId);
|
|
316
|
+
if (!result.ok) {
|
|
317
|
+
json(res, result.status ?? 500, {
|
|
318
|
+
error: result.reason,
|
|
319
|
+
});
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
json(res, 202, {
|
|
323
|
+
ok: true,
|
|
324
|
+
runId: result.runId,
|
|
325
|
+
stageId: result.stageId,
|
|
343
326
|
});
|
|
344
|
-
return;
|
|
327
|
+
return true;
|
|
345
328
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
json(res,
|
|
349
|
-
return;
|
|
329
|
+
if (method === "GET" && pathname === "/api/tasks") {
|
|
330
|
+
const catalog = await browseCatalog(cwd);
|
|
331
|
+
json(res, 200, { tasks: catalog.tasks });
|
|
332
|
+
return true;
|
|
350
333
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (method === "POST" && retryMatch) {
|
|
356
|
-
await readJsonBody(req);
|
|
357
|
-
const runId = decodeURIComponent(retryMatch[1] ?? "");
|
|
358
|
-
const stageId = decodeURIComponent(retryMatch[2] ?? "");
|
|
359
|
-
const result = await manager.retryStage(runId, stageId);
|
|
360
|
-
if (!result.ok) {
|
|
361
|
-
json(res, result.status ?? 500, retryStageFailureBody(result));
|
|
362
|
-
return;
|
|
334
|
+
if (method === "GET" && pathname === "/api/pipelines") {
|
|
335
|
+
const catalog = await browseCatalog(cwd);
|
|
336
|
+
json(res, 200, { pipelines: catalog.pipelines });
|
|
337
|
+
return true;
|
|
363
338
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
attemptIndex: result.attemptIndex,
|
|
368
|
-
});
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const abandonMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/abandon$/);
|
|
372
|
-
if (method === "POST" && abandonMatch) {
|
|
373
|
-
await readJsonBody(req);
|
|
374
|
-
const runId = decodeURIComponent(abandonMatch[1] ?? "");
|
|
375
|
-
const stageId = decodeURIComponent(abandonMatch[2] ?? "");
|
|
376
|
-
const result = await manager.abandonStage(runId, stageId);
|
|
377
|
-
if (!result.ok) {
|
|
378
|
-
json(res, result.status ?? 500, {
|
|
379
|
-
error: result.reason,
|
|
339
|
+
if (method === "GET" && pathname === "/api/stages") {
|
|
340
|
+
json(res, 404, {
|
|
341
|
+
error: "Global stage library removed; stages are pipeline-scoped",
|
|
380
342
|
});
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
json(res, 202, {
|
|
384
|
-
ok: true,
|
|
385
|
-
runId: result.runId,
|
|
386
|
-
stageId: result.stageId,
|
|
387
|
-
});
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (method === "GET" && pathname === "/api/tasks") {
|
|
391
|
-
json(res, 200, { tasks: await listTasks(cwd) });
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (method === "GET" && pathname === "/api/pipelines") {
|
|
395
|
-
json(res, 200, { pipelines: await listPipelines(cwd) });
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (method === "GET" && pathname === "/api/stages") {
|
|
399
|
-
json(res, 200, { stages: await listStages(cwd) });
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (method === "POST" && pathname === "/api/stages") {
|
|
403
|
-
let body;
|
|
404
|
-
try {
|
|
405
|
-
body = await readJsonBody(req);
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
json(res, 400, { error: "Invalid JSON body" });
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const parsed = parseCreateStageBody(body);
|
|
412
|
-
if ("ok" in parsed) {
|
|
413
|
-
json(res, parsed.status, { error: parsed.error });
|
|
414
|
-
return;
|
|
343
|
+
return true;
|
|
415
344
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
345
|
+
if (method === "POST" && pathname === "/api/stages") {
|
|
346
|
+
let body;
|
|
347
|
+
try {
|
|
348
|
+
body = await readJsonBody(req);
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
const parsed = parseCreateStageBody(body);
|
|
355
|
+
if ("ok" in parsed) {
|
|
356
|
+
json(res, parsed.status, { error: parsed.error });
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
const ctx = await resolveStageflowContext(cwd);
|
|
360
|
+
if (!ctx.isGitProject) {
|
|
361
|
+
json(res, 400, {
|
|
362
|
+
error: "Project root not found; initialize stageflow.yaml in a git repo",
|
|
363
|
+
});
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
const result = await createStage(ctx.projectRoot, parsed);
|
|
367
|
+
if (!result.ok) {
|
|
368
|
+
json(res, result.status, { error: result.error });
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
json(res, 201, result.stage);
|
|
372
|
+
return true;
|
|
420
373
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
374
|
+
if (method === "POST" && pathname === "/api/pipelines") {
|
|
375
|
+
let body;
|
|
376
|
+
try {
|
|
377
|
+
body = await readJsonBody(req);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
const parsed = parseCreatePipelineBody(body);
|
|
384
|
+
if ("ok" in parsed) {
|
|
385
|
+
json(res, parsed.status, { error: parsed.error });
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
const ctx = await resolveStageflowContext(cwd);
|
|
389
|
+
if (!ctx.isGitProject) {
|
|
390
|
+
json(res, 400, {
|
|
391
|
+
error: "Project root not found; initialize stageflow.yaml in a git repo",
|
|
392
|
+
});
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
const result = await createPipeline(ctx.projectRoot, parsed);
|
|
396
|
+
if (!result.ok) {
|
|
397
|
+
json(res, result.status, { error: result.error });
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
json(res, 201, result.pipeline);
|
|
401
|
+
return true;
|
|
428
402
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
403
|
+
if (method === "GET" && pathname === "/api/models") {
|
|
404
|
+
const catalog = await browseCatalog(cwd);
|
|
405
|
+
json(res, 200, { models: catalog.models });
|
|
406
|
+
return true;
|
|
432
407
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
408
|
+
if (await handleProviderRoutes(req, res, {
|
|
409
|
+
cwd,
|
|
410
|
+
readJsonBody,
|
|
411
|
+
json,
|
|
412
|
+
providerAuthContext,
|
|
413
|
+
})) {
|
|
414
|
+
return true;
|
|
437
415
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return;
|
|
416
|
+
if (method === "GET" && pathname === "/api/skills") {
|
|
417
|
+
json(res, 200, await listSkills({ cwd, agentDir }));
|
|
418
|
+
return true;
|
|
442
419
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (method === "GET" && pathname === "/api/models") {
|
|
447
|
-
json(res, 200, { models: await listModels(cwd) });
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (await handleProviderRoutes(req, res, {
|
|
451
|
-
cwd,
|
|
452
|
-
readJsonBody,
|
|
453
|
-
json,
|
|
454
|
-
providerAuthContext,
|
|
455
|
-
})) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (method === "GET" && pathname === "/api/skills") {
|
|
459
|
-
json(res, 200, await listSkills({ cwd, agentDir }));
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (method === "GET" && pathname === "/api/extensions") {
|
|
463
|
-
json(res, 200, await listExtensions({ cwd, agentDir }));
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
if (method === "GET" && pathname === "/api/health") {
|
|
467
|
-
json(res, 200, manager.getHealth());
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (method === "GET" && pathname === "/api/settings") {
|
|
471
|
-
const health = manager.getHealth();
|
|
472
|
-
const credential = getCredentialSourceSettings(cwd);
|
|
473
|
-
json(res, 200, {
|
|
474
|
-
maxConcurrent: health.maxConcurrent,
|
|
475
|
-
...credential,
|
|
476
|
-
});
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
if (method === "POST" && pathname === "/api/settings") {
|
|
480
|
-
let body;
|
|
481
|
-
try {
|
|
482
|
-
body = await readJsonBody(req);
|
|
420
|
+
if (method === "GET" && pathname === "/api/extensions") {
|
|
421
|
+
json(res, 200, await listExtensions({ cwd, agentDir }));
|
|
422
|
+
return true;
|
|
483
423
|
}
|
|
484
|
-
|
|
485
|
-
json(res,
|
|
486
|
-
return;
|
|
424
|
+
if (method === "GET" && pathname === "/api/health") {
|
|
425
|
+
json(res, 200, manager.getHealth());
|
|
426
|
+
return true;
|
|
487
427
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
json(res, 400, {
|
|
495
|
-
error: "maxConcurrent or credentialSource is required",
|
|
428
|
+
if (method === "GET" && pathname === "/api/settings") {
|
|
429
|
+
const health = manager.getHealth();
|
|
430
|
+
const credential = getCredentialSourceSettings(cwd);
|
|
431
|
+
json(res, 200, {
|
|
432
|
+
maxConcurrent: health.maxConcurrent,
|
|
433
|
+
...credential,
|
|
496
434
|
});
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
let health = manager.getHealth();
|
|
500
|
-
if (hasMax) {
|
|
501
|
-
const n = parseSlotCount(record.maxConcurrent);
|
|
502
|
-
if (n === undefined) {
|
|
503
|
-
json(res, 400, { error: INVALID_SLOT_COUNT_MESSAGE });
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
health = manager.setMaxConcurrent(n);
|
|
435
|
+
return true;
|
|
507
436
|
}
|
|
508
|
-
|
|
509
|
-
|
|
437
|
+
if (method === "POST" && pathname === "/api/settings") {
|
|
438
|
+
let body;
|
|
510
439
|
try {
|
|
511
|
-
|
|
440
|
+
body = await readJsonBody(req);
|
|
512
441
|
}
|
|
513
|
-
catch
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
442
|
+
catch {
|
|
443
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
const record = body !== null && typeof body === "object" && !Array.isArray(body)
|
|
447
|
+
? body
|
|
448
|
+
: {};
|
|
449
|
+
const hasMax = Object.prototype.hasOwnProperty.call(record, "maxConcurrent");
|
|
450
|
+
const hasCredentialSource = Object.prototype.hasOwnProperty.call(record, "credentialSource");
|
|
451
|
+
if (!hasMax && !hasCredentialSource) {
|
|
452
|
+
json(res, 400, {
|
|
453
|
+
error: "maxConcurrent or credentialSource is required",
|
|
454
|
+
});
|
|
455
|
+
return true;
|
|
517
456
|
}
|
|
457
|
+
let health = manager.getHealth();
|
|
458
|
+
if (hasMax) {
|
|
459
|
+
const n = parseSlotCount(record.maxConcurrent);
|
|
460
|
+
if (n === undefined) {
|
|
461
|
+
json(res, 400, { error: INVALID_SLOT_COUNT_MESSAGE });
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
health = manager.setMaxConcurrent(n);
|
|
465
|
+
}
|
|
466
|
+
let credential = getCredentialSourceSettings(cwd);
|
|
467
|
+
if (hasCredentialSource) {
|
|
468
|
+
try {
|
|
469
|
+
credential = setCredentialSource(cwd, record.credentialSource);
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
const mapped = providerAuthErrorBody(err);
|
|
473
|
+
json(res, mapped.status, mapped.body);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
json(res, 200, {
|
|
478
|
+
...health,
|
|
479
|
+
...credential,
|
|
480
|
+
});
|
|
481
|
+
return true;
|
|
518
482
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
error: "UI not built. Run npm run ui:build, or use Vite dev proxy.",
|
|
531
|
-
});
|
|
532
|
-
return;
|
|
483
|
+
if (method === "GET") {
|
|
484
|
+
const served = await serveStatic(res, uiDistDir, pathname);
|
|
485
|
+
if (served)
|
|
486
|
+
return true;
|
|
487
|
+
json(res, 404, {
|
|
488
|
+
error: "UI not built. Run npm run ui:build, or use Vite dev proxy.",
|
|
489
|
+
});
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
json(res, 404, { error: "Not found" });
|
|
493
|
+
return true;
|
|
533
494
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
495
|
+
catch (err) {
|
|
496
|
+
if (!res.headersSent) {
|
|
497
|
+
json(res, 500, {
|
|
498
|
+
error: err instanceof Error ? err.message : String(err),
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return true;
|
|
541
502
|
}
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
await new Promise((resolve, reject) => {
|
|
545
|
-
server.listen(port, host, () => resolve());
|
|
546
|
-
server.on("error", reject);
|
|
503
|
+
},
|
|
547
504
|
});
|
|
548
|
-
const address = server.address();
|
|
549
|
-
const boundPort = address && typeof address !== "string" ? address.port : port;
|
|
550
|
-
const url = `http://${host}:${boundPort}`;
|
|
551
|
-
return {
|
|
552
|
-
server,
|
|
553
|
-
port: boundPort,
|
|
554
|
-
host,
|
|
555
|
-
url,
|
|
556
|
-
mcpUrl: `${url}/mcp`,
|
|
557
|
-
manager,
|
|
558
|
-
store,
|
|
559
|
-
};
|
|
560
505
|
}
|
|
561
506
|
export { DEFAULT_PORT };
|