stageflow 0.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/agent/activity.d.ts +76 -0
- package/dist/agent/activity.js +89 -0
- package/dist/agent/activityObserver.d.ts +17 -0
- package/dist/agent/activityObserver.js +66 -0
- package/dist/agent/cursorExtension.d.ts +12 -0
- package/dist/agent/cursorExtension.js +88 -0
- package/dist/agent/cursorProvider.d.ts +14 -0
- package/dist/agent/cursorProvider.js +139 -0
- package/dist/agent/fakeAgent.d.ts +29 -0
- package/dist/agent/fakeAgent.js +295 -0
- package/dist/agent/oauthSessionManager.d.ts +33 -0
- package/dist/agent/oauthSessionManager.js +214 -0
- package/dist/agent/piAdapter.d.ts +128 -0
- package/dist/agent/piAdapter.js +967 -0
- package/dist/agent/port.d.ts +63 -0
- package/dist/agent/port.js +46 -0
- package/dist/agent/providerAuth.d.ts +101 -0
- package/dist/agent/providerAuth.js +282 -0
- package/dist/agent/providerSupport.d.ts +28 -0
- package/dist/agent/providerSupport.js +19 -0
- package/dist/cli/providersCommand.d.ts +11 -0
- package/dist/cli/providersCommand.js +275 -0
- package/dist/cli/terminalAuthInteraction.d.ts +8 -0
- package/dist/cli/terminalAuthInteraction.js +65 -0
- package/dist/cli/terminalSecret.d.ts +2 -0
- package/dist/cli/terminalSecret.js +67 -0
- package/dist/cli/validateCommand.d.ts +11 -0
- package/dist/cli/validateCommand.js +81 -0
- package/dist/cli/validateOutput.d.ts +8 -0
- package/dist/cli/validateOutput.js +75 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +273 -0
- package/dist/config/createPipeline.d.ts +31 -0
- package/dist/config/createPipeline.js +255 -0
- package/dist/config/createStage.d.ts +25 -0
- package/dist/config/createStage.js +181 -0
- package/dist/config/listConfig.d.ts +33 -0
- package/dist/config/listConfig.js +195 -0
- package/dist/config/listExtensions.d.ts +26 -0
- package/dist/config/listExtensions.js +50 -0
- package/dist/config/listSkills.d.ts +29 -0
- package/dist/config/listSkills.js +55 -0
- package/dist/config/loadOutcome.d.ts +27 -0
- package/dist/config/loadOutcome.js +6 -0
- package/dist/config/loadPipeline.d.ts +25 -0
- package/dist/config/loadPipeline.js +196 -0
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +127 -0
- package/dist/config/loadTask.d.ts +7 -0
- package/dist/config/loadTask.js +69 -0
- package/dist/config/readYamlObject.d.ts +1 -0
- package/dist/config/readYamlObject.js +5 -0
- package/dist/config/resolvePipelineDag.d.ts +12 -0
- package/dist/config/resolvePipelineDag.js +282 -0
- package/dist/config/validateCatalog.d.ts +36 -0
- package/dist/config/validateCatalog.js +322 -0
- package/dist/envelope/check.d.ts +3 -0
- package/dist/envelope/check.js +52 -0
- package/dist/envelope/payloadSchema.d.ts +10 -0
- package/dist/envelope/payloadSchema.js +91 -0
- package/dist/hitl/qaTrail.d.ts +31 -0
- package/dist/hitl/qaTrail.js +65 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +7 -0
- package/dist/mcp/projectRun.d.ts +37 -0
- package/dist/mcp/projectRun.js +57 -0
- package/dist/mcp/readArtifact.d.ts +2 -0
- package/dist/mcp/readArtifact.js +46 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +26 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +96 -0
- package/dist/package-meta.d.ts +1 -0
- package/dist/package-meta.js +1 -0
- package/dist/prompt/priorEnvelope.d.ts +2 -0
- package/dist/prompt/priorEnvelope.js +5 -0
- package/dist/runstore/catalog.d.ts +8 -0
- package/dist/runstore/catalog.js +16 -0
- package/dist/runstore/createStore.d.ts +15 -0
- package/dist/runstore/createStore.js +23 -0
- package/dist/runstore/disk/DiskRunStore.d.ts +30 -0
- package/dist/runstore/disk/DiskRunStore.js +238 -0
- package/dist/runstore/disk/catalogHelpers.d.ts +11 -0
- package/dist/runstore/disk/catalogHelpers.js +102 -0
- package/dist/runstore/layout.d.ts +22 -0
- package/dist/runstore/layout.js +58 -0
- package/dist/runstore/paths.d.ts +7 -0
- package/dist/runstore/paths.js +60 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +5 -0
- package/dist/runstore/pipelineDagSnapshot.js +43 -0
- package/dist/runstore/port.d.ts +146 -0
- package/dist/runstore/port.js +28 -0
- package/dist/runstore/runProjection.d.ts +6 -0
- package/dist/runstore/runProjection.js +102 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +48 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +460 -0
- package/dist/runstore/sqlite/migrateFromDisk.d.ts +5 -0
- package/dist/runstore/sqlite/migrateFromDisk.js +108 -0
- package/dist/runstore/sqlite/schema.d.ts +1 -0
- package/dist/runstore/sqlite/schema.js +59 -0
- package/dist/runstore/stageExecution.d.ts +13 -0
- package/dist/runstore/stageExecution.js +22 -0
- package/dist/runstore/stageSnapshot.d.ts +2 -0
- package/dist/runstore/stageSnapshot.js +46 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +2 -0
- package/dist/runstore/syntheticStageSnapshot.js +10 -0
- package/dist/runstore/trackProjection.d.ts +7 -0
- package/dist/runstore/trackProjection.js +94 -0
- package/dist/runstore/workspaceLayout.d.ts +31 -0
- package/dist/runstore/workspaceLayout.js +172 -0
- package/dist/runtime/answerResume.d.ts +16 -0
- package/dist/runtime/answerResume.js +22 -0
- package/dist/runtime/credentialBinding.d.ts +16 -0
- package/dist/runtime/credentialBinding.js +80 -0
- package/dist/runtime/dagTraversal.d.ts +3 -0
- package/dist/runtime/dagTraversal.js +26 -0
- package/dist/runtime/envelopeRouting.d.ts +20 -0
- package/dist/runtime/envelopeRouting.js +59 -0
- package/dist/runtime/hitlSeams.d.ts +38 -0
- package/dist/runtime/hitlSeams.js +2 -0
- package/dist/runtime/pipelineRunner.d.ts +77 -0
- package/dist/runtime/pipelineRunner.js +141 -0
- package/dist/runtime/pipelineScheduler.d.ts +90 -0
- package/dist/runtime/pipelineScheduler.js +494 -0
- package/dist/runtime/pipelineValidationError.d.ts +6 -0
- package/dist/runtime/pipelineValidationError.js +8 -0
- package/dist/runtime/resumeReconstruct.d.ts +33 -0
- package/dist/runtime/resumeReconstruct.js +144 -0
- package/dist/runtime/runManager.d.ts +129 -0
- package/dist/runtime/runManager.js +818 -0
- package/dist/runtime/runRetryCoordinator.d.ts +85 -0
- package/dist/runtime/runRetryCoordinator.js +372 -0
- package/dist/runtime/settingsFile.d.ts +17 -0
- package/dist/runtime/settingsFile.js +88 -0
- package/dist/runtime/stageAttemptBootstrap.d.ts +40 -0
- package/dist/runtime/stageAttemptBootstrap.js +82 -0
- package/dist/runtime/stageAttemptContext.d.ts +11 -0
- package/dist/runtime/stageAttemptContext.js +26 -0
- package/dist/runtime/stageConcurrency.d.ts +10 -0
- package/dist/runtime/stageConcurrency.js +51 -0
- package/dist/runtime/stageHitl.d.ts +95 -0
- package/dist/runtime/stageHitl.js +198 -0
- package/dist/runtime/stageProcessLauncher.d.ts +45 -0
- package/dist/runtime/stageProcessLauncher.js +199 -0
- package/dist/runtime/stageRecovery.d.ts +10 -0
- package/dist/runtime/stageRecovery.js +43 -0
- package/dist/runtime/stageRoots.d.ts +22 -0
- package/dist/runtime/stageRoots.js +93 -0
- package/dist/runtime/stageRunner.d.ts +59 -0
- package/dist/runtime/stageRunner.js +221 -0
- package/dist/runtime/stageWorker.d.ts +6 -0
- package/dist/runtime/stageWorker.js +109 -0
- package/dist/runtime/stageWorkerProtocol.d.ts +27 -0
- package/dist/runtime/stageWorkerProtocol.js +16 -0
- package/dist/runtime/taskInput.d.ts +15 -0
- package/dist/runtime/taskInput.js +38 -0
- package/dist/server/http.d.ts +31 -0
- package/dist/server/http.js +561 -0
- package/dist/server/providerRoutes.d.ts +15 -0
- package/dist/server/providerRoutes.js +186 -0
- package/dist/tools/askOperator.d.ts +185 -0
- package/dist/tools/askOperator.js +409 -0
- package/dist/tools/emitStageEnvelope.d.ts +42 -0
- package/dist/tools/emitStageEnvelope.js +61 -0
- package/dist/tools/writeStageArtifact.d.ts +26 -0
- package/dist/tools/writeStageArtifact.js +103 -0
- package/dist/types/envelope.d.ts +12 -0
- package/dist/types/envelope.js +6 -0
- package/dist/types/pipeline.d.ts +25 -0
- package/dist/types/pipeline.js +1 -0
- package/dist/types/stage.d.ts +12 -0
- package/dist/types/stage.js +6 -0
- package/dist/types/task.d.ts +7 -0
- package/dist/types/task.js +1 -0
- package/dist/ui/assets/index-Cry0Tpfx.js +118 -0
- package/dist/ui/assets/index-DCsDopak.css +1 -0
- package/dist/ui/index.html +21 -0
- package/package.json +72 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TSchema } from "typebox/type";
|
|
2
|
+
import { type StageEnvelope } from "../types/envelope.js";
|
|
3
|
+
export type CompiledPayloadSchema = TSchema;
|
|
4
|
+
/**
|
|
5
|
+
* Compile a JSON Schema subset used for stage payload_schema.
|
|
6
|
+
* Supported: type object/string/number/integer/boolean/array,
|
|
7
|
+
* properties, required, items, additionalProperties (boolean).
|
|
8
|
+
*/
|
|
9
|
+
export declare function compilePayloadSchema(raw: unknown): CompiledPayloadSchema;
|
|
10
|
+
export declare function assertEnvelopePayload(envelope: StageEnvelope, payloadSchema: unknown | undefined): void;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
import { EnvelopeError } from "../types/envelope.js";
|
|
4
|
+
function compileNode(node, path) {
|
|
5
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
6
|
+
throw new Error(`${path}: schema must be an object`);
|
|
7
|
+
}
|
|
8
|
+
const schema = node;
|
|
9
|
+
if (typeof schema.type !== "string") {
|
|
10
|
+
throw new Error(`${path}: type is required`);
|
|
11
|
+
}
|
|
12
|
+
switch (schema.type) {
|
|
13
|
+
case "string":
|
|
14
|
+
return Type.String();
|
|
15
|
+
case "number":
|
|
16
|
+
return Type.Number();
|
|
17
|
+
case "integer":
|
|
18
|
+
return Type.Integer();
|
|
19
|
+
case "boolean":
|
|
20
|
+
return Type.Boolean();
|
|
21
|
+
case "array": {
|
|
22
|
+
if (schema.items === undefined) {
|
|
23
|
+
throw new Error(`${path}: array requires items`);
|
|
24
|
+
}
|
|
25
|
+
return Type.Array(compileNode(schema.items, `${path}.items`));
|
|
26
|
+
}
|
|
27
|
+
case "object": {
|
|
28
|
+
const properties = schema.properties ?? {};
|
|
29
|
+
if (properties === null ||
|
|
30
|
+
typeof properties !== "object" ||
|
|
31
|
+
Array.isArray(properties)) {
|
|
32
|
+
throw new Error(`${path}: properties must be an object when present`);
|
|
33
|
+
}
|
|
34
|
+
const requiredList = schema.required ?? [];
|
|
35
|
+
if (!Array.isArray(requiredList) ||
|
|
36
|
+
!requiredList.every((k) => typeof k === "string")) {
|
|
37
|
+
throw new Error(`${path}: required must be an array of strings when present`);
|
|
38
|
+
}
|
|
39
|
+
const required = new Set(requiredList);
|
|
40
|
+
if (schema.additionalProperties !== undefined &&
|
|
41
|
+
typeof schema.additionalProperties !== "boolean") {
|
|
42
|
+
throw new Error(`${path}: additionalProperties must be a boolean when present`);
|
|
43
|
+
}
|
|
44
|
+
const compiledProps = {};
|
|
45
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
46
|
+
const propSchema = compileNode(value, `${path}.properties.${key}`);
|
|
47
|
+
compiledProps[key] = required.has(key)
|
|
48
|
+
? propSchema
|
|
49
|
+
: Type.Optional(propSchema);
|
|
50
|
+
}
|
|
51
|
+
const options = schema.additionalProperties === false
|
|
52
|
+
? { additionalProperties: false }
|
|
53
|
+
: undefined;
|
|
54
|
+
return Type.Object(compiledProps, options);
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
throw new Error(`${path}: unsupported type "${schema.type}" (supported: object, string, number, integer, boolean, array)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compile a JSON Schema subset used for stage payload_schema.
|
|
62
|
+
* Supported: type object/string/number/integer/boolean/array,
|
|
63
|
+
* properties, required, items, additionalProperties (boolean).
|
|
64
|
+
*/
|
|
65
|
+
export function compilePayloadSchema(raw) {
|
|
66
|
+
const compiled = compileNode(raw, "payload_schema");
|
|
67
|
+
if (!Type.IsObject(compiled)) {
|
|
68
|
+
throw new Error("payload_schema: root type must be object");
|
|
69
|
+
}
|
|
70
|
+
return compiled;
|
|
71
|
+
}
|
|
72
|
+
export function assertEnvelopePayload(envelope, payloadSchema) {
|
|
73
|
+
if (payloadSchema === undefined) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (envelope.status !== "success") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const schema = compilePayloadSchema(payloadSchema);
|
|
80
|
+
if (envelope.payload === undefined) {
|
|
81
|
+
throw new EnvelopeError("payload is required by stage payload_schema");
|
|
82
|
+
}
|
|
83
|
+
if (!Value.Check(schema, envelope.payload)) {
|
|
84
|
+
const details = [...Value.Errors(schema, envelope.payload)]
|
|
85
|
+
.map((err) => `${err.instancePath || "/"} ${err.message}`)
|
|
86
|
+
.join("; ");
|
|
87
|
+
throw new EnvelopeError(details
|
|
88
|
+
? `payload does not match payload_schema: ${details}`
|
|
89
|
+
: "payload does not match payload_schema");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Q&A trail — public entry for operator prompt/answer activity events.
|
|
3
|
+
*
|
|
4
|
+
* Event shapes live on StageActivityEvent; this module re-exports them,
|
|
5
|
+
* provides narrow guards, append helpers, and pure pending/history derivation.
|
|
6
|
+
*/
|
|
7
|
+
import type { OperatorAnswerActivityEvent, OperatorPromptActivityEvent, StageActivityEvent, StageLogLine } from "../agent/activity.js";
|
|
8
|
+
import type { RunStore, StageLogEvent } from "../runstore/port.js";
|
|
9
|
+
import type { AskOperatorAnswer, AskOperatorPrompt } from "../tools/askOperator.js";
|
|
10
|
+
export type { OperatorAnswerActivityEvent, OperatorPromptActivityEvent, } from "../agent/activity.js";
|
|
11
|
+
export type { AskOperatorAnswer, AskOperatorPrompt } from "../tools/askOperator.js";
|
|
12
|
+
export type QaExchange = {
|
|
13
|
+
prompt: AskOperatorPrompt;
|
|
14
|
+
answer: AskOperatorAnswer | null;
|
|
15
|
+
};
|
|
16
|
+
export declare class QaTrailInvariantError extends Error {
|
|
17
|
+
readonly code: "R8_MULTIPLE_PENDING_PROMPTS";
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function isOperatorPromptEvent(event: StageLogLine | StageActivityEvent): event is OperatorPromptActivityEvent;
|
|
21
|
+
export declare function isOperatorAnswerEvent(event: StageLogLine | StageActivityEvent): event is OperatorAnswerActivityEvent;
|
|
22
|
+
export declare function operatorPromptEvent(prompt: AskOperatorPrompt): OperatorPromptActivityEvent;
|
|
23
|
+
export declare function operatorAnswerEvent(answer: AskOperatorAnswer): OperatorAnswerActivityEvent;
|
|
24
|
+
export declare function appendOperatorPrompt(store: RunStore, runId: string, stageId: string, prompt: AskOperatorPrompt, options?: {
|
|
25
|
+
attempt?: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function appendOperatorAnswer(store: RunStore, runId: string, stageId: string, answer: AskOperatorAnswer, options?: {
|
|
28
|
+
attempt?: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
export declare function listQaExchanges(events: readonly StageLogEvent[]): QaExchange[];
|
|
31
|
+
export declare function derivePendingPrompt(events: readonly StageLogEvent[]): AskOperatorPrompt | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Q&A trail — public entry for operator prompt/answer activity events.
|
|
3
|
+
*
|
|
4
|
+
* Event shapes live on StageActivityEvent; this module re-exports them,
|
|
5
|
+
* provides narrow guards, append helpers, and pure pending/history derivation.
|
|
6
|
+
*/
|
|
7
|
+
export class QaTrailInvariantError extends Error {
|
|
8
|
+
code = "R8_MULTIPLE_PENDING_PROMPTS";
|
|
9
|
+
constructor(message = "Q&A trail has more than one pending operator prompt (R8)") {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "QaTrailInvariantError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function isOperatorPromptEvent(event) {
|
|
15
|
+
return event.event === "operator_prompt";
|
|
16
|
+
}
|
|
17
|
+
export function isOperatorAnswerEvent(event) {
|
|
18
|
+
return event.event === "operator_answer";
|
|
19
|
+
}
|
|
20
|
+
export function operatorPromptEvent(prompt) {
|
|
21
|
+
return { event: "operator_prompt", prompt };
|
|
22
|
+
}
|
|
23
|
+
export function operatorAnswerEvent(answer) {
|
|
24
|
+
return {
|
|
25
|
+
event: "operator_answer",
|
|
26
|
+
promptId: answer.promptId,
|
|
27
|
+
answer,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function appendOperatorPrompt(store, runId, stageId, prompt, options) {
|
|
31
|
+
await store.appendStageEvent(runId, stageId, operatorPromptEvent(prompt), options);
|
|
32
|
+
}
|
|
33
|
+
export async function appendOperatorAnswer(store, runId, stageId, answer, options) {
|
|
34
|
+
await store.appendStageEvent(runId, stageId, operatorAnswerEvent(answer), options);
|
|
35
|
+
}
|
|
36
|
+
function collectQaExchanges(events) {
|
|
37
|
+
const exchanges = [];
|
|
38
|
+
for (const event of events) {
|
|
39
|
+
if (isOperatorPromptEvent(event)) {
|
|
40
|
+
exchanges.push({ prompt: event.prompt, answer: null });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (isOperatorAnswerEvent(event)) {
|
|
44
|
+
const open = exchanges.find((ex) => ex.answer === null && ex.prompt.id === event.promptId);
|
|
45
|
+
if (open)
|
|
46
|
+
open.answer = event.answer;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return exchanges;
|
|
50
|
+
}
|
|
51
|
+
function assertAtMostOnePending(exchanges) {
|
|
52
|
+
const pendingCount = exchanges.reduce((n, ex) => n + (ex.answer === null ? 1 : 0), 0);
|
|
53
|
+
if (pendingCount > 1) {
|
|
54
|
+
throw new QaTrailInvariantError();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function listQaExchanges(events) {
|
|
58
|
+
const exchanges = collectQaExchanges(events);
|
|
59
|
+
assertAtMostOnePending(exchanges);
|
|
60
|
+
return exchanges;
|
|
61
|
+
}
|
|
62
|
+
export function derivePendingPrompt(events) {
|
|
63
|
+
const pending = listQaExchanges(events).find((ex) => ex.answer === null);
|
|
64
|
+
return pending?.prompt ?? null;
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { PACKAGE_NAME } from "./package-meta.js";
|
|
2
|
+
export type { StageEnvelope } from "./types/envelope.js";
|
|
3
|
+
export { assertRequiredEnvelope, isAdvancingEnvelope, } from "./envelope/check.js";
|
|
4
|
+
export type { AgentPort } from "./agent/port.js";
|
|
5
|
+
export type { StageActivityEvent, StageLogLine } from "./agent/activity.js";
|
|
6
|
+
export type { StageRoots } from "./runtime/stageRoots.js";
|
|
7
|
+
export { runPipeline, startPipeline } from "./runtime/pipelineRunner.js";
|
|
8
|
+
export { createRunStore } from "./runstore/createStore.js";
|
|
9
|
+
export type { RunStoreConfig, RunStoreKind } from "./runstore/createStore.js";
|
|
10
|
+
export type { RunStore, RunSummary, RunDetail, RunMeta, RunStatus, StageSnapshot, StageLogEvent, CreatedRun, } from "./runstore/port.js";
|
|
11
|
+
export { deriveStatusFromStages } from "./runstore/port.js";
|
|
12
|
+
export { RunManager } from "./runtime/runManager.js";
|
|
13
|
+
export { startUiServer } from "./server/http.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { PACKAGE_NAME } from "./package-meta.js";
|
|
2
|
+
export { assertRequiredEnvelope, isAdvancingEnvelope, } from "./envelope/check.js";
|
|
3
|
+
export { runPipeline, startPipeline } from "./runtime/pipelineRunner.js";
|
|
4
|
+
export { createRunStore } from "./runstore/createStore.js";
|
|
5
|
+
export { deriveStatusFromStages } from "./runstore/port.js";
|
|
6
|
+
export { RunManager } from "./runtime/runManager.js";
|
|
7
|
+
export { startUiServer } from "./server/http.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RunDetail, StageSnapshot } from "../runstore/port.js";
|
|
2
|
+
import type { PipelineTrackProjection, RunSummary } from "../runstore/port.js";
|
|
3
|
+
import type { AskOperatorPrompt } from "../tools/askOperator.js";
|
|
4
|
+
import type { StageEnvelope } from "../types/envelope.js";
|
|
5
|
+
export type McpStageProjection = {
|
|
6
|
+
stage_id: string;
|
|
7
|
+
status: StageSnapshot["status"];
|
|
8
|
+
envelope: {
|
|
9
|
+
status: StageEnvelope["status"];
|
|
10
|
+
summary: string;
|
|
11
|
+
artifacts: string[];
|
|
12
|
+
payload?: Record<string, unknown>;
|
|
13
|
+
} | null;
|
|
14
|
+
artifacts: string[];
|
|
15
|
+
last_at?: string;
|
|
16
|
+
pending_prompt?: AskOperatorPrompt;
|
|
17
|
+
};
|
|
18
|
+
export type McpRunProjection = {
|
|
19
|
+
run_id: string;
|
|
20
|
+
pipeline_id: string;
|
|
21
|
+
task_id?: string;
|
|
22
|
+
status: RunDetail["status"];
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at?: string;
|
|
25
|
+
stages: McpStageProjection[];
|
|
26
|
+
pipeline_track: PipelineTrackProjection;
|
|
27
|
+
waiting_stage_id?: RunSummary["waiting_stage_id"];
|
|
28
|
+
waiting_stage_ids?: RunSummary["waiting_stage_ids"];
|
|
29
|
+
waiting_summary?: RunSummary["waiting_summary"];
|
|
30
|
+
waiting_kind?: RunSummary["waiting_kind"];
|
|
31
|
+
waiting_prompt_id?: RunSummary["waiting_prompt_id"];
|
|
32
|
+
waiting_artifacts?: RunSummary["waiting_artifacts"];
|
|
33
|
+
waiting_questions?: RunSummary["waiting_questions"];
|
|
34
|
+
failed_stage_id?: RunSummary["failed_stage_id"];
|
|
35
|
+
failed_reason?: RunSummary["failed_reason"];
|
|
36
|
+
};
|
|
37
|
+
export declare function projectRunForMcp(detail: RunDetail): McpRunProjection;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function projectRunForMcp(detail) {
|
|
2
|
+
return {
|
|
3
|
+
run_id: detail.run_id,
|
|
4
|
+
pipeline_id: detail.pipeline_id,
|
|
5
|
+
task_id: detail.task_id,
|
|
6
|
+
status: detail.status,
|
|
7
|
+
created_at: detail.created_at,
|
|
8
|
+
updated_at: detail.updated_at,
|
|
9
|
+
pipeline_track: detail.pipeline_track,
|
|
10
|
+
...(detail.waiting_stage_id !== undefined
|
|
11
|
+
? { waiting_stage_id: detail.waiting_stage_id }
|
|
12
|
+
: {}),
|
|
13
|
+
...(detail.waiting_stage_ids !== undefined
|
|
14
|
+
? { waiting_stage_ids: detail.waiting_stage_ids }
|
|
15
|
+
: {}),
|
|
16
|
+
...(detail.waiting_summary !== undefined
|
|
17
|
+
? { waiting_summary: detail.waiting_summary }
|
|
18
|
+
: {}),
|
|
19
|
+
...(detail.waiting_kind !== undefined
|
|
20
|
+
? { waiting_kind: detail.waiting_kind }
|
|
21
|
+
: {}),
|
|
22
|
+
...(detail.waiting_prompt_id !== undefined
|
|
23
|
+
? { waiting_prompt_id: detail.waiting_prompt_id }
|
|
24
|
+
: {}),
|
|
25
|
+
...(detail.waiting_artifacts !== undefined
|
|
26
|
+
? { waiting_artifacts: detail.waiting_artifacts }
|
|
27
|
+
: {}),
|
|
28
|
+
...(detail.waiting_questions !== undefined
|
|
29
|
+
? { waiting_questions: detail.waiting_questions }
|
|
30
|
+
: {}),
|
|
31
|
+
...(detail.failed_stage_id !== undefined
|
|
32
|
+
? { failed_stage_id: detail.failed_stage_id }
|
|
33
|
+
: {}),
|
|
34
|
+
...(detail.failed_reason !== undefined
|
|
35
|
+
? { failed_reason: detail.failed_reason }
|
|
36
|
+
: {}),
|
|
37
|
+
stages: detail.stages.map((stage) => ({
|
|
38
|
+
stage_id: stage.stage_id,
|
|
39
|
+
status: stage.status,
|
|
40
|
+
envelope: stage.envelope
|
|
41
|
+
? {
|
|
42
|
+
status: stage.envelope.status,
|
|
43
|
+
summary: stage.envelope.summary,
|
|
44
|
+
artifacts: stage.envelope.artifacts,
|
|
45
|
+
...(stage.envelope.payload !== undefined
|
|
46
|
+
? { payload: stage.envelope.payload }
|
|
47
|
+
: {}),
|
|
48
|
+
}
|
|
49
|
+
: null,
|
|
50
|
+
artifacts: stage.artifacts,
|
|
51
|
+
last_at: stage.last_at,
|
|
52
|
+
...(stage.pending_prompt
|
|
53
|
+
? { pending_prompt: stage.pending_prompt }
|
|
54
|
+
: {}),
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isInsideDir } from "../runstore/workspaceLayout.js";
|
|
4
|
+
function assertSafeRunId(runId) {
|
|
5
|
+
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
6
|
+
throw new Error("runId must be a non-empty string");
|
|
7
|
+
}
|
|
8
|
+
if (runId.includes("..") ||
|
|
9
|
+
runId.includes("/") ||
|
|
10
|
+
runId.includes("\\") ||
|
|
11
|
+
path.isAbsolute(runId)) {
|
|
12
|
+
throw new Error("runId must not contain path separators or ..");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function readRunArtifact(store, runId, relativePath) {
|
|
16
|
+
assertSafeRunId(runId);
|
|
17
|
+
if (typeof relativePath !== "string" || relativePath.trim().length === 0) {
|
|
18
|
+
throw new Error("path must be a non-empty string");
|
|
19
|
+
}
|
|
20
|
+
if (path.isAbsolute(relativePath)) {
|
|
21
|
+
throw new Error("path must be relative to the run workspace");
|
|
22
|
+
}
|
|
23
|
+
const segments = relativePath.split(/[/\\]/);
|
|
24
|
+
if (segments.some((segment) => segment === "..")) {
|
|
25
|
+
throw new Error("path must not contain .. segments");
|
|
26
|
+
}
|
|
27
|
+
if (segments.some((segment) => segment === ".pi-agent") ||
|
|
28
|
+
path.basename(relativePath) === "auth.json") {
|
|
29
|
+
throw new Error("Artifact path denied");
|
|
30
|
+
}
|
|
31
|
+
await store.readRunMeta(runId);
|
|
32
|
+
const workspaceDir = store.getWorkspaceDir(runId);
|
|
33
|
+
const workspaceReal = await realpath(workspaceDir);
|
|
34
|
+
const candidate = path.resolve(workspaceDir, relativePath);
|
|
35
|
+
let fileReal;
|
|
36
|
+
try {
|
|
37
|
+
fileReal = await realpath(candidate);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new Error(`Artifact not found: ${relativePath}`);
|
|
41
|
+
}
|
|
42
|
+
if (!isInsideDir(fileReal, workspaceReal)) {
|
|
43
|
+
throw new Error("path escapes the run workspace");
|
|
44
|
+
}
|
|
45
|
+
return readFile(fileReal, "utf8");
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
3
|
+
import { type McpToolDeps } from "./tools.js";
|
|
4
|
+
export type { McpToolDeps };
|
|
5
|
+
export declare function createStageflowMcpServer(deps: McpToolDeps): McpServer;
|
|
6
|
+
/** Stateless Streamable HTTP: new server + transport per request. */
|
|
7
|
+
export declare function handleMcpHttpRequest(req: IncomingMessage, res: ServerResponse, deps: McpToolDeps): Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
3
|
+
import { registerMcpTools } from "./tools.js";
|
|
4
|
+
export function createStageflowMcpServer(deps) {
|
|
5
|
+
const server = new McpServer({
|
|
6
|
+
name: "stageflow",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
});
|
|
9
|
+
registerMcpTools(server, deps);
|
|
10
|
+
return server;
|
|
11
|
+
}
|
|
12
|
+
/** Stateless Streamable HTTP: new server + transport per request. */
|
|
13
|
+
export async function handleMcpHttpRequest(req, res, deps) {
|
|
14
|
+
const server = createStageflowMcpServer(deps);
|
|
15
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
16
|
+
sessionIdGenerator: undefined,
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
await server.connect(transport);
|
|
20
|
+
await transport.handleRequest(req, res);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await transport.close().catch(() => undefined);
|
|
24
|
+
await server.close().catch(() => undefined);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { RunStore } from "../runstore/port.js";
|
|
3
|
+
import type { RunManager } from "../runtime/runManager.js";
|
|
4
|
+
export type McpToolDeps = {
|
|
5
|
+
manager: RunManager;
|
|
6
|
+
store: RunStore;
|
|
7
|
+
cwd: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function registerMcpTools(server: McpServer, deps: McpToolDeps): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listPipelines } from "../config/listConfig.js";
|
|
3
|
+
import { projectRunForMcp } from "./projectRun.js";
|
|
4
|
+
import { readRunArtifact } from "./readArtifact.js";
|
|
5
|
+
const taskFileSchema = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
goal: z.string(),
|
|
8
|
+
context: z.string().optional(),
|
|
9
|
+
constraints: z.string().optional(),
|
|
10
|
+
checkout: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
function textResult(data, isError = false) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
15
|
+
...(isError ? { isError: true } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function registerMcpTools(server, deps) {
|
|
19
|
+
const { manager, store, cwd } = deps;
|
|
20
|
+
server.registerTool("list_pipelines", {
|
|
21
|
+
description: "List runnable pipeline ids from the factory cwd",
|
|
22
|
+
inputSchema: z.object({}),
|
|
23
|
+
}, async () => {
|
|
24
|
+
const pipelines = await listPipelines(cwd);
|
|
25
|
+
return textResult({ pipelines });
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("list_runs", {
|
|
28
|
+
description: "List known pipeline runs",
|
|
29
|
+
inputSchema: z.object({}),
|
|
30
|
+
}, async () => {
|
|
31
|
+
const runs = await store.listRuns();
|
|
32
|
+
return textResult({ runs });
|
|
33
|
+
});
|
|
34
|
+
server.registerTool("get_health", {
|
|
35
|
+
description: "Server health and soft-max run capacity: activeRunIds, activeCount, maxConcurrent, slotsAvailable. Start until slotsAvailable is 0; then wait for a run to finish or raise STAGEFLOW_MAX_CONCURRENT_RUNS.",
|
|
36
|
+
inputSchema: z.object({}),
|
|
37
|
+
}, async () => textResult(manager.getHealth()));
|
|
38
|
+
server.registerTool("start_run", {
|
|
39
|
+
description: "Start a pipeline with an inline TaskFile (not written under tasks/). Returns { runId }. On conflict returns isError with code busy_capacity (soft max full) or busy_checkout (same checkout leased), plus activeCount/maxConcurrent/activeRunIds and optional conflictingRunId/conflictingCheckout.",
|
|
40
|
+
inputSchema: z.object({
|
|
41
|
+
pipeline: z.string(),
|
|
42
|
+
task: taskFileSchema,
|
|
43
|
+
}),
|
|
44
|
+
}, async ({ pipeline, task }) => {
|
|
45
|
+
if (!pipeline.trim()) {
|
|
46
|
+
return textResult({ error: "pipeline is required" }, true);
|
|
47
|
+
}
|
|
48
|
+
const result = await manager.startRun({ pipeline, task });
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
const { ok: _ok, reason, ...rest } = result;
|
|
51
|
+
return textResult({ error: reason, ...rest }, true);
|
|
52
|
+
}
|
|
53
|
+
return textResult({ runId: result.runId });
|
|
54
|
+
});
|
|
55
|
+
server.registerTool("get_run", {
|
|
56
|
+
description: "Poll a run: status, stage statuses, and envelope summary/payload/artifact paths (no events).",
|
|
57
|
+
inputSchema: z.object({
|
|
58
|
+
runId: z.string(),
|
|
59
|
+
}),
|
|
60
|
+
}, async ({ runId }) => {
|
|
61
|
+
try {
|
|
62
|
+
const detail = await store.readRun(runId);
|
|
63
|
+
return textResult(projectRunForMcp(detail));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
+
const notFound = /not found|no such|unknown run/i.test(message);
|
|
68
|
+
return textResult({
|
|
69
|
+
error: message,
|
|
70
|
+
status: notFound ? 404 : 500,
|
|
71
|
+
}, true);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
server.registerTool("read_artifact", {
|
|
75
|
+
description: "Read a text artifact from a run workspace by relative path (contained under the run workspace).",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
runId: z.string(),
|
|
78
|
+
path: z.string(),
|
|
79
|
+
}),
|
|
80
|
+
}, async ({ runId, path: artifactPath }) => {
|
|
81
|
+
try {
|
|
82
|
+
const content = await readRunArtifact(store, runId, artifactPath);
|
|
83
|
+
return textResult({ runId, path: artifactPath, content });
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
87
|
+
const notFound = message.startsWith("Run not found") ||
|
|
88
|
+
message.startsWith("Artifact not found") ||
|
|
89
|
+
/no such|not found/i.test(message);
|
|
90
|
+
return textResult({
|
|
91
|
+
error: message,
|
|
92
|
+
status: notFound ? 404 : 400,
|
|
93
|
+
}, true);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "stageflow";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PACKAGE_NAME = "stageflow";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Prefer RunStore.listRuns / readRun via createRunStore.
|
|
3
|
+
*/
|
|
4
|
+
export { deriveStatusFromStages, type StageLogEvent, type StageSnapshot, type RunSummary, type RunDetail, } from "./port.js";
|
|
5
|
+
import type { RunDetail, RunSummary, StageLogEvent } from "./port.js";
|
|
6
|
+
export declare function listRuns(rootDir: string): Promise<RunSummary[]>;
|
|
7
|
+
export declare function readRun(rootDir: string, runId: string): Promise<RunDetail>;
|
|
8
|
+
export declare function readStageLog(runDir: string, stageId: string): Promise<StageLogEvent[]>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Prefer RunStore.listRuns / readRun via createRunStore.
|
|
3
|
+
*/
|
|
4
|
+
export { deriveStatusFromStages, } from "./port.js";
|
|
5
|
+
import { DiskRunStore } from "./disk/DiskRunStore.js";
|
|
6
|
+
import { storeRootFor } from "./paths.js";
|
|
7
|
+
import { readStageLogFromDisk } from "./disk/catalogHelpers.js";
|
|
8
|
+
export async function listRuns(rootDir) {
|
|
9
|
+
return new DiskRunStore(storeRootFor(rootDir)).listRuns();
|
|
10
|
+
}
|
|
11
|
+
export async function readRun(rootDir, runId) {
|
|
12
|
+
return new DiskRunStore(storeRootFor(rootDir)).readRun(runId);
|
|
13
|
+
}
|
|
14
|
+
export async function readStageLog(runDir, stageId) {
|
|
15
|
+
return readStageLogFromDisk(runDir, stageId);
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RunStore } from "./port.js";
|
|
2
|
+
export type RunStoreKind = "sqlite";
|
|
3
|
+
export type RunStoreConfig = {
|
|
4
|
+
/** Project / factory root. Store data lives under `<rootDir>/.stageflow`. */
|
|
5
|
+
rootDir: string;
|
|
6
|
+
/** Defaults to `SF_STORE` env or `"sqlite"`. */
|
|
7
|
+
kind?: RunStoreKind | "disk";
|
|
8
|
+
};
|
|
9
|
+
export declare const DISK_STORE_REJECTED = "SF_STORE=disk is no longer a live RunStore. SQLite is the only adapter. Disk-era .stageflow/runs trees import automatically when the SQLite store is empty.";
|
|
10
|
+
/**
|
|
11
|
+
* Factory at the application edge. Call sites should receive the returned `RunStore`
|
|
12
|
+
* and must not branch on backend kind.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRunStore(config: RunStoreConfig): RunStore;
|
|
15
|
+
export type { RunStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { migrateLegacyStoreRoot, storeRootFor } from "./paths.js";
|
|
3
|
+
import { SqliteRunStore } from "./sqlite/SqliteRunStore.js";
|
|
4
|
+
export const DISK_STORE_REJECTED = "SF_STORE=disk is no longer a live RunStore. SQLite is the only adapter. Disk-era .stageflow/runs trees import automatically when the SQLite store is empty.";
|
|
5
|
+
function resolveKind(kind) {
|
|
6
|
+
const raw = kind ?? process.env.SF_STORE?.trim().toLowerCase();
|
|
7
|
+
if (raw === "disk") {
|
|
8
|
+
console.error(DISK_STORE_REJECTED);
|
|
9
|
+
throw new Error(DISK_STORE_REJECTED);
|
|
10
|
+
}
|
|
11
|
+
return "sqlite";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Factory at the application edge. Call sites should receive the returned `RunStore`
|
|
15
|
+
* and must not branch on backend kind.
|
|
16
|
+
*/
|
|
17
|
+
export function createRunStore(config) {
|
|
18
|
+
resolveKind(config.kind);
|
|
19
|
+
migrateLegacyStoreRoot(config.rootDir);
|
|
20
|
+
const storeRoot = storeRootFor(config.rootDir);
|
|
21
|
+
mkdirSync(storeRoot, { recursive: true });
|
|
22
|
+
return new SqliteRunStore(storeRoot);
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StageEnvelope } from "../../types/envelope.js";
|
|
2
|
+
import type { StageLogLine } from "../../agent/activity.js";
|
|
3
|
+
import type { CreateRunInput, CreatedRun, RunMeta, RunStatus, RunStore, StageExecution, StageExecutionPatch, StageLogEvent } from "../port.js";
|
|
4
|
+
export declare class DiskRunStore implements RunStore {
|
|
5
|
+
private readonly storeRoot;
|
|
6
|
+
constructor(storeRoot: string);
|
|
7
|
+
getWorkspaceDir(runId: string): string;
|
|
8
|
+
createRun(input: CreateRunInput): Promise<CreatedRun>;
|
|
9
|
+
updateRunStatus(runId: string, status: RunStatus): Promise<void>;
|
|
10
|
+
readRunMeta(runId: string): Promise<RunMeta>;
|
|
11
|
+
readTaskYaml(runId: string): Promise<string>;
|
|
12
|
+
ensureStageWorkspace(runId: string, stageId: string): Promise<void>;
|
|
13
|
+
ensureAttemptWorkspace(runId: string, stageId: string, attempt: number): Promise<void>;
|
|
14
|
+
createStageExecution(runId: string, stageId: string): Promise<StageExecution>;
|
|
15
|
+
listStageExecutions(runId: string, stageId: string): Promise<StageExecution[]>;
|
|
16
|
+
getLatestStageExecution(runId: string, stageId: string): Promise<StageExecution | null>;
|
|
17
|
+
countStageAttempts(runId: string, stageId: string): Promise<number>;
|
|
18
|
+
getStageExecution(runId: string, stageId: string, attempt: number): Promise<StageExecution>;
|
|
19
|
+
updateStageExecution(runId: string, stageId: string, attempt: number, patch: StageExecutionPatch): Promise<void>;
|
|
20
|
+
writeEnvelope(runId: string, stageId: string, envelope: StageEnvelope, options?: {
|
|
21
|
+
attempt?: number;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
readEnvelope(runId: string, stageId: string): Promise<StageEnvelope>;
|
|
24
|
+
appendStageEvent(runId: string, stageId: string, event: StageLogLine, options?: {
|
|
25
|
+
attempt?: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
|
|
28
|
+
listRuns(): Promise<import("../port.js").RunSummary[]>;
|
|
29
|
+
readRun(runId: string): Promise<import("../port.js").RunDetail>;
|
|
30
|
+
}
|