stageflow 0.12.0 → 0.15.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 +5 -1
- package/dist/agent/claudeAdapter.js +31 -1
- package/dist/agent/piAdapter.d.ts +6 -4
- package/dist/agent/piAdapter.js +71 -26
- package/dist/agent/piIsolatedMcp.d.ts +23 -0
- package/dist/agent/piIsolatedMcp.js +223 -0
- package/dist/agent/piIsolatedMcpProbe.d.ts +15 -0
- package/dist/agent/piIsolatedMcpProbe.js +292 -0
- package/dist/agent/port.d.ts +4 -2
- package/dist/agent/port.js +1 -1
- package/dist/cli/initTemplates.d.ts +1 -1
- package/dist/cli/initTemplates.js +1 -1
- package/dist/cli/migrateYamlCommand.d.ts +18 -0
- package/dist/cli/migrateYamlCommand.js +180 -0
- package/dist/cli/runOutput.js +26 -18
- package/dist/cli/validateCommand.d.ts +1 -0
- package/dist/cli/validateCommand.js +2 -0
- package/dist/cli.js +15 -1
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +1 -1
- package/dist/config/createPipeline.js +23 -11
- package/dist/config/createStage.d.ts +1 -1
- package/dist/config/createStage.js +10 -5
- package/dist/config/legacyYaml.d.ts +28 -0
- package/dist/config/legacyYaml.js +98 -0
- package/dist/config/loadOutcome.d.ts +2 -1
- package/dist/config/loadOutcome.js +4 -1
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +151 -8
- package/dist/config/loadStage.d.ts +9 -1
- package/dist/config/loadStage.js +207 -29
- package/dist/config/loadStageflowManifest.js +10 -0
- package/dist/config/loadTask.js +14 -0
- package/dist/config/materializeStageModels.d.ts +7 -0
- package/dist/config/materializeStageModels.js +35 -0
- package/dist/config/mergePipelineIncludes.d.ts +5 -1
- package/dist/config/mergePipelineIncludes.js +82 -4
- package/dist/config/migrateYaml.d.ts +19 -0
- package/dist/config/migrateYaml.js +627 -0
- package/dist/config/modelField.d.ts +13 -0
- package/dist/config/modelField.js +16 -0
- package/dist/config/normalizePipelineStageEntry.js +48 -3
- package/dist/config/parseCompletionContract.d.ts +5 -2
- package/dist/config/parseCompletionContract.js +16 -3
- package/dist/config/parsePreEmitChecks.js +4 -0
- package/dist/config/pipelineNeeds.js +3 -3
- package/dist/config/pipelineStageKeys.d.ts +11 -0
- package/dist/config/pipelineStageKeys.js +15 -0
- package/dist/config/printTargetYaml.d.ts +28 -0
- package/dist/config/printTargetYaml.js +243 -0
- package/dist/config/resolveModel.d.ts +20 -0
- package/dist/config/resolveModel.js +35 -0
- package/dist/config/resolveStageMcpServers.d.ts +35 -0
- package/dist/config/resolveStageMcpServers.js +207 -0
- package/dist/config/validateCatalog.d.ts +5 -1
- package/dist/config/validateCatalog.js +87 -6
- package/dist/config/validateCompletionContract.js +5 -0
- package/dist/config/yamlDialect.d.ts +29 -0
- package/dist/config/yamlDialect.js +327 -0
- package/dist/envelope/payloadSchema.d.ts +37 -5
- package/dist/envelope/payloadSchema.js +272 -22
- package/dist/mcp/catalogTools.js +1 -0
- package/dist/mcp/readArtifact.d.ts +2 -0
- package/dist/mcp/readArtifact.js +25 -1
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/pipelineDagSnapshot.js +2 -0
- package/dist/runstore/port.d.ts +1 -0
- package/dist/runtime/completionCheckRunner.d.ts +3 -2
- package/dist/runtime/completionCheckRunner.js +3 -2
- package/dist/runtime/envelopeRouting.d.ts +2 -2
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +23 -3
- package/dist/runtime/runManager.js +3 -2
- package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
- package/dist/runtime/stageAttemptBootstrap.js +38 -2
- package/dist/runtime/stageRunner.d.ts +2 -2
- package/dist/runtime/taskInput.d.ts +7 -0
- package/dist/runtime/taskInput.js +60 -0
- package/dist/runtime/verifiedStageExecution.d.ts +2 -0
- package/dist/runtime/verifiedStageExecution.js +13 -2
- package/dist/server/http.js +23 -4
- package/dist/server/projectMcpRoutes.d.ts +6 -0
- package/dist/server/projectMcpRoutes.js +38 -0
- package/dist/types/completion.d.ts +6 -0
- package/dist/types/pipeline.d.ts +23 -2
- package/dist/types/preEmitCheck.d.ts +4 -6
- package/dist/types/stage.d.ts +24 -4
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/types/task.d.ts +1 -0
- package/dist/ui/assets/index-GtyiWiZg.js +118 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -1
- package/skills/stageflow-author/SKILL.md +20 -2
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +12 -2
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +11 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +19 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +16 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -1
- package/skills/stageflow-author/references/catalog-mapping.md +80 -17
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-author/references/stage-prompt-template.md +63 -4
- package/skills/stageflow-session-capture/SKILL.md +1 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +2 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +13 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +18 -2
- package/skills/stageflow-session-capture/references/catalog-authoring.md +6 -3
- package/dist/ui/assets/index-CXGz4nL7.js +0 -118
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { resolveSkillByName } from "../config/listSkills.js";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { MCP_CATALOG_FILENAME, STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV, StageMcpError, resolveStageMcpServers, stampStagePromptArtifactsDir, } from "../config/resolveStageMcpServers.js";
|
|
4
|
+
import { attemptArtifactsDir } from "../runstore/workspaceLayout.js";
|
|
2
5
|
import { resolveCloneEmitContext, resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
|
|
3
6
|
import { createAttemptQaTrailReader } from "../hitl/qaTrail.js";
|
|
4
7
|
import { buildCompletedEnvelopesFromRun, resolvePriorEnvelope, } from "./envelopeRouting.js";
|
|
@@ -20,16 +23,49 @@ async function resolveStageSkillForRun(stage, catalog) {
|
|
|
20
23
|
}
|
|
21
24
|
return { ok: true, skillFilePath: resolved.filePath };
|
|
22
25
|
}
|
|
23
|
-
async function
|
|
26
|
+
async function resolveAttemptMcpServers(allowlist, factoryCwd, artifactsDir) {
|
|
27
|
+
const names = allowlist ?? [];
|
|
28
|
+
if (names.length === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (factoryCwd === undefined) {
|
|
31
|
+
throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" is missing`, "missing_catalog");
|
|
32
|
+
}
|
|
33
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
34
|
+
const resolved = await resolveStageMcpServers({
|
|
35
|
+
projectRoot: factoryCwd,
|
|
36
|
+
allowlist: names,
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
[STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV]: artifactsDir,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
43
|
+
}
|
|
44
|
+
async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, artifactsDir) {
|
|
24
45
|
const skill = await resolveStageSkillForRun(input.stage, catalog);
|
|
25
46
|
if (!skill.ok)
|
|
26
47
|
return skill;
|
|
48
|
+
let resolvedMcpServers;
|
|
49
|
+
try {
|
|
50
|
+
resolvedMcpServers = await resolveAttemptMcpServers(input.stage.mcp, factoryCwd, artifactsDir);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof StageMcpError) {
|
|
54
|
+
return { ok: false, reason: err.message };
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
27
58
|
try {
|
|
28
59
|
const handle = agent.openStage({
|
|
29
60
|
...input,
|
|
61
|
+
stage: {
|
|
62
|
+
...input.stage,
|
|
63
|
+
system_prompt: stampStagePromptArtifactsDir(input.stage.system_prompt, artifactsDir),
|
|
64
|
+
},
|
|
30
65
|
...(skill.skillFilePath !== undefined
|
|
31
66
|
? { skillFilePath: skill.skillFilePath }
|
|
32
67
|
: {}),
|
|
68
|
+
...(resolvedMcpServers !== undefined ? { resolvedMcpServers } : {}),
|
|
33
69
|
});
|
|
34
70
|
return { ok: true, handle };
|
|
35
71
|
}
|
|
@@ -159,7 +195,7 @@ export async function openStageAttempt(input) {
|
|
|
159
195
|
...(input.stage.timeout_ms !== undefined
|
|
160
196
|
? { timeoutMs: input.stage.timeout_ms }
|
|
161
197
|
: {}),
|
|
162
|
-
}, input.operatorCatalog);
|
|
198
|
+
}, input.operatorCatalog, input.factoryCwd, attemptArtifactsDir(input.workspaceDir, stageId, attempt));
|
|
163
199
|
if (!opened.ok)
|
|
164
200
|
return opened;
|
|
165
201
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentPort, FeedbackLoopContext, StageHandle, StageRunResult, StageSessionMode } from "../agent/port.js";
|
|
2
2
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { LoadedStageConfig } from "../types/stage.js";
|
|
4
4
|
import type { TaskFile } from "../types/task.js";
|
|
5
5
|
import type { RunStore } from "../runstore/port.js";
|
|
6
6
|
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
@@ -21,7 +21,7 @@ export type RunStageOptions = {
|
|
|
21
21
|
agent: AgentPort;
|
|
22
22
|
store: RunStore;
|
|
23
23
|
runId: string;
|
|
24
|
-
stage:
|
|
24
|
+
stage: LoadedStageConfig;
|
|
25
25
|
task: TaskFile;
|
|
26
26
|
dag?: ResolvedPipelineDag;
|
|
27
27
|
priorEnvelope?: StageEnvelope | null;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ValidationFinding } from "../config/validateCatalog.js";
|
|
2
|
+
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
1
3
|
import type { TaskFile } from "../types/task.js";
|
|
2
4
|
export type StartTaskInput = {
|
|
3
5
|
task?: string | TaskFile;
|
|
@@ -12,4 +14,9 @@ export type ResolvedTaskInput = {
|
|
|
12
14
|
};
|
|
13
15
|
export declare function isTaskFile(value: unknown): value is TaskFile;
|
|
14
16
|
export declare function taskFileToYaml(task: TaskFile): string;
|
|
17
|
+
/** Validate optional `task.input` against entry-stage IR `clone_input_schema` (YAML: `io.input.schema`). */
|
|
18
|
+
export declare function checkTaskEntryInput(task: TaskFile, loaded: LoadedPipeline, options: {
|
|
19
|
+
cwd: string;
|
|
20
|
+
taskPath?: string;
|
|
21
|
+
}): ValidationFinding[];
|
|
15
22
|
export declare function resolveStartTaskInput(input: StartTaskInput, cwd: string): ResolvedTaskInput;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { stringify as stringifyYaml } from "yaml";
|
|
2
3
|
import { parseTaskFile } from "../config/loadTask.js";
|
|
4
|
+
import { relPath } from "../config/validateCatalog.js";
|
|
5
|
+
import { payloadInstanceMismatch } from "../envelope/payloadSchema.js";
|
|
3
6
|
import { resolveTaskPath } from "./pipelineRunner.js";
|
|
4
7
|
export function isTaskFile(value) {
|
|
5
8
|
return parseTaskFile(value, "task").ok;
|
|
@@ -15,8 +18,65 @@ export function taskFileToYaml(task) {
|
|
|
15
18
|
doc.constraints = task.constraints;
|
|
16
19
|
if (task.checkout !== undefined)
|
|
17
20
|
doc.checkout = task.checkout;
|
|
21
|
+
if (task.input !== undefined)
|
|
22
|
+
doc.input = task.input;
|
|
18
23
|
return stringifyYaml(doc);
|
|
19
24
|
}
|
|
25
|
+
/** Validate optional `task.input` against entry-stage IR `clone_input_schema` (YAML: `io.input.schema`). */
|
|
26
|
+
export function checkTaskEntryInput(task, loaded, options) {
|
|
27
|
+
const absPath = options.taskPath
|
|
28
|
+
? path.resolve(options.cwd, options.taskPath)
|
|
29
|
+
: path.resolve(options.cwd, "task.yaml");
|
|
30
|
+
const findingPath = relPath(options.cwd, absPath);
|
|
31
|
+
const stageById = new Map(loaded.stages.map((stage) => [stage.id, stage]));
|
|
32
|
+
const findings = [];
|
|
33
|
+
for (const rootId of loaded.dag.roots) {
|
|
34
|
+
const stage = stageById.get(rootId);
|
|
35
|
+
if (stage?.clone_input_schema === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
if (task.input === undefined) {
|
|
38
|
+
findings.push({
|
|
39
|
+
severity: "warning",
|
|
40
|
+
code: "task.entry_input_unmet",
|
|
41
|
+
path: findingPath,
|
|
42
|
+
message: `Task has no input; entry stage "${rootId}" requires io.input`,
|
|
43
|
+
category: "task",
|
|
44
|
+
pipelineId: loaded.pipeline.id,
|
|
45
|
+
stageId: rootId,
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let details;
|
|
50
|
+
try {
|
|
51
|
+
details = payloadInstanceMismatch(task.input, stage.clone_input_schema);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
findings.push({
|
|
56
|
+
severity: "error",
|
|
57
|
+
code: "task.invalid_shape",
|
|
58
|
+
path: findingPath,
|
|
59
|
+
message: `Task input does not match entry stage "${rootId}" io.input: ${message}`,
|
|
60
|
+
category: "task",
|
|
61
|
+
pipelineId: loaded.pipeline.id,
|
|
62
|
+
stageId: rootId,
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (details !== undefined) {
|
|
67
|
+
findings.push({
|
|
68
|
+
severity: "error",
|
|
69
|
+
code: "task.invalid_shape",
|
|
70
|
+
path: findingPath,
|
|
71
|
+
message: `Task input does not match entry stage "${rootId}" io.input: ${details}`,
|
|
72
|
+
category: "task",
|
|
73
|
+
pipelineId: loaded.pipeline.id,
|
|
74
|
+
stageId: rootId,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return findings;
|
|
79
|
+
}
|
|
20
80
|
export function resolveStartTaskInput(input, cwd) {
|
|
21
81
|
if (typeof input.taskYaml === "string") {
|
|
22
82
|
if (input.taskYaml.trim().length === 0) {
|
|
@@ -3,6 +3,8 @@ import type { RunStore } from "../runstore/port.js";
|
|
|
3
3
|
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
4
4
|
import type { StageConfig } from "../types/stage.js";
|
|
5
5
|
import type { StageRoots } from "./stageRoots.js";
|
|
6
|
+
/** Sidecar written before completion command checks so they can inspect fork_choice. */
|
|
7
|
+
export declare const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
|
|
6
8
|
export type VerifiedStageExecution = {
|
|
7
9
|
/** Establish durable observation before agent work begins. */
|
|
8
10
|
prepare(): Promise<void>;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After-phase VSE. Runs IR `CompletionContract` (YAML: `verify` when after).
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
1
6
|
import { listQaExchanges } from "../hitl/qaTrail.js";
|
|
2
|
-
import { attemptArtifactsDir } from "../runstore/workspaceLayout.js";
|
|
7
|
+
import { attemptArtifactsDir, attemptWorkspaceDir, } from "../runstore/workspaceLayout.js";
|
|
3
8
|
import { COMPLETION_VERIFICATION_FAILURE_PREFIX, } from "../types/completion.js";
|
|
4
9
|
import { runCompletionContract, } from "./completionCheckRunner.js";
|
|
5
10
|
import { readCompletionCheckoutBaseline, writeCompletionCheckoutBaseline, } from "./completionCheckoutBaseline.js";
|
|
6
11
|
import { createGitCheckoutCapability } from "./gitCheckoutCapability.js";
|
|
12
|
+
/** Sidecar written before completion command checks so they can inspect fork_choice. */
|
|
13
|
+
export const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
|
|
7
14
|
export function completionVerificationFailureReason(failedCheckIds) {
|
|
8
15
|
return `${COMPLETION_VERIFICATION_FAILURE_PREFIX} ${failedCheckIds.join(", ")}`;
|
|
9
16
|
}
|
|
@@ -65,11 +72,15 @@ export function createVerifiedStageExecution(options) {
|
|
|
65
72
|
if (contract === undefined)
|
|
66
73
|
return candidate;
|
|
67
74
|
try {
|
|
75
|
+
const artifactsDir = attemptArtifactsDir(roots.runWorkspaceDir, stageId, attempt);
|
|
76
|
+
const attemptDir = attemptWorkspaceDir(roots.runWorkspaceDir, stageId, attempt);
|
|
77
|
+
await mkdir(attemptDir, { recursive: true });
|
|
78
|
+
await writeFile(path.join(attemptDir, COMPLETION_CANDIDATE_ENVELOPE_FILENAME), `${JSON.stringify(candidate.envelope, null, 2)}\n`, "utf8");
|
|
68
79
|
const verification = await runCompletionContract({
|
|
69
80
|
contract,
|
|
70
81
|
envelope: candidate.envelope,
|
|
71
82
|
payloadSchema: stage.payload_schema,
|
|
72
|
-
artifactsDir
|
|
83
|
+
artifactsDir,
|
|
73
84
|
commandWorkingDirectory: roots.cwd,
|
|
74
85
|
gates: gateDecisionsForAttempt({ store, runId, stageId, attempt }),
|
|
75
86
|
checkout,
|
package/dist/server/http.js
CHANGED
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { getCredentialSourceSettings, setCredentialSource, } from "../agent/providerAuth.js";
|
|
5
5
|
import { handleProviderRoutes, providerAuthErrorBody, } from "./providerRoutes.js";
|
|
6
|
+
import { handleProjectMcpRoutes } from "./projectMcpRoutes.js";
|
|
6
7
|
import { createPipeline, parseCreatePipelineBody } from "../config/createPipeline.js";
|
|
7
8
|
import { createStage, parseCreateStageBody } from "../config/createStage.js";
|
|
8
9
|
import { browseCatalog } from "../config/browseCatalog.js";
|
|
9
10
|
import { listExtensions } from "../config/listExtensions.js";
|
|
10
11
|
import { listSkills } from "../config/listSkills.js";
|
|
11
|
-
import { readRunArtifact } from "../mcp/readArtifact.js";
|
|
12
|
+
import { artifactMediaType, readRunArtifact, readRunArtifactBytes, } from "../mcp/readArtifact.js";
|
|
12
13
|
import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
|
|
13
14
|
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
14
15
|
import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount, } from "../runtime/settingsFile.js";
|
|
@@ -96,7 +97,8 @@ function isMutatingApi(method, pathname) {
|
|
|
96
97
|
/^\/api\/providers\/[^/]+\/login$/.test(pathname) ||
|
|
97
98
|
/^\/api\/providers\/[^/]+\/login\/[^/]+\/answer$/.test(pathname) ||
|
|
98
99
|
/^\/api\/providers\/[^/]+\/login\/[^/]+\/cancel$/.test(pathname) ||
|
|
99
|
-
/^\/api\/providers\/[^/]+\/logout$/.test(pathname)
|
|
100
|
+
/^\/api\/providers\/[^/]+\/logout$/.test(pathname) ||
|
|
101
|
+
/^\/api\/project-mcp\/[^/]+\/probe$/.test(pathname));
|
|
100
102
|
}
|
|
101
103
|
function isCredentialMutatingApi(method, pathname) {
|
|
102
104
|
if (method !== "POST")
|
|
@@ -210,8 +212,19 @@ export async function startUiServer(options) {
|
|
|
210
212
|
return true;
|
|
211
213
|
}
|
|
212
214
|
try {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
+
const mediaType = artifactMediaType(artifactPath);
|
|
216
|
+
if (mediaType !== undefined) {
|
|
217
|
+
const bytes = await readRunArtifactBytes(store, runId, artifactPath);
|
|
218
|
+
res.writeHead(200, {
|
|
219
|
+
"Content-Type": mediaType,
|
|
220
|
+
"Content-Length": bytes.length,
|
|
221
|
+
});
|
|
222
|
+
res.end(bytes);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const content = await readRunArtifact(store, runId, artifactPath);
|
|
226
|
+
textPlain(res, 200, content);
|
|
227
|
+
}
|
|
215
228
|
}
|
|
216
229
|
catch (err) {
|
|
217
230
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -527,6 +540,12 @@ export async function startUiServer(options) {
|
|
|
527
540
|
})) {
|
|
528
541
|
return true;
|
|
529
542
|
}
|
|
543
|
+
if (await handleProjectMcpRoutes(req, res, {
|
|
544
|
+
projectRoot: boot.rootDir,
|
|
545
|
+
json,
|
|
546
|
+
})) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
530
549
|
if (method === "GET" && pathname === "/api/skills") {
|
|
531
550
|
json(res, 200, await listSkills({ cwd, agentDir }));
|
|
532
551
|
return true;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export type ProjectMcpRoutesCtx = {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
json: (res: ServerResponse, status: number, body: unknown) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function handleProjectMcpRoutes(req: IncomingMessage, res: ServerResponse, ctx: ProjectMcpRoutesCtx): Promise<boolean>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { probeProjectMcpServer } from "../agent/piIsolatedMcpProbe.js";
|
|
2
|
+
import { listProjectMcpCatalog } from "../config/resolveStageMcpServers.js";
|
|
3
|
+
function requestAbortSignal(req) {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const abort = () => {
|
|
6
|
+
if (!controller.signal.aborted)
|
|
7
|
+
controller.abort();
|
|
8
|
+
};
|
|
9
|
+
if (req.aborted || req.destroyed) {
|
|
10
|
+
abort();
|
|
11
|
+
return controller.signal;
|
|
12
|
+
}
|
|
13
|
+
req.once("aborted", abort);
|
|
14
|
+
req.once("close", abort);
|
|
15
|
+
return controller.signal;
|
|
16
|
+
}
|
|
17
|
+
export async function handleProjectMcpRoutes(req, res, ctx) {
|
|
18
|
+
const { projectRoot, json } = ctx;
|
|
19
|
+
const method = req.method ?? "GET";
|
|
20
|
+
const rawUrl = req.url ?? "/";
|
|
21
|
+
const pathname = new URL(rawUrl, "http://localhost").pathname;
|
|
22
|
+
if (method === "GET" && pathname === "/api/project-mcp") {
|
|
23
|
+
json(res, 200, await listProjectMcpCatalog(projectRoot));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const probeMatch = pathname.match(/^\/api\/project-mcp\/([^/]+)\/probe$/);
|
|
27
|
+
if (method === "POST" && probeMatch) {
|
|
28
|
+
const name = decodeURIComponent(probeMatch[1] ?? "");
|
|
29
|
+
const result = await probeProjectMcpServer({
|
|
30
|
+
projectRoot,
|
|
31
|
+
name,
|
|
32
|
+
signal: requestAbortSignal(req),
|
|
33
|
+
});
|
|
34
|
+
json(res, 200, result);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { StageGateKind } from "./stage.js";
|
|
2
2
|
export declare const COMPLETION_VERIFICATION_FAILURE_PREFIX = "Completion verification failed:";
|
|
3
|
+
/**
|
|
4
|
+
* After-phase check IR (`DAG node.completion`). YAML: `verify` items whose
|
|
5
|
+
* `when` includes `after`. `type: payload_schema` here is a check kind (re-check
|
|
6
|
+
* the success payload after emit), not the old YAML authoring key.
|
|
7
|
+
*/
|
|
3
8
|
export type CompletionCheck = {
|
|
4
9
|
id: string;
|
|
5
10
|
type: "command";
|
|
@@ -31,6 +36,7 @@ export type CompletionContract = {
|
|
|
31
36
|
mode: "all";
|
|
32
37
|
checks: CompletionCheck[];
|
|
33
38
|
};
|
|
39
|
+
/** IR recovery on the DAG node. YAML: `on_verify_fail`. */
|
|
34
40
|
export type RecoveryPolicy = {
|
|
35
41
|
mode: "repair";
|
|
36
42
|
max_attempts: number;
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { CloneAction } from "./forkChoice.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LoadedStageConfig, StageGateKind, StageIoYaml } from "./stage.js";
|
|
3
3
|
import type { CompletionContract, RecoveryPolicy } from "./completion.js";
|
|
4
4
|
export type PipelineConfig = {
|
|
5
5
|
id: string;
|
|
6
6
|
stages: string[];
|
|
7
7
|
/** Selects the AgentPort backend for every stage in this pipeline; overrides the global default. */
|
|
8
8
|
agent?: string;
|
|
9
|
+
/** Default LLM model id for every stage in this pipeline; overrides the global default. */
|
|
10
|
+
model?: string;
|
|
11
|
+
/** Pipeline-file `$ref` root (`#/schemas/<name>`). */
|
|
12
|
+
schemas?: Record<string, unknown>;
|
|
9
13
|
};
|
|
10
14
|
export type PipelineForkConfig = {
|
|
11
15
|
select: "one" | "subset";
|
|
@@ -37,12 +41,20 @@ export type PipelineStageRef = {
|
|
|
37
41
|
};
|
|
38
42
|
clonable?: boolean;
|
|
39
43
|
clone_cap?: number;
|
|
44
|
+
/** IR: after-phase checks. YAML: `verify` items whose `when` includes `after`. */
|
|
40
45
|
completion?: CompletionContract;
|
|
46
|
+
/** IR: after-phase failure policy. YAML: `on_verify_fail`. */
|
|
41
47
|
recovery?: RecoveryPolicy;
|
|
42
48
|
feedback_loop?: FeedbackLoopConfig;
|
|
43
49
|
/** Omitted means this stage is safe to include in a feedback replay. */
|
|
44
50
|
replay_safe?: boolean;
|
|
45
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Pipeline YAML stage entry (both dialects). Target authoring is `io` / `verify`
|
|
54
|
+
* / `on_verify_fail`. The payload_schema / pre_emit_checks / completion /
|
|
55
|
+
* recovery keys are the legacy YAML dialect (and also the IR names after compile).
|
|
56
|
+
* New catalog fields: add target YAML keys here and compile them in yamlDialect.ts.
|
|
57
|
+
*/
|
|
46
58
|
export type PipelineStageYamlEntry = PipelineStageRef & {
|
|
47
59
|
uses?: string;
|
|
48
60
|
system_prompt?: string;
|
|
@@ -52,6 +64,11 @@ export type PipelineStageYamlEntry = PipelineStageRef & {
|
|
|
52
64
|
clone_input_schema?: unknown;
|
|
53
65
|
clone_actions?: CloneAction[];
|
|
54
66
|
skill?: string;
|
|
67
|
+
mcp?: string[];
|
|
68
|
+
io?: StageIoYaml;
|
|
69
|
+
verify?: unknown;
|
|
70
|
+
on_verify_fail?: RecoveryPolicy;
|
|
71
|
+
pre_emit_checks?: unknown;
|
|
55
72
|
};
|
|
56
73
|
export type PipelineIncludeEntry = {
|
|
57
74
|
local: string;
|
|
@@ -69,11 +86,13 @@ export type NormalizedPipelineStageEntry = {
|
|
|
69
86
|
};
|
|
70
87
|
clonable?: boolean;
|
|
71
88
|
clone_cap?: number;
|
|
89
|
+
/** IR after compile. YAML `verify` after-phase / `on_verify_fail`. */
|
|
72
90
|
completion?: CompletionContract;
|
|
73
91
|
recovery?: RecoveryPolicy;
|
|
74
92
|
feedback_loop?: FeedbackLoopConfig;
|
|
75
93
|
replay_safe?: boolean;
|
|
76
94
|
skill?: string;
|
|
95
|
+
mcp?: string[];
|
|
77
96
|
body: {
|
|
78
97
|
kind: "inline";
|
|
79
98
|
raw: Record<string, unknown>;
|
|
@@ -100,7 +119,9 @@ export type ResolvedPipelineStageNode = {
|
|
|
100
119
|
clonable?: boolean;
|
|
101
120
|
clone_cap?: number;
|
|
102
121
|
definition_id?: string;
|
|
122
|
+
/** Persisted on pipeline_dag. YAML `verify` after-phase. */
|
|
103
123
|
completion?: CompletionContract;
|
|
124
|
+
/** Persisted on pipeline_dag. YAML `on_verify_fail`. */
|
|
104
125
|
recovery?: RecoveryPolicy;
|
|
105
126
|
feedback_loop?: FeedbackLoopConfig;
|
|
106
127
|
replay_safe?: boolean;
|
|
@@ -112,7 +133,7 @@ export type ResolvedPipelineDag = {
|
|
|
112
133
|
};
|
|
113
134
|
export type LoadedPipeline = {
|
|
114
135
|
pipeline: PipelineConfig;
|
|
115
|
-
stages:
|
|
136
|
+
stages: LoadedStageConfig[];
|
|
116
137
|
dag: ResolvedPipelineDag;
|
|
117
138
|
pipelinePath: string;
|
|
118
139
|
stageSources?: Record<string, PipelineStageSource>;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { StageGateKind } from "./stage.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* (`src/types/completion.ts`)
|
|
6
|
-
*
|
|
7
|
-
* `PreEmitCheck` runs synchronously inside the agent's own `emit_stage_envelope`
|
|
8
|
-
* tool call, driven by the stage-body `pre_emit_checks` field.
|
|
3
|
+
* Emit-phase check IR (`StageConfig.pre_emit_checks`). YAML: `verify` items
|
|
4
|
+
* whose `when` includes `emit`. Distinct from after-phase `CompletionCheck`
|
|
5
|
+
* (`src/types/completion.ts`), which runs after a candidate envelope is captured.
|
|
6
|
+
* This runs inside `emit_stage_envelope` before success is accepted.
|
|
9
7
|
*/
|
|
10
8
|
export type PreEmitCheck = {
|
|
11
9
|
id: string;
|
package/dist/types/stage.d.ts
CHANGED
|
@@ -2,24 +2,44 @@ import type { PreEmitCheck } from "./preEmitCheck.js";
|
|
|
2
2
|
import type { CloneAction } from "./forkChoice.js";
|
|
3
3
|
export declare const STAGE_GATE_KINDS: readonly ["free_text", "confirm", "multi_question", "artifact_backed"];
|
|
4
4
|
export type StageGateKind = (typeof STAGE_GATE_KINDS)[number];
|
|
5
|
+
/** Target YAML `io:` block. Compiles onto StageConfig.payload_schema / clone_input_schema. */
|
|
6
|
+
export type StageIoYaml = {
|
|
7
|
+
input?: {
|
|
8
|
+
schema?: unknown;
|
|
9
|
+
};
|
|
10
|
+
output?: {
|
|
11
|
+
schema?: unknown;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Loaded stage IR. Field names are the runtime contract (emit, VSE, snapshots),
|
|
16
|
+
* not the catalog YAML spelling. Target YAML `io` / `verify` compile onto these
|
|
17
|
+
* in `src/config/yamlDialect.ts`. Do not add new catalog keys here — add them on
|
|
18
|
+
* the YAML dialect and map them in compileTargetContract.
|
|
19
|
+
*/
|
|
5
20
|
export type StageConfig = {
|
|
6
21
|
id: string;
|
|
7
22
|
system_prompt: string;
|
|
8
|
-
model
|
|
9
|
-
/**
|
|
23
|
+
model?: string;
|
|
24
|
+
/** IR: success envelope.payload schema. YAML: `io.output.schema`. */
|
|
10
25
|
payload_schema?: unknown;
|
|
11
26
|
/** Declared ask_operator kinds this stage is expected to stop on. */
|
|
12
27
|
gate_kinds?: StageGateKind[];
|
|
13
|
-
/**
|
|
28
|
+
/** IR: emit-phase checks. YAML: `verify` items whose `when` includes `emit`. */
|
|
14
29
|
pre_emit_checks?: PreEmitCheck[];
|
|
30
|
+
/** IR: clone/fan-out assignment schema. YAML: `io.input.schema`. */
|
|
15
31
|
clone_input_schema?: unknown;
|
|
16
32
|
clone_actions?: CloneAction[];
|
|
17
|
-
/** Optional stage wall-clock timeout in milliseconds (default
|
|
33
|
+
/** Optional stage wall-clock timeout in milliseconds (default 60 minutes). */
|
|
18
34
|
timeout_ms?: number;
|
|
19
35
|
skill?: string;
|
|
36
|
+
mcp?: string[];
|
|
20
37
|
/**
|
|
21
38
|
* Selects the AgentPort backend for this stage, overriding pipeline/global.
|
|
22
39
|
* Parsed but not yet consulted — see STAGE_LEVEL_AGENT_OVERRIDE_ENABLED.
|
|
23
40
|
*/
|
|
24
41
|
agent?: string;
|
|
25
42
|
};
|
|
43
|
+
export type LoadedStageConfig = StageConfig & {
|
|
44
|
+
model: string;
|
|
45
|
+
};
|
|
@@ -13,6 +13,8 @@ export type StageflowManifest = {
|
|
|
13
13
|
catalog: StageflowManifestCatalog;
|
|
14
14
|
/** Default AgentPort backend for every pipeline/stage that doesn't override it. */
|
|
15
15
|
agent?: string;
|
|
16
|
+
/** Default LLM model id for every stage that doesn't override it at pipeline or stage level. */
|
|
17
|
+
model?: string;
|
|
16
18
|
};
|
|
17
19
|
export type LoadedManifest = {
|
|
18
20
|
path: string;
|