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
|
@@ -11,13 +11,22 @@ function resolveKind(kind) {
|
|
|
11
11
|
return "sqlite";
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Composition-root factory: hands back the concrete SQLite connection alongside the `RunStore`,
|
|
15
|
+
* for the one caller wiring a second, colocated store (A2A's tables) into the same `state.db`
|
|
16
|
+
* file. Ordinary call sites use `createRunStore` below and never see this.
|
|
16
17
|
*/
|
|
17
|
-
export function
|
|
18
|
+
export function createRunStoreWithConnection(config) {
|
|
18
19
|
resolveKind(config.kind);
|
|
19
20
|
migrateLegacyStoreRoot(config.rootDir);
|
|
20
21
|
const storeRoot = storeRootFor(config.rootDir);
|
|
21
22
|
mkdirSync(storeRoot, { recursive: true });
|
|
22
|
-
|
|
23
|
+
const store = new SqliteRunStore(storeRoot);
|
|
24
|
+
return { store, connection: store.connection };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Factory at the application edge. Call sites should receive the returned `RunStore`
|
|
28
|
+
* and must not branch on backend kind.
|
|
29
|
+
*/
|
|
30
|
+
export function createRunStore(config) {
|
|
31
|
+
return createRunStoreWithConnection(config).store;
|
|
23
32
|
}
|
package/dist/runstore/port.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { StageLogLine } from "../agent/activity.js";
|
|
2
|
+
import type { RunSubmission, RunSubmissionRecord } from "./submission.js";
|
|
2
3
|
import type { AskOperatorPrompt } from "../tools/askOperator.js";
|
|
3
4
|
import type { StageEnvelope } from "../types/envelope.js";
|
|
4
|
-
import type { FeedbackLoopConfig, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
5
|
+
import type { FeedbackLoopConfig, InlinePipelineDefinition, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
5
6
|
import type { CompletionCheck } from "../types/completion.js";
|
|
6
7
|
import type { StageGateKind } from "../types/stage.js";
|
|
7
8
|
import type { StageUsage } from "../types/usage.js";
|
|
@@ -51,6 +52,14 @@ export type RunMeta = {
|
|
|
51
52
|
pipeline_path?: string;
|
|
52
53
|
task_path?: string;
|
|
53
54
|
project_root?: string;
|
|
55
|
+
/**
|
|
56
|
+
* The raw pipeline body for a run started from an inline pipeline
|
|
57
|
+
* (no pipeline_path). Lets a fresh stage-worker process — which has no
|
|
58
|
+
* memory of the original in-process call — reconstruct the pipeline
|
|
59
|
+
* without needing a catalog file, mirroring how task_yaml already lets
|
|
60
|
+
* task reconstruction work without a task_path.
|
|
61
|
+
*/
|
|
62
|
+
inline_pipeline?: InlinePipelineDefinition;
|
|
54
63
|
};
|
|
55
64
|
export type CreatedRun = {
|
|
56
65
|
runId: string;
|
|
@@ -275,6 +284,7 @@ export type FeedbackLoopHistory = {
|
|
|
275
284
|
fork_generations: ForkGenerationRecord[];
|
|
276
285
|
};
|
|
277
286
|
export type CreateRunInput = {
|
|
287
|
+
submission?: RunSubmission;
|
|
278
288
|
pipelineId: string;
|
|
279
289
|
taskYaml: string;
|
|
280
290
|
taskId?: string;
|
|
@@ -286,6 +296,8 @@ export type CreateRunInput = {
|
|
|
286
296
|
pipelinePath?: string;
|
|
287
297
|
taskPath?: string;
|
|
288
298
|
projectRoot?: string;
|
|
299
|
+
/** Raw inline pipeline body, persisted only when pipelinePath is absent. */
|
|
300
|
+
inlinePipeline?: InlinePipelineDefinition;
|
|
289
301
|
};
|
|
290
302
|
export type ListRunsFilter = {
|
|
291
303
|
status?: RunStatus;
|
|
@@ -301,6 +313,7 @@ export type ListRunsFilter = {
|
|
|
301
313
|
* run id is all you have (catalog / rehydrate).
|
|
302
314
|
*/
|
|
303
315
|
export interface RunStore {
|
|
316
|
+
getRunBySubmission(key: string): Promise<RunSubmissionRecord | null>;
|
|
304
317
|
createRun(input: CreateRunInput): Promise<CreatedRun>;
|
|
305
318
|
updateRunStatus(runId: string, status: RunStatus): Promise<void>;
|
|
306
319
|
readRunMeta(runId: string): Promise<RunMeta>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
1
2
|
import type { StageEnvelope } from "../../types/envelope.js";
|
|
2
3
|
import type { StageLogLine } from "../../agent/activity.js";
|
|
3
4
|
import { type CreateFeedbackLoopInput, type CreateFeedbackReplayInput, type CreateFeedbackReplayStagePassInput, type CreateForkGenerationInput, type CreateRunInput, type CreatedRun, type FeedbackLoopPatch, type FeedbackLoopRecord, type FeedbackLoopState, type FeedbackReplayPatch, type FeedbackReplayRecord, type FeedbackReplayStagePassPatch, type FeedbackReplayStagePassRecord, type ForkGenerationPatch, type ForkGenerationRecord, type ListRunsFilter, type RunDetail, type RunMeta, type RunPipelineDagSnapshot, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent, type VerificationCheckResult, type VerificationCheckResultPatch } from "../port.js";
|
|
5
|
+
import { type RunSubmissionRecord } from "../submission.js";
|
|
4
6
|
export declare class SqliteRunStore implements RunStore {
|
|
5
7
|
private readonly storeRoot;
|
|
6
8
|
private readonly db;
|
|
@@ -8,8 +10,15 @@ export declare class SqliteRunStore implements RunStore {
|
|
|
8
10
|
constructor(storeRoot: string);
|
|
9
11
|
/** Await one-shot disk import before catalog reads (create paths also wait). */
|
|
10
12
|
ready(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* The connection this store owns, for the one caller allowed to share it: the composition root
|
|
15
|
+
* wiring the A2A tables into the same `state.db` file. Not part of the `RunStore` interface.
|
|
16
|
+
*/
|
|
17
|
+
get connection(): Database.Database;
|
|
11
18
|
getWorkspaceDir(runId: string): string;
|
|
12
19
|
createRun(input: CreateRunInput): Promise<CreatedRun>;
|
|
20
|
+
private readSubmission;
|
|
21
|
+
getRunBySubmission(key: string): Promise<RunSubmissionRecord | null>;
|
|
13
22
|
updateRunStatus(runId: string, status: RunStatus): Promise<void>;
|
|
14
23
|
updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
|
|
15
24
|
readRunMeta(runId: string): Promise<RunMeta>;
|
|
@@ -9,6 +9,7 @@ import { newRunId, runWorkspaceDir } from "../paths.js";
|
|
|
9
9
|
import { attemptAgentDir, attemptArtifactsDir, stageDir, } from "../workspaceLayout.js";
|
|
10
10
|
import { importDiskRunsIfEmpty } from "./migrateFromDisk.js";
|
|
11
11
|
import { SCHEMA_SQL } from "./schema.js";
|
|
12
|
+
import { RunSubmissionExistsError } from "../submission.js";
|
|
12
13
|
const ensuredStageDirs = new Set();
|
|
13
14
|
const DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5000;
|
|
14
15
|
function readSqliteBusyTimeoutMs() {
|
|
@@ -52,6 +53,12 @@ function ensureRunLocatorColumns(db) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
function ensureInlinePipelineColumn(db) {
|
|
57
|
+
const cols = db.prepare(`PRAGMA table_info(runs)`).all();
|
|
58
|
+
if (!cols.some((c) => c.name === "inline_pipeline_json")) {
|
|
59
|
+
db.exec(`ALTER TABLE runs ADD COLUMN inline_pipeline_json TEXT`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
55
62
|
function ensureStageExecutionsTable(db) {
|
|
56
63
|
db.exec(`
|
|
57
64
|
CREATE TABLE IF NOT EXISTS stage_executions (
|
|
@@ -304,6 +311,7 @@ export class SqliteRunStore {
|
|
|
304
311
|
ensureCiIdentityColumns(this.db);
|
|
305
312
|
ensurePipelineDagColumn(this.db);
|
|
306
313
|
ensureRunLocatorColumns(this.db);
|
|
314
|
+
ensureInlinePipelineColumn(this.db);
|
|
307
315
|
ensureStageExecutionsTable(this.db);
|
|
308
316
|
ensureStageExecutionVerificationOutcomeColumn(this.db);
|
|
309
317
|
ensureStageExecutionCostColumns(this.db);
|
|
@@ -319,6 +327,13 @@ export class SqliteRunStore {
|
|
|
319
327
|
async ready() {
|
|
320
328
|
await this.migratePromise;
|
|
321
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* The connection this store owns, for the one caller allowed to share it: the composition root
|
|
332
|
+
* wiring the A2A tables into the same `state.db` file. Not part of the `RunStore` interface.
|
|
333
|
+
*/
|
|
334
|
+
get connection() {
|
|
335
|
+
return this.db;
|
|
336
|
+
}
|
|
322
337
|
getWorkspaceDir(runId) {
|
|
323
338
|
return runWorkspaceDir(this.storeRoot, runId);
|
|
324
339
|
}
|
|
@@ -337,32 +352,55 @@ export class SqliteRunStore {
|
|
|
337
352
|
const projectRoot = input.projectRoot
|
|
338
353
|
? normalizeCatalogPath(input.projectRoot)
|
|
339
354
|
: null;
|
|
340
|
-
this.db
|
|
341
|
-
.
|
|
342
|
-
|
|
355
|
+
this.db.transaction(() => {
|
|
356
|
+
if (input.submission) {
|
|
357
|
+
const existing = this.readSubmission(input.submission.key);
|
|
358
|
+
if (existing)
|
|
359
|
+
throw new RunSubmissionExistsError(existing);
|
|
360
|
+
}
|
|
361
|
+
this.db
|
|
362
|
+
.prepare(`INSERT INTO runs
|
|
363
|
+
(run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root, inline_pipeline_json)
|
|
343
364
|
VALUES
|
|
344
|
-
(@run_id, @pipeline_id, @task_id, @task_yaml, @status, @created_at, @updated_at, @checkout_root, @pipeline_dag_json, @git_sha, @ci_pr_url, @ci_job_url, @pipeline_path, @task_path, @project_root)`)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
(@run_id, @pipeline_id, @task_id, @task_yaml, @status, @created_at, @updated_at, @checkout_root, @pipeline_dag_json, @git_sha, @ci_pr_url, @ci_job_url, @pipeline_path, @task_path, @project_root, @inline_pipeline_json)`)
|
|
366
|
+
.run({
|
|
367
|
+
run_id: runId,
|
|
368
|
+
pipeline_id: input.pipelineId,
|
|
369
|
+
task_id: input.taskId ?? null,
|
|
370
|
+
task_yaml: input.taskYaml,
|
|
371
|
+
status: "running",
|
|
372
|
+
created_at: now,
|
|
373
|
+
updated_at: now,
|
|
374
|
+
checkout_root: input.checkoutRoot ?? null,
|
|
375
|
+
pipeline_dag_json: input.pipelineDag
|
|
376
|
+
? JSON.stringify(input.pipelineDag)
|
|
377
|
+
: null,
|
|
378
|
+
git_sha: input.gitSha ?? null,
|
|
379
|
+
ci_pr_url: input.ciPrUrl ?? null,
|
|
380
|
+
ci_job_url: input.ciJobUrl ?? null,
|
|
381
|
+
pipeline_path: pipelinePath,
|
|
382
|
+
task_path: taskPath,
|
|
383
|
+
project_root: projectRoot,
|
|
384
|
+
inline_pipeline_json: input.inlinePipeline
|
|
385
|
+
? JSON.stringify(input.inlinePipeline)
|
|
386
|
+
: null,
|
|
387
|
+
});
|
|
388
|
+
if (input.submission) {
|
|
389
|
+
this.db.prepare("INSERT INTO run_submissions (submission_key, request_hash, run_id) VALUES (?, ?, ?)")
|
|
390
|
+
.run(input.submission.key, input.submission.requestHash, runId);
|
|
391
|
+
}
|
|
392
|
+
})();
|
|
364
393
|
return { runId, workspaceDir };
|
|
365
394
|
}
|
|
395
|
+
readSubmission(key) {
|
|
396
|
+
const row = this.db.prepare("SELECT submission_key AS key, request_hash AS requestHash, run_id AS runId FROM run_submissions WHERE submission_key = ?")
|
|
397
|
+
.get(key);
|
|
398
|
+
return row ?? null;
|
|
399
|
+
}
|
|
400
|
+
async getRunBySubmission(key) {
|
|
401
|
+
await this.ready();
|
|
402
|
+
return this.readSubmission(key);
|
|
403
|
+
}
|
|
366
404
|
async updateRunStatus(runId, status) {
|
|
367
405
|
await this.ready();
|
|
368
406
|
const result = this.db
|
|
@@ -710,7 +748,7 @@ export class SqliteRunStore {
|
|
|
710
748
|
}
|
|
711
749
|
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
712
750
|
const rows = this.db
|
|
713
|
-
.prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root
|
|
751
|
+
.prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root, inline_pipeline_json
|
|
714
752
|
FROM runs ${where} ORDER BY created_at DESC`)
|
|
715
753
|
.all(...params);
|
|
716
754
|
const summaries = [];
|
|
@@ -1139,11 +1177,14 @@ export class SqliteRunStore {
|
|
|
1139
1177
|
...(row.task_path != null ? { task_path: row.task_path } : {}),
|
|
1140
1178
|
...(row.project_root != null ? { project_root: row.project_root } : {}),
|
|
1141
1179
|
...(pipeline_dag ? { pipeline_dag } : {}),
|
|
1180
|
+
...(row.inline_pipeline_json != null
|
|
1181
|
+
? { inline_pipeline: JSON.parse(row.inline_pipeline_json) }
|
|
1182
|
+
: {}),
|
|
1142
1183
|
};
|
|
1143
1184
|
}
|
|
1144
1185
|
getRunRow(runId) {
|
|
1145
1186
|
const row = this.db
|
|
1146
|
-
.prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root
|
|
1187
|
+
.prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url, pipeline_path, task_path, project_root, inline_pipeline_json
|
|
1147
1188
|
FROM runs WHERE run_id = ?`)
|
|
1148
1189
|
.get(runId);
|
|
1149
1190
|
if (!row)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n cost_usd REAL,\n usage_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|
|
1
|
+
export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS run_submissions (\n submission_key TEXT PRIMARY KEY,\n request_hash TEXT NOT NULL,\n run_id TEXT NOT NULL UNIQUE REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n cost_usd REAL,\n usage_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
|
|
@@ -17,6 +17,12 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
17
17
|
project_root TEXT
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
+
CREATE TABLE IF NOT EXISTS run_submissions (
|
|
21
|
+
submission_key TEXT PRIMARY KEY,
|
|
22
|
+
request_hash TEXT NOT NULL,
|
|
23
|
+
run_id TEXT NOT NULL UNIQUE REFERENCES runs(run_id)
|
|
24
|
+
);
|
|
25
|
+
|
|
20
26
|
CREATE TABLE IF NOT EXISTS stages (
|
|
21
27
|
run_id TEXT NOT NULL,
|
|
22
28
|
stage_id TEXT NOT NULL,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type RunSubmission = {
|
|
2
|
+
key: string;
|
|
3
|
+
requestHash: string;
|
|
4
|
+
};
|
|
5
|
+
export type RunSubmissionRecord = RunSubmission & {
|
|
6
|
+
runId: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class RunSubmissionExistsError extends Error {
|
|
9
|
+
readonly submission: RunSubmissionRecord;
|
|
10
|
+
constructor(submission: RunSubmissionRecord);
|
|
11
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RunSubmission } from "../runstore/submission.js";
|
|
1
2
|
import type { AgentPort } from "../agent/port.js";
|
|
2
3
|
import { type ValidationFinding } from "../config/validateCatalog.js";
|
|
3
4
|
import type { RunStore } from "../runstore/port.js";
|
|
@@ -70,6 +71,7 @@ export declare function runPipeline(options: {
|
|
|
70
71
|
}): Promise<PipelineRunResult>;
|
|
71
72
|
/** Create the run immediately, then execute stages in the returned promise. */
|
|
72
73
|
export declare function startPipeline(options: {
|
|
74
|
+
submission?: RunSubmission;
|
|
73
75
|
agent: AgentPort;
|
|
74
76
|
store: RunStore;
|
|
75
77
|
taskPath?: string;
|
|
@@ -65,6 +65,7 @@ async function preparePipeline(options) {
|
|
|
65
65
|
: undefined;
|
|
66
66
|
const projectRoot = normalizeCatalogPath(options.projectRoot ?? options.cwd);
|
|
67
67
|
const run = await options.store.createRun({
|
|
68
|
+
submission: options.submission,
|
|
68
69
|
pipelineId: loaded.pipeline.id,
|
|
69
70
|
taskYaml,
|
|
70
71
|
taskId: task.id,
|
|
@@ -76,6 +77,7 @@ async function preparePipeline(options) {
|
|
|
76
77
|
pipelinePath,
|
|
77
78
|
taskPath,
|
|
78
79
|
projectRoot,
|
|
80
|
+
inlinePipeline: typeof options.pipeline === "string" ? undefined : options.pipeline,
|
|
79
81
|
});
|
|
80
82
|
const executionMode = readStageExecutionMode(process.env, options.executionMode);
|
|
81
83
|
const stageProcessLauncher = resolveStageProcessLauncher(executionMode, options.stageProcessLauncher);
|
|
@@ -145,6 +147,7 @@ export async function startPipeline(options) {
|
|
|
145
147
|
const cwd = options.cwd ?? process.cwd();
|
|
146
148
|
const projectRoot = options.projectRoot ?? cwd;
|
|
147
149
|
const prepared = await preparePipeline({
|
|
150
|
+
submission: options.submission,
|
|
148
151
|
agent: options.agent,
|
|
149
152
|
store: options.store,
|
|
150
153
|
taskPath: options.taskPath,
|
|
@@ -220,6 +220,25 @@ export function applyForkSkipsFromEnvelopes(dag, states, completedEnvelopes) {
|
|
|
220
220
|
applyForkChoiceToSchedule(dag, node.id, chosen, states);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
+
async function applyEagerRouteIfSkipsFromEnvelopes(dag, states, completedEnvelopes, persistSkip) {
|
|
224
|
+
for (const [parentId, envelope] of completedEnvelopes) {
|
|
225
|
+
if (states.get(parentId) !== "succeeded")
|
|
226
|
+
continue;
|
|
227
|
+
for (const childId of dag.childrenOf[parentId] ?? []) {
|
|
228
|
+
const child = dag.nodes.find((n) => n.id === childId);
|
|
229
|
+
if (!child)
|
|
230
|
+
continue;
|
|
231
|
+
const result = classifyInboundAfterSuccess(child, parentId, envelope.payload);
|
|
232
|
+
if (result === "missing_field") {
|
|
233
|
+
return parentId;
|
|
234
|
+
}
|
|
235
|
+
if (isEagerSingleParentIfSkip(child, parentId, envelope.payload)) {
|
|
236
|
+
await persistSkip(childId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
223
242
|
export function applyRetryRootDelta(dag, states, completedEnvelopes, retryRoots, stageId, attempt, clearSchedulingHalt, alreadyLaunchedIds) {
|
|
224
243
|
retryRoots.set(stageId, attempt);
|
|
225
244
|
const downstream = cloneRetryDownstream(dag, [stageId]);
|
|
@@ -472,6 +491,13 @@ export async function runPipelineDag(options) {
|
|
|
472
491
|
await persistSkipPending(id);
|
|
473
492
|
}
|
|
474
493
|
}
|
|
494
|
+
const missingRouteIfParent = await applyEagerRouteIfSkipsFromEnvelopes(dag, states, completedEnvelopes, persistSkipPending);
|
|
495
|
+
if (missingRouteIfParent !== undefined) {
|
|
496
|
+
schedulingHalted = true;
|
|
497
|
+
if (firstFailureReason === undefined) {
|
|
498
|
+
firstFailureReason = `stage "${missingRouteIfParent}": route if field missing from payload`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
475
501
|
const markSkippedPending = () => {
|
|
476
502
|
for (const node of dag.nodes) {
|
|
477
503
|
if (states.get(node.id) === "pending") {
|
|
@@ -756,22 +782,13 @@ export async function runPipelineDag(options) {
|
|
|
756
782
|
await store.appendStageEvent(run.runId, id, { event: "skipped" });
|
|
757
783
|
}
|
|
758
784
|
}
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
const result = classifyInboundAfterSuccess(child, stageId, envelope.payload);
|
|
765
|
-
if (result === "missing_field") {
|
|
766
|
-
schedulingHalted = true;
|
|
767
|
-
if (firstFailureReason === undefined) {
|
|
768
|
-
firstFailureReason = `stage "${stageId}": route if field missing from payload`;
|
|
769
|
-
}
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
if (isEagerSingleParentIfSkip(child, stageId, envelope.payload)) {
|
|
773
|
-
await persistSkipPending(childId);
|
|
785
|
+
const missingParent = await applyEagerRouteIfSkipsFromEnvelopes(dag, states, new Map([[stageId, envelope]]), persistSkipPending);
|
|
786
|
+
if (missingParent !== undefined) {
|
|
787
|
+
schedulingHalted = true;
|
|
788
|
+
if (firstFailureReason === undefined) {
|
|
789
|
+
firstFailureReason = `stage "${missingParent}": route if field missing from payload`;
|
|
774
790
|
}
|
|
791
|
+
return;
|
|
775
792
|
}
|
|
776
793
|
await reopenRunnableSkippedStages(store, run.runId, dag, states, completedEnvelopes);
|
|
777
794
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
|
-
import { loadPipeline } from "../config/loadPipeline.js";
|
|
2
|
+
import { loadPipeline, loadPipelineFromObject } from "../config/loadPipeline.js";
|
|
3
3
|
import { loadTask } from "../config/loadTask.js";
|
|
4
4
|
import { normalizeCatalogPath } from "../runstore/normalizeCatalogPath.js";
|
|
5
5
|
function missingLocator(field, runId) {
|
|
@@ -33,6 +33,12 @@ async function assertCatalogFileExists(filePath, label) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function reloadPipelineForRun(meta) {
|
|
35
35
|
const projectRoot = requireProjectRoot(meta);
|
|
36
|
+
// An inline-pipeline run (no pipeline_path — nothing was ever written to a
|
|
37
|
+
// catalog file) carries its own pipeline body in meta.inline_pipeline
|
|
38
|
+
// instead, the same way a task without task_path reloads from task_yaml.
|
|
39
|
+
if (meta.inline_pipeline) {
|
|
40
|
+
return loadPipelineFromObject(meta.inline_pipeline, { projectRoot });
|
|
41
|
+
}
|
|
36
42
|
const pipelinePath = requirePipelinePath(meta);
|
|
37
43
|
await assertCatalogFileExists(pipelinePath, "Pipeline");
|
|
38
44
|
return loadPipeline(pipelinePath, { cwd: projectRoot });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type StoreLookupKind } from "../server/operatorResults.js";
|
|
2
|
+
import type { RunStore } from "../runstore/port.js";
|
|
3
|
+
import type { TaskFile } from "../types/task.js";
|
|
4
|
+
export type EnvelopeRefInput = {
|
|
5
|
+
runId: string;
|
|
6
|
+
stageId: string;
|
|
7
|
+
attempt?: number;
|
|
8
|
+
};
|
|
9
|
+
type EnvelopeRefStore = Pick<RunStore, "readRunMeta" | "readRun" | "readEnvelope">;
|
|
10
|
+
/** Carries enough to map onto either MCP's textResult shape or an A2aApplicationError. */
|
|
11
|
+
export declare class EnvelopeRefError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly kind: StoreLookupKind;
|
|
14
|
+
constructor(message: string, status: number, kind: StoreLookupKind);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves one or more Envelope References (ADR-0002) into a TaskFile for
|
|
18
|
+
* the next standalone stage call — shared by the MCP run_stage tool and the
|
|
19
|
+
* A2A run_stage operation so this resolution logic exists in exactly one
|
|
20
|
+
* place (it was duplicated between them once already; see the reuse finding
|
|
21
|
+
* from this feature's code review).
|
|
22
|
+
*
|
|
23
|
+
* A single ref keeps today's exact shape: goal = its summary, input = its
|
|
24
|
+
* payload verbatim. Multiple refs namespace each resolved payload under its
|
|
25
|
+
* stageId (disambiguated by runId only if two refs share a stageId), so a
|
|
26
|
+
* stage author addresses each source by name in their own io.input.schema
|
|
27
|
+
* instead of one silently overwriting another on a key collision.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveEnvelopeRefsToTask(store: EnvelopeRefStore, refs: EnvelopeRefInput | EnvelopeRefInput[], checkout?: string): Promise<TaskFile>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mapStoreLookupError } from "../server/operatorResults.js";
|
|
2
|
+
/** Carries enough to map onto either MCP's textResult shape or an A2aApplicationError. */
|
|
3
|
+
export class EnvelopeRefError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
kind;
|
|
6
|
+
constructor(message, status, kind) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.kind = kind;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function resolveOne(store, ref) {
|
|
13
|
+
try {
|
|
14
|
+
await store.readRunMeta(ref.runId);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const mapped = mapStoreLookupError(err, { policy: "run" });
|
|
18
|
+
throw new EnvelopeRefError(mapped.error, mapped.status, mapped.kind);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const detail = await store.readRun(ref.runId);
|
|
22
|
+
if (!detail.stages.some((s) => s.stage_id === ref.stageId)) {
|
|
23
|
+
throw new EnvelopeRefError(`Stage not found: ${ref.stageId}`, 404, "not_found");
|
|
24
|
+
}
|
|
25
|
+
return await store.readEnvelope(ref.runId, ref.stageId, ref.attempt);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof EnvelopeRefError)
|
|
29
|
+
throw err;
|
|
30
|
+
const mapped = mapStoreLookupError(err, { policy: "envelope" });
|
|
31
|
+
throw new EnvelopeRefError(mapped.error, mapped.status, mapped.kind);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolves one or more Envelope References (ADR-0002) into a TaskFile for
|
|
36
|
+
* the next standalone stage call — shared by the MCP run_stage tool and the
|
|
37
|
+
* A2A run_stage operation so this resolution logic exists in exactly one
|
|
38
|
+
* place (it was duplicated between them once already; see the reuse finding
|
|
39
|
+
* from this feature's code review).
|
|
40
|
+
*
|
|
41
|
+
* A single ref keeps today's exact shape: goal = its summary, input = its
|
|
42
|
+
* payload verbatim. Multiple refs namespace each resolved payload under its
|
|
43
|
+
* stageId (disambiguated by runId only if two refs share a stageId), so a
|
|
44
|
+
* stage author addresses each source by name in their own io.input.schema
|
|
45
|
+
* instead of one silently overwriting another on a key collision.
|
|
46
|
+
*/
|
|
47
|
+
export async function resolveEnvelopeRefsToTask(store, refs, checkout) {
|
|
48
|
+
const list = Array.isArray(refs) ? refs : [refs];
|
|
49
|
+
const resolved = [];
|
|
50
|
+
for (const ref of list) {
|
|
51
|
+
resolved.push({ ref, envelope: await resolveOne(store, ref) });
|
|
52
|
+
}
|
|
53
|
+
if (resolved.length === 1) {
|
|
54
|
+
const { ref, envelope } = resolved[0];
|
|
55
|
+
return {
|
|
56
|
+
id: `ref-${ref.runId}-${ref.stageId}`,
|
|
57
|
+
goal: envelope.summary,
|
|
58
|
+
input: envelope.payload ?? {},
|
|
59
|
+
...(checkout ? { checkout } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const usedKeys = new Set();
|
|
63
|
+
const input = {};
|
|
64
|
+
const summaryLines = [];
|
|
65
|
+
for (const { ref, envelope } of resolved) {
|
|
66
|
+
let key = ref.stageId;
|
|
67
|
+
if (usedKeys.has(key))
|
|
68
|
+
key = `${ref.stageId}:${ref.runId}`;
|
|
69
|
+
usedKeys.add(key);
|
|
70
|
+
input[key] = envelope.payload ?? {};
|
|
71
|
+
summaryLines.push(`${key}: ${envelope.summary}`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: `ref-${list.map((r) => r.stageId).join("+")}`,
|
|
75
|
+
goal: summaryLines.join("\n"),
|
|
76
|
+
input,
|
|
77
|
+
...(checkout ? { checkout } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RunSubmission } from "../runstore/submission.js";
|
|
1
2
|
import type { AgentPort, OpaqueAnswer } from "../agent/port.js";
|
|
2
3
|
import type { InlinePipelineDefinition } from "../types/pipeline.js";
|
|
3
4
|
import { type RunStore } from "../runstore/port.js";
|
|
@@ -35,6 +36,23 @@ export type CapacityHealth = {
|
|
|
35
36
|
activeStageProcesses: number;
|
|
36
37
|
maxActiveStageProcesses: number | null;
|
|
37
38
|
};
|
|
39
|
+
export type StartRunOnceResult = {
|
|
40
|
+
ok: true;
|
|
41
|
+
runId: string;
|
|
42
|
+
/** False only for the caller whose submission durably created this run for the first time. */
|
|
43
|
+
reused: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Present when this call observed the run being launched in this process — either it launched
|
|
46
|
+
* the run itself, or it piggybacked on another in-flight call to the same submission key while
|
|
47
|
+
* that launch was still starting. Absent when the submission was already durably committed by
|
|
48
|
+
* an earlier, separate call: `reused: true` does not by itself imply `done` is absent, since a
|
|
49
|
+
* piggybacked call is also `reused: true`. Callers that need completion regardless of which case
|
|
50
|
+
* they hit must still fall back to polling run status by `runId`.
|
|
51
|
+
*/
|
|
52
|
+
done?: Promise<PipelineRunResult>;
|
|
53
|
+
} | Extract<StartRunResult, {
|
|
54
|
+
ok: false;
|
|
55
|
+
}>;
|
|
38
56
|
export type { DeliverAnswerResult };
|
|
39
57
|
export type { RetryStageResult };
|
|
40
58
|
export type AbandonStageResult = {
|
|
@@ -66,6 +84,7 @@ export type StopManualRecoveryResult = {
|
|
|
66
84
|
};
|
|
67
85
|
export declare class RunManager {
|
|
68
86
|
private readonly options;
|
|
87
|
+
private readonly submissionsInFlight;
|
|
69
88
|
private readonly active;
|
|
70
89
|
private readonly checkoutLeases;
|
|
71
90
|
private readonly provisionalIds;
|
|
@@ -125,6 +144,8 @@ export declare class RunManager {
|
|
|
125
144
|
runId: string;
|
|
126
145
|
}>>;
|
|
127
146
|
abandonStage(runId: string, stageId: string): Promise<AbandonStageResult>;
|
|
147
|
+
startRunOnce(input: Parameters<RunManager["startRun"]>[0], submission: RunSubmission): Promise<StartRunOnceResult>;
|
|
148
|
+
private startSubmittedRun;
|
|
128
149
|
startRun(input: StartTaskInput & {
|
|
129
150
|
pipeline: string | InlinePipelineDefinition;
|
|
130
151
|
task?: string | TaskFile;
|
|
@@ -133,7 +154,7 @@ export declare class RunManager {
|
|
|
133
154
|
gitSha?: string;
|
|
134
155
|
ciPrUrl?: string;
|
|
135
156
|
ciJobUrl?: string;
|
|
136
|
-
}): Promise<StartRunResult>;
|
|
157
|
+
}, submission?: RunSubmission): Promise<StartRunResult>;
|
|
137
158
|
rerun(runId: string): Promise<StartRunResult>;
|
|
138
159
|
retryStage(runId: string, stageId: string): Promise<RetryStageResult>;
|
|
139
160
|
resumeTimedOutStage(runId: string, stageId: string): Promise<RetryStageResult>;
|