stageflow 0.23.0 → 0.25.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 +15 -3
- package/dist/a2a/configDiscovery.d.ts +9 -0
- package/dist/a2a/configDiscovery.js +18 -0
- package/dist/a2a/contracts.d.ts +93 -0
- package/dist/a2a/contracts.js +163 -0
- package/dist/a2a/limits.d.ts +14 -0
- package/dist/a2a/limits.js +29 -0
- package/dist/a2a/registry.d.ts +44 -0
- package/dist/a2a/registry.js +159 -0
- package/dist/a2a/server.d.ts +29 -0
- package/dist/a2a/server.js +341 -0
- package/dist/a2a/service.d.ts +97 -0
- package/dist/a2a/service.js +496 -0
- package/dist/a2a/store.d.ts +106 -0
- package/dist/a2a/store.js +245 -0
- package/dist/cli/a2aCommand.d.ts +8 -0
- package/dist/cli/a2aCommand.js +131 -0
- package/dist/cli/runStageCommand.d.ts +33 -0
- package/dist/cli/runStageCommand.js +407 -0
- package/dist/cli.js +19 -0
- package/dist/mcp/catalogTools.js +139 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/createStore.d.ts +10 -0
- package/dist/runstore/createStore.js +13 -4
- package/dist/runstore/port.d.ts +14 -1
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +9 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +66 -25
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +6 -0
- package/dist/runstore/submission.d.ts +11 -0
- package/dist/runstore/submission.js +7 -0
- package/dist/runtime/pipelineRunner.d.ts +2 -0
- package/dist/runtime/pipelineRunner.js +3 -0
- package/dist/runtime/pipelineScheduler.js +32 -15
- package/dist/runtime/reloadRunCatalog.js +7 -1
- package/dist/runtime/resolveEnvelopeRef.d.ts +30 -0
- package/dist/runtime/resolveEnvelopeRef.js +79 -0
- package/dist/runtime/runManager.d.ts +22 -1
- package/dist/runtime/runManager.js +48 -4
- package/dist/server/bootstrap.d.ts +2 -0
- package/dist/server/bootstrap.js +14 -3
- package/dist/server/createHttpHost.js +6 -0
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { RunSubmissionExistsError } from "../runstore/submission.js";
|
|
2
3
|
import { readFile, realpath } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { findProjectRoot } from "../project/findProjectRoot.js";
|
|
@@ -22,6 +23,12 @@ import { resumeSessionFilePath } from "./stageAttemptContext.js";
|
|
|
22
23
|
import { resolveStartTaskInput } from "./taskInput.js";
|
|
23
24
|
import { failStageAsInterrupted, syncRunStatusFromStages, } from "./stageRecovery.js";
|
|
24
25
|
import { blocksGenericRetry, manualRecoveryEligibility, readManualRecoveryState, } from "./manualRecoveryState.js";
|
|
26
|
+
function existingSubmissionResult(existing, request) {
|
|
27
|
+
if (existing.requestHash !== request.requestHash) {
|
|
28
|
+
return { ok: false, status: 409, reason: "Submission key was already used for different input" };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, runId: existing.runId, reused: true };
|
|
31
|
+
}
|
|
25
32
|
const DEFAULT_MAX_CONCURRENT = 3;
|
|
26
33
|
const STARTUP_RECONCILE_REASON = "process_interrupted: no active worker (server restart)";
|
|
27
34
|
const OPERATOR_ABANDON_REASON = "process_interrupted: operator abandoned stage";
|
|
@@ -45,6 +52,7 @@ async function toCheckoutLeaseKey(absPath) {
|
|
|
45
52
|
}
|
|
46
53
|
export class RunManager {
|
|
47
54
|
options;
|
|
55
|
+
submissionsInFlight = new Map();
|
|
48
56
|
active = new Map();
|
|
49
57
|
checkoutLeases = new Map();
|
|
50
58
|
provisionalIds = new Set();
|
|
@@ -348,7 +356,42 @@ export class RunManager {
|
|
|
348
356
|
}
|
|
349
357
|
return { ok: true, runId, stageId };
|
|
350
358
|
}
|
|
351
|
-
async
|
|
359
|
+
async startRunOnce(input, submission) {
|
|
360
|
+
if (!submission.key.trim() || submission.key.length > 256 || !/^[a-f0-9]{64}$/.test(submission.requestHash)) {
|
|
361
|
+
return { ok: false, status: 400, reason: "Submission requires a key and SHA-256 request hash" };
|
|
362
|
+
}
|
|
363
|
+
const pending = this.submissionsInFlight.get(submission.key);
|
|
364
|
+
if (pending) {
|
|
365
|
+
if (pending.requestHash !== submission.requestHash) {
|
|
366
|
+
return { ok: false, status: 409, reason: "Submission key was already used for different input" };
|
|
367
|
+
}
|
|
368
|
+
const result = await pending.result;
|
|
369
|
+
return result.ok ? { ...result, reused: true } : result;
|
|
370
|
+
}
|
|
371
|
+
const result = this.startSubmittedRun(input, submission);
|
|
372
|
+
this.submissionsInFlight.set(submission.key, { requestHash: submission.requestHash, result });
|
|
373
|
+
try {
|
|
374
|
+
return await result;
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
this.submissionsInFlight.delete(submission.key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async startSubmittedRun(input, submission) {
|
|
381
|
+
const existing = await this.options.store.getRunBySubmission(submission.key);
|
|
382
|
+
if (existing)
|
|
383
|
+
return existingSubmissionResult(existing, submission);
|
|
384
|
+
try {
|
|
385
|
+
const result = await this.startRun(input, submission);
|
|
386
|
+
return result.ok ? { ok: true, runId: result.runId, reused: false, done: result.done } : result;
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (error instanceof RunSubmissionExistsError)
|
|
390
|
+
return existingSubmissionResult(error.submission, submission);
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async startRun(input, submission) {
|
|
352
395
|
const cwd = this.options.cwd ?? process.cwd();
|
|
353
396
|
// An inline pipeline has no filesystem anchor to derive a project root from.
|
|
354
397
|
const derivedProjectRoot = typeof input.pipeline === "string"
|
|
@@ -389,7 +432,7 @@ export class RunManager {
|
|
|
389
432
|
gitSha: input.gitSha,
|
|
390
433
|
ciPrUrl: input.ciPrUrl,
|
|
391
434
|
ciJobUrl: input.ciJobUrl,
|
|
392
|
-
}, derivedProjectRoot, resolved.kind === "path" ? resolved.taskPath : undefined);
|
|
435
|
+
}, derivedProjectRoot, resolved.kind === "path" ? resolved.taskPath : undefined, submission);
|
|
393
436
|
}
|
|
394
437
|
async rerun(runId) {
|
|
395
438
|
const cwd = this.options.cwd ?? process.cwd();
|
|
@@ -1156,7 +1199,7 @@ export class RunManager {
|
|
|
1156
1199
|
this.attachedWaiting.delete(`${runId}\0${stageId}`);
|
|
1157
1200
|
}
|
|
1158
1201
|
}
|
|
1159
|
-
async reserveAndStartPipeline(taskYaml, pipeline, taskLabel, cwd, checkoutOverride, skipGates, ciIdentity, projectRoot, taskPath) {
|
|
1202
|
+
async reserveAndStartPipeline(taskYaml, pipeline, taskLabel, cwd, checkoutOverride, skipGates, ciIdentity, projectRoot, taskPath, submission) {
|
|
1160
1203
|
let checkoutKey;
|
|
1161
1204
|
try {
|
|
1162
1205
|
const task = loadTaskFromYaml(taskYaml, taskLabel);
|
|
@@ -1178,6 +1221,7 @@ export class RunManager {
|
|
|
1178
1221
|
return reserved.failure;
|
|
1179
1222
|
try {
|
|
1180
1223
|
const started = await startPipeline({
|
|
1224
|
+
submission,
|
|
1181
1225
|
agent: this.options.agent,
|
|
1182
1226
|
store: this.options.store,
|
|
1183
1227
|
taskYaml,
|
|
@@ -1201,7 +1245,7 @@ export class RunManager {
|
|
|
1201
1245
|
}
|
|
1202
1246
|
catch (err) {
|
|
1203
1247
|
this.clearReservation(reserved.provisionalId);
|
|
1204
|
-
if (err instanceof PipelineValidationError) {
|
|
1248
|
+
if (err instanceof PipelineValidationError || err instanceof RunSubmissionExistsError) {
|
|
1205
1249
|
throw err;
|
|
1206
1250
|
}
|
|
1207
1251
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type A2aHost } from "../a2a/server.js";
|
|
1
2
|
import type { AgentPort } from "../agent/port.js";
|
|
2
3
|
import type { ProviderAuthContext } from "../agent/providerAuth.js";
|
|
3
4
|
import { type RunStoreKind } from "../runstore/createStore.js";
|
|
@@ -18,6 +19,7 @@ export type StageflowHostOptions = {
|
|
|
18
19
|
runChangeBus?: RunChangeBus;
|
|
19
20
|
};
|
|
20
21
|
export type StageflowHostBootstrap = {
|
|
22
|
+
a2a?: A2aHost;
|
|
21
23
|
cwd: string;
|
|
22
24
|
agentDir: string;
|
|
23
25
|
rootDir: string;
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { createA2aHost } from "../a2a/server.js";
|
|
3
|
+
import { createRunStoreWithConnection } from "../runstore/createStore.js";
|
|
4
|
+
import { resolveA2aConfigPath } from "../a2a/configDiscovery.js";
|
|
3
5
|
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
4
6
|
import { findProjectRoot } from "../project/findProjectRoot.js";
|
|
5
7
|
import { RunManager } from "../runtime/runManager.js";
|
|
@@ -14,8 +16,16 @@ export async function bootstrapStageflowHost(options) {
|
|
|
14
16
|
const isGitProject = options.rootDir !== undefined
|
|
15
17
|
? findProjectRoot(rootDir) !== null
|
|
16
18
|
: ctx.isGitProject;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
let rawStore;
|
|
20
|
+
let sqliteConnection;
|
|
21
|
+
if (options.store) {
|
|
22
|
+
rawStore = options.store;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const created = createRunStoreWithConnection({ rootDir: ctx.globalHome, kind: options.storeKind });
|
|
26
|
+
rawStore = created.store;
|
|
27
|
+
sqliteConnection = created.connection;
|
|
28
|
+
}
|
|
19
29
|
const boundBus = isRunStoreWrapped(rawStore)
|
|
20
30
|
? getRunChangeBusFromWrappedStore(rawStore)
|
|
21
31
|
: undefined;
|
|
@@ -50,6 +60,7 @@ export async function bootstrapStageflowHost(options) {
|
|
|
50
60
|
projectRoot: rootDir,
|
|
51
61
|
}, { mcpStateless });
|
|
52
62
|
return {
|
|
63
|
+
a2a: await createA2aHost({ manager, runStore: store, rootDir, connection: sqliteConnection }, resolveA2aConfigPath(rootDir)),
|
|
53
64
|
cwd,
|
|
54
65
|
agentDir,
|
|
55
66
|
rootDir,
|
|
@@ -17,6 +17,12 @@ export async function createHttpHost(options) {
|
|
|
17
17
|
const method = req.method ?? "GET";
|
|
18
18
|
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
19
19
|
const pathname = url.pathname;
|
|
20
|
+
if (pathname === "/api/a2a/status" && method === "GET") {
|
|
21
|
+
json(res, 200, boot.a2a?.status ?? { state: "disabled" });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (await boot.a2a?.handle(req, res, pathname))
|
|
25
|
+
return;
|
|
20
26
|
if (pathname === "/mcp") {
|
|
21
27
|
if (!validateMcpHost(req, res) || !validateMcpOrigin(req, res)) {
|
|
22
28
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stageflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Stageflow — CLI pipeline runtime for configurable stages on Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"vitest": "^4.1.10"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
+
"@a2a-js/sdk": "1.1.0",
|
|
64
65
|
"@anthropic-ai/claude-agent-sdk": "^0.3.263",
|
|
65
66
|
"@earendil-works/pi-ai": "^0.84.1",
|
|
66
67
|
"@earendil-works/pi-coding-agent": "^0.84.1",
|