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.
Files changed (180) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/agent/activity.d.ts +76 -0
  4. package/dist/agent/activity.js +89 -0
  5. package/dist/agent/activityObserver.d.ts +17 -0
  6. package/dist/agent/activityObserver.js +66 -0
  7. package/dist/agent/cursorExtension.d.ts +12 -0
  8. package/dist/agent/cursorExtension.js +88 -0
  9. package/dist/agent/cursorProvider.d.ts +14 -0
  10. package/dist/agent/cursorProvider.js +139 -0
  11. package/dist/agent/fakeAgent.d.ts +29 -0
  12. package/dist/agent/fakeAgent.js +295 -0
  13. package/dist/agent/oauthSessionManager.d.ts +33 -0
  14. package/dist/agent/oauthSessionManager.js +214 -0
  15. package/dist/agent/piAdapter.d.ts +128 -0
  16. package/dist/agent/piAdapter.js +967 -0
  17. package/dist/agent/port.d.ts +63 -0
  18. package/dist/agent/port.js +46 -0
  19. package/dist/agent/providerAuth.d.ts +101 -0
  20. package/dist/agent/providerAuth.js +282 -0
  21. package/dist/agent/providerSupport.d.ts +28 -0
  22. package/dist/agent/providerSupport.js +19 -0
  23. package/dist/cli/providersCommand.d.ts +11 -0
  24. package/dist/cli/providersCommand.js +275 -0
  25. package/dist/cli/terminalAuthInteraction.d.ts +8 -0
  26. package/dist/cli/terminalAuthInteraction.js +65 -0
  27. package/dist/cli/terminalSecret.d.ts +2 -0
  28. package/dist/cli/terminalSecret.js +67 -0
  29. package/dist/cli/validateCommand.d.ts +11 -0
  30. package/dist/cli/validateCommand.js +81 -0
  31. package/dist/cli/validateOutput.d.ts +8 -0
  32. package/dist/cli/validateOutput.js +75 -0
  33. package/dist/cli.d.ts +2 -0
  34. package/dist/cli.js +273 -0
  35. package/dist/config/createPipeline.d.ts +31 -0
  36. package/dist/config/createPipeline.js +255 -0
  37. package/dist/config/createStage.d.ts +25 -0
  38. package/dist/config/createStage.js +181 -0
  39. package/dist/config/listConfig.d.ts +33 -0
  40. package/dist/config/listConfig.js +195 -0
  41. package/dist/config/listExtensions.d.ts +26 -0
  42. package/dist/config/listExtensions.js +50 -0
  43. package/dist/config/listSkills.d.ts +29 -0
  44. package/dist/config/listSkills.js +55 -0
  45. package/dist/config/loadOutcome.d.ts +27 -0
  46. package/dist/config/loadOutcome.js +6 -0
  47. package/dist/config/loadPipeline.d.ts +25 -0
  48. package/dist/config/loadPipeline.js +196 -0
  49. package/dist/config/loadStage.d.ts +4 -0
  50. package/dist/config/loadStage.js +127 -0
  51. package/dist/config/loadTask.d.ts +7 -0
  52. package/dist/config/loadTask.js +69 -0
  53. package/dist/config/readYamlObject.d.ts +1 -0
  54. package/dist/config/readYamlObject.js +5 -0
  55. package/dist/config/resolvePipelineDag.d.ts +12 -0
  56. package/dist/config/resolvePipelineDag.js +282 -0
  57. package/dist/config/validateCatalog.d.ts +36 -0
  58. package/dist/config/validateCatalog.js +322 -0
  59. package/dist/envelope/check.d.ts +3 -0
  60. package/dist/envelope/check.js +52 -0
  61. package/dist/envelope/payloadSchema.d.ts +10 -0
  62. package/dist/envelope/payloadSchema.js +91 -0
  63. package/dist/hitl/qaTrail.d.ts +31 -0
  64. package/dist/hitl/qaTrail.js +65 -0
  65. package/dist/index.d.ts +13 -0
  66. package/dist/index.js +7 -0
  67. package/dist/mcp/projectRun.d.ts +37 -0
  68. package/dist/mcp/projectRun.js +57 -0
  69. package/dist/mcp/readArtifact.d.ts +2 -0
  70. package/dist/mcp/readArtifact.js +46 -0
  71. package/dist/mcp/server.d.ts +7 -0
  72. package/dist/mcp/server.js +26 -0
  73. package/dist/mcp/tools.d.ts +9 -0
  74. package/dist/mcp/tools.js +96 -0
  75. package/dist/package-meta.d.ts +1 -0
  76. package/dist/package-meta.js +1 -0
  77. package/dist/prompt/priorEnvelope.d.ts +2 -0
  78. package/dist/prompt/priorEnvelope.js +5 -0
  79. package/dist/runstore/catalog.d.ts +8 -0
  80. package/dist/runstore/catalog.js +16 -0
  81. package/dist/runstore/createStore.d.ts +15 -0
  82. package/dist/runstore/createStore.js +23 -0
  83. package/dist/runstore/disk/DiskRunStore.d.ts +30 -0
  84. package/dist/runstore/disk/DiskRunStore.js +238 -0
  85. package/dist/runstore/disk/catalogHelpers.d.ts +11 -0
  86. package/dist/runstore/disk/catalogHelpers.js +102 -0
  87. package/dist/runstore/layout.d.ts +22 -0
  88. package/dist/runstore/layout.js +58 -0
  89. package/dist/runstore/paths.d.ts +7 -0
  90. package/dist/runstore/paths.js +60 -0
  91. package/dist/runstore/pipelineDagSnapshot.d.ts +5 -0
  92. package/dist/runstore/pipelineDagSnapshot.js +43 -0
  93. package/dist/runstore/port.d.ts +146 -0
  94. package/dist/runstore/port.js +28 -0
  95. package/dist/runstore/runProjection.d.ts +6 -0
  96. package/dist/runstore/runProjection.js +102 -0
  97. package/dist/runstore/sqlite/SqliteRunStore.d.ts +48 -0
  98. package/dist/runstore/sqlite/SqliteRunStore.js +460 -0
  99. package/dist/runstore/sqlite/migrateFromDisk.d.ts +5 -0
  100. package/dist/runstore/sqlite/migrateFromDisk.js +108 -0
  101. package/dist/runstore/sqlite/schema.d.ts +1 -0
  102. package/dist/runstore/sqlite/schema.js +59 -0
  103. package/dist/runstore/stageExecution.d.ts +13 -0
  104. package/dist/runstore/stageExecution.js +22 -0
  105. package/dist/runstore/stageSnapshot.d.ts +2 -0
  106. package/dist/runstore/stageSnapshot.js +46 -0
  107. package/dist/runstore/syntheticStageSnapshot.d.ts +2 -0
  108. package/dist/runstore/syntheticStageSnapshot.js +10 -0
  109. package/dist/runstore/trackProjection.d.ts +7 -0
  110. package/dist/runstore/trackProjection.js +94 -0
  111. package/dist/runstore/workspaceLayout.d.ts +31 -0
  112. package/dist/runstore/workspaceLayout.js +172 -0
  113. package/dist/runtime/answerResume.d.ts +16 -0
  114. package/dist/runtime/answerResume.js +22 -0
  115. package/dist/runtime/credentialBinding.d.ts +16 -0
  116. package/dist/runtime/credentialBinding.js +80 -0
  117. package/dist/runtime/dagTraversal.d.ts +3 -0
  118. package/dist/runtime/dagTraversal.js +26 -0
  119. package/dist/runtime/envelopeRouting.d.ts +20 -0
  120. package/dist/runtime/envelopeRouting.js +59 -0
  121. package/dist/runtime/hitlSeams.d.ts +38 -0
  122. package/dist/runtime/hitlSeams.js +2 -0
  123. package/dist/runtime/pipelineRunner.d.ts +77 -0
  124. package/dist/runtime/pipelineRunner.js +141 -0
  125. package/dist/runtime/pipelineScheduler.d.ts +90 -0
  126. package/dist/runtime/pipelineScheduler.js +494 -0
  127. package/dist/runtime/pipelineValidationError.d.ts +6 -0
  128. package/dist/runtime/pipelineValidationError.js +8 -0
  129. package/dist/runtime/resumeReconstruct.d.ts +33 -0
  130. package/dist/runtime/resumeReconstruct.js +144 -0
  131. package/dist/runtime/runManager.d.ts +129 -0
  132. package/dist/runtime/runManager.js +818 -0
  133. package/dist/runtime/runRetryCoordinator.d.ts +85 -0
  134. package/dist/runtime/runRetryCoordinator.js +372 -0
  135. package/dist/runtime/settingsFile.d.ts +17 -0
  136. package/dist/runtime/settingsFile.js +88 -0
  137. package/dist/runtime/stageAttemptBootstrap.d.ts +40 -0
  138. package/dist/runtime/stageAttemptBootstrap.js +82 -0
  139. package/dist/runtime/stageAttemptContext.d.ts +11 -0
  140. package/dist/runtime/stageAttemptContext.js +26 -0
  141. package/dist/runtime/stageConcurrency.d.ts +10 -0
  142. package/dist/runtime/stageConcurrency.js +51 -0
  143. package/dist/runtime/stageHitl.d.ts +95 -0
  144. package/dist/runtime/stageHitl.js +198 -0
  145. package/dist/runtime/stageProcessLauncher.d.ts +45 -0
  146. package/dist/runtime/stageProcessLauncher.js +199 -0
  147. package/dist/runtime/stageRecovery.d.ts +10 -0
  148. package/dist/runtime/stageRecovery.js +43 -0
  149. package/dist/runtime/stageRoots.d.ts +22 -0
  150. package/dist/runtime/stageRoots.js +93 -0
  151. package/dist/runtime/stageRunner.d.ts +59 -0
  152. package/dist/runtime/stageRunner.js +221 -0
  153. package/dist/runtime/stageWorker.d.ts +6 -0
  154. package/dist/runtime/stageWorker.js +109 -0
  155. package/dist/runtime/stageWorkerProtocol.d.ts +27 -0
  156. package/dist/runtime/stageWorkerProtocol.js +16 -0
  157. package/dist/runtime/taskInput.d.ts +15 -0
  158. package/dist/runtime/taskInput.js +38 -0
  159. package/dist/server/http.d.ts +31 -0
  160. package/dist/server/http.js +561 -0
  161. package/dist/server/providerRoutes.d.ts +15 -0
  162. package/dist/server/providerRoutes.js +186 -0
  163. package/dist/tools/askOperator.d.ts +185 -0
  164. package/dist/tools/askOperator.js +409 -0
  165. package/dist/tools/emitStageEnvelope.d.ts +42 -0
  166. package/dist/tools/emitStageEnvelope.js +61 -0
  167. package/dist/tools/writeStageArtifact.d.ts +26 -0
  168. package/dist/tools/writeStageArtifact.js +103 -0
  169. package/dist/types/envelope.d.ts +12 -0
  170. package/dist/types/envelope.js +6 -0
  171. package/dist/types/pipeline.d.ts +25 -0
  172. package/dist/types/pipeline.js +1 -0
  173. package/dist/types/stage.d.ts +12 -0
  174. package/dist/types/stage.js +6 -0
  175. package/dist/types/task.d.ts +7 -0
  176. package/dist/types/task.js +1 -0
  177. package/dist/ui/assets/index-Cry0Tpfx.js +118 -0
  178. package/dist/ui/assets/index-DCsDopak.css +1 -0
  179. package/dist/ui/index.html +21 -0
  180. package/package.json +72 -0
@@ -0,0 +1,22 @@
1
+ import type { StageAttemptContext } from "./stageAttemptContext.js";
2
+ import type { TaskFile } from "../types/task.js";
3
+ export type StageRoots = {
4
+ mode: "bound" | "unbound";
5
+ cwd: string;
6
+ runWorkspaceDir: string;
7
+ checkoutRoot?: string;
8
+ agentDir: string;
9
+ attempt?: number;
10
+ /** Durable Pi auth.json path for ModelRuntime.create({ authPath }). */
11
+ authPath?: string;
12
+ };
13
+ export declare const STAGEFLOW_RUN_WORKSPACE = "STAGEFLOW_RUN_WORKSPACE";
14
+ export declare const PI_CODING_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
15
+ export declare function withResolvedAuthPath(roots: StageRoots, factoryCwd: string): StageRoots;
16
+ export declare function rootsForStageWorker(runWorkspaceDir: string, stageId: string, model: string, checkoutRoot?: string, attemptCtx?: StageAttemptContext): StageRoots;
17
+ export declare function bindPiAgentDirEnv(agentDirPath: string): () => void;
18
+ export declare function resolveCheckoutPath(declared: string, cwd: string): string;
19
+ export declare function resolveAndValidateCheckout(task: TaskFile, override: string | undefined, factoryCwd: string): Promise<string | undefined>;
20
+ export declare function buildStageRoots(runWorkspaceDir: string, stageId: string, checkoutRoot?: string, attemptCtx?: StageAttemptContext): StageRoots;
21
+ export declare function bindRunWorkspaceEnv(_runWorkspaceDir: string): () => void;
22
+ export declare function resetBindRunWorkspaceEnvForTests(): void;
@@ -0,0 +1,93 @@
1
+ import { access, constants, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { isCursorModelRef } from "../agent/cursorProvider.js";
4
+ import { stageDir } from "../runstore/paths.js";
5
+ import { noAttemptContext } from "./stageAttemptContext.js";
6
+ import { resolveCredentialBinding } from "./credentialBinding.js";
7
+ export const STAGEFLOW_RUN_WORKSPACE = "STAGEFLOW_RUN_WORKSPACE";
8
+ export const PI_CODING_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
9
+ export function withResolvedAuthPath(roots, factoryCwd) {
10
+ if (roots.authPath)
11
+ return roots;
12
+ const binding = resolveCredentialBinding(factoryCwd);
13
+ return { ...roots, authPath: binding.authPath };
14
+ }
15
+ export function rootsForStageWorker(runWorkspaceDir, stageId, model, checkoutRoot, attemptCtx) {
16
+ const roots = buildStageRoots(runWorkspaceDir, stageId, checkoutRoot, attemptCtx);
17
+ if (!isCursorModelRef(model) || roots.mode === "bound") {
18
+ return roots;
19
+ }
20
+ return {
21
+ ...roots,
22
+ cwd: stageDir(runWorkspaceDir, stageId),
23
+ };
24
+ }
25
+ export function bindPiAgentDirEnv(agentDirPath) {
26
+ const prev = process.env[PI_CODING_AGENT_DIR_ENV];
27
+ process.env[PI_CODING_AGENT_DIR_ENV] = agentDirPath;
28
+ return () => {
29
+ if (prev === undefined) {
30
+ delete process.env[PI_CODING_AGENT_DIR_ENV];
31
+ }
32
+ else {
33
+ process.env[PI_CODING_AGENT_DIR_ENV] = prev;
34
+ }
35
+ };
36
+ }
37
+ export function resolveCheckoutPath(declared, cwd) {
38
+ return path.resolve(cwd, declared);
39
+ }
40
+ export async function resolveAndValidateCheckout(task, override, factoryCwd) {
41
+ const raw = override ?? task.checkout;
42
+ if (raw === undefined)
43
+ return undefined;
44
+ if (raw.trim() === "") {
45
+ throw new Error("Invalid checkout: path is empty or whitespace-only");
46
+ }
47
+ const absPath = resolveCheckoutPath(raw, factoryCwd);
48
+ let st;
49
+ try {
50
+ st = await stat(absPath);
51
+ }
52
+ catch {
53
+ throw new Error(`Checkout path does not exist: ${absPath}`);
54
+ }
55
+ if (!st.isDirectory()) {
56
+ throw new Error(`Checkout path is not a directory: ${absPath}`);
57
+ }
58
+ try {
59
+ await access(absPath, constants.R_OK | constants.W_OK | constants.X_OK);
60
+ }
61
+ catch {
62
+ throw new Error(`Checkout path is not readable/writable/searchable: ${absPath}`);
63
+ }
64
+ return absPath;
65
+ }
66
+ export function buildStageRoots(runWorkspaceDir, stageId, checkoutRoot, attemptCtx) {
67
+ const ctx = attemptCtx ?? noAttemptContext();
68
+ const agentDirPath = ctx.agentDirPath(runWorkspaceDir, stageId);
69
+ const attempt = ctx.attempt;
70
+ if (checkoutRoot !== undefined) {
71
+ return {
72
+ mode: "bound",
73
+ cwd: checkoutRoot,
74
+ runWorkspaceDir,
75
+ checkoutRoot,
76
+ agentDir: agentDirPath,
77
+ attempt,
78
+ };
79
+ }
80
+ return {
81
+ mode: "unbound",
82
+ cwd: runWorkspaceDir,
83
+ runWorkspaceDir,
84
+ agentDir: agentDirPath,
85
+ attempt,
86
+ };
87
+ }
88
+ export function bindRunWorkspaceEnv(_runWorkspaceDir) {
89
+ return () => { };
90
+ }
91
+ export function resetBindRunWorkspaceEnvForTests() {
92
+ delete process.env[STAGEFLOW_RUN_WORKSPACE];
93
+ }
@@ -0,0 +1,59 @@
1
+ import type { AgentPort, StageHandle, StageRunResult } from "../agent/port.js";
2
+ import type { StageEnvelope } from "../types/envelope.js";
3
+ import type { StageConfig } from "../types/stage.js";
4
+ import type { TaskFile } from "../types/task.js";
5
+ import type { RunStore } from "../runstore/port.js";
6
+ import type { ResolvedPipelineDag } from "../types/pipeline.js";
7
+ import type { StageAttemptContext } from "./stageAttemptContext.js";
8
+ import { type StageRoots } from "./stageRoots.js";
9
+ import { type StageHitlController } from "./stageHitl.js";
10
+ import { type OperatorCatalog } from "./stageAttemptBootstrap.js";
11
+ export type RunStageOutcome = StageRunResult | {
12
+ waiting: true;
13
+ };
14
+ export type RunStageYieldLoopResult = StageRunResult | {
15
+ waiting: true;
16
+ };
17
+ export declare function isRunStageWaiting(outcome: RunStageOutcome): outcome is {
18
+ waiting: true;
19
+ };
20
+ export type RunStageOptions = {
21
+ agent: AgentPort;
22
+ store: RunStore;
23
+ runId: string;
24
+ stage: StageConfig;
25
+ task: TaskFile;
26
+ dag?: ResolvedPipelineDag;
27
+ priorEnvelope?: StageEnvelope | null;
28
+ checkoutRoot?: string;
29
+ workspaceDir?: string;
30
+ hitl?: StageHitlController;
31
+ /** Skip appending `started` (restart reconstruct already past start). */
32
+ skipStarted?: boolean;
33
+ /** When set, used instead of buildStageRoots (worker cursor isolation). */
34
+ roots?: StageRoots;
35
+ /**
36
+ * When set, use this handle instead of openStage (restart: caller opens and
37
+ * may pre-inject the answer before the yield loop).
38
+ */
39
+ existingHandle?: StageHandle;
40
+ workerMode?: boolean;
41
+ attemptCtx?: StageAttemptContext;
42
+ /** Factory project root for credential binding (settings + SF-owned auth). */
43
+ factoryCwd?: string;
44
+ operatorCatalog?: OperatorCatalog;
45
+ completedEnvelopes?: Map<string, StageEnvelope>;
46
+ };
47
+ /**
48
+ * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
49
+ */
50
+ export declare function runStageYieldLoop(options: {
51
+ handle: StageHandle;
52
+ runId: string;
53
+ stageId: string;
54
+ hitl?: StageHitlController;
55
+ workerMode?: boolean;
56
+ store?: RunStore;
57
+ attemptCtx?: StageAttemptContext;
58
+ }): Promise<RunStageYieldLoopResult>;
59
+ export declare function runStage(options: RunStageOptions): Promise<RunStageOutcome>;
@@ -0,0 +1,221 @@
1
+ import { deriveExecutionPatchFromEvent } from "../runstore/stageExecution.js";
2
+ import { buildStageRoots, withResolvedAuthPath, } from "./stageRoots.js";
3
+ import { createDefaultHitlQaHooks, recordWaitEnter, } from "./stageHitl.js";
4
+ import { openStageAttempt, } from "./stageAttemptBootstrap.js";
5
+ export function isRunStageWaiting(outcome) {
6
+ return "waiting" in outcome && outcome.waiting === true;
7
+ }
8
+ const LIFECYCLE_EVENTS = new Set([
9
+ "started",
10
+ "resumed",
11
+ "waiting_for_input",
12
+ "succeeded",
13
+ "failed",
14
+ ]);
15
+ async function ensureStageExecutionForAttempt(store, runId, stageId, attempt) {
16
+ try {
17
+ await store.getStageExecution(runId, stageId, attempt);
18
+ }
19
+ catch {
20
+ if (attempt !== 1) {
21
+ throw new Error(`Stage execution not found: ${runId}/${stageId} attempt ${attempt}`);
22
+ }
23
+ await store.createStageExecution(runId, stageId);
24
+ }
25
+ }
26
+ async function appendLifecycleEventAndPatchExecution(options) {
27
+ const { store, runId, stageId, event, attemptCtx } = options;
28
+ const attemptNum = attemptCtx?.attempt ?? 1;
29
+ const attemptOpt = attemptCtx?.eventOptions();
30
+ await store.appendStageEvent(runId, stageId, event, attemptOpt);
31
+ if (!LIFECYCLE_EVENTS.has(event.event))
32
+ return;
33
+ try {
34
+ const execution = await store.getStageExecution(runId, stageId, attemptNum);
35
+ const patch = deriveExecutionPatchFromEvent(event, execution);
36
+ if (Object.keys(patch).length === 0)
37
+ return;
38
+ await store.updateStageExecution(runId, stageId, attemptNum, patch);
39
+ }
40
+ catch {
41
+ // no execution row yet
42
+ }
43
+ }
44
+ async function finalizeStageResult(options) {
45
+ const { store, runId, stageId, result, activityChain, attemptCtx } = options;
46
+ const attemptOpt = attemptCtx?.eventOptions();
47
+ await activityChain;
48
+ if (result.envelope) {
49
+ await store.writeEnvelope(runId, stageId, result.envelope, attemptOpt);
50
+ }
51
+ if (!result.ok) {
52
+ await appendLifecycleEventAndPatchExecution({
53
+ store,
54
+ runId,
55
+ stageId,
56
+ event: { event: "failed", reason: result.reason },
57
+ attemptCtx,
58
+ });
59
+ console.error(`Stage ${stageId} failed: ${result.reason}`);
60
+ return result;
61
+ }
62
+ await appendLifecycleEventAndPatchExecution({
63
+ store,
64
+ runId,
65
+ stageId,
66
+ event: { event: "succeeded" },
67
+ attemptCtx,
68
+ });
69
+ console.error(`Stage ${stageId} succeeded.`);
70
+ return result;
71
+ }
72
+ /**
73
+ * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
74
+ */
75
+ export async function runStageYieldLoop(options) {
76
+ const { handle, runId, stageId, hitl, workerMode, store, attemptCtx } = options;
77
+ while (true) {
78
+ const event = await handle.next();
79
+ if (event.status === "waiting_for_input") {
80
+ if (workerMode) {
81
+ if (!store) {
82
+ return {
83
+ ok: false,
84
+ reason: "workerMode requires store",
85
+ };
86
+ }
87
+ await recordWaitEnter({
88
+ store,
89
+ runId,
90
+ stageId,
91
+ request: event.request,
92
+ attemptCtx,
93
+ qaHooks: createDefaultHitlQaHooks(store),
94
+ });
95
+ return { waiting: true };
96
+ }
97
+ if (!hitl) {
98
+ return {
99
+ ok: false,
100
+ reason: "stage requested wait but no HITL controller is configured",
101
+ };
102
+ }
103
+ await hitl.enterWait(runId, stageId, handle, event.request, attemptCtx);
104
+ continue;
105
+ }
106
+ return event.result;
107
+ }
108
+ }
109
+ export async function runStage(options) {
110
+ const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, } = options;
111
+ const attemptOpt = attemptCtx?.eventOptions();
112
+ const baseRoots = rootsOverride ??
113
+ buildStageRoots(workspaceDir ?? store.getWorkspaceDir(runId), stage.id, checkoutRoot, attemptCtx);
114
+ const roots = factoryCwd !== undefined
115
+ ? withResolvedAuthPath(baseRoots, factoryCwd)
116
+ : baseRoots;
117
+ const attempt = attemptCtx?.attempt ?? 1;
118
+ await ensureStageExecutionForAttempt(store, runId, stage.id, attempt);
119
+ await store.ensureAttemptWorkspace(runId, stage.id, attempt);
120
+ if (!skipStarted) {
121
+ await appendLifecycleEventAndPatchExecution({
122
+ store,
123
+ runId,
124
+ stageId: stage.id,
125
+ event: { event: "started" },
126
+ attemptCtx,
127
+ });
128
+ }
129
+ console.error(`Running stage ${stage.id} (${stage.model})...`);
130
+ let activityChain = Promise.resolve();
131
+ const enqueueActivity = (event) => {
132
+ activityChain = activityChain.then(() => store.appendStageEvent(runId, stage.id, event, attemptOpt));
133
+ };
134
+ let handle;
135
+ if (existingHandle) {
136
+ handle = existingHandle;
137
+ }
138
+ else {
139
+ const opened = await openStageAttempt({
140
+ agent,
141
+ store,
142
+ runId,
143
+ stage,
144
+ task,
145
+ dag: dag ?? {
146
+ nodes: [
147
+ { id: stage.id, needs: null, ancestors: [], stageIndex: 0 },
148
+ ],
149
+ roots: [stage.id],
150
+ childrenOf: {},
151
+ },
152
+ checkoutRoot,
153
+ workspaceDir: workspaceDir ?? store.getWorkspaceDir(runId),
154
+ factoryCwd,
155
+ attemptCtx,
156
+ operatorCatalog,
157
+ roots,
158
+ completedEnvelopes,
159
+ onActivity: (event) => {
160
+ enqueueActivity(event);
161
+ },
162
+ });
163
+ if (!opened.ok) {
164
+ await appendLifecycleEventAndPatchExecution({
165
+ store,
166
+ runId,
167
+ stageId: stage.id,
168
+ event: { event: "failed", reason: opened.reason },
169
+ attemptCtx,
170
+ });
171
+ console.error(`Stage ${stage.id} failed: ${opened.reason}`);
172
+ return { ok: false, reason: opened.reason };
173
+ }
174
+ handle = opened.handle;
175
+ }
176
+ let outcome;
177
+ try {
178
+ outcome = await runStageYieldLoop({
179
+ handle,
180
+ runId,
181
+ stageId: stage.id,
182
+ hitl,
183
+ workerMode,
184
+ store: workerMode ? store : undefined,
185
+ attemptCtx,
186
+ });
187
+ }
188
+ catch (err) {
189
+ const reason = err instanceof Error ? err.message : String(err);
190
+ await activityChain;
191
+ hitl?.clearLiveWait(runId, stage.id);
192
+ await appendLifecycleEventAndPatchExecution({
193
+ store,
194
+ runId,
195
+ stageId: stage.id,
196
+ event: { event: "failed", reason },
197
+ attemptCtx,
198
+ });
199
+ console.error(`Stage ${stage.id} failed: ${reason}`);
200
+ await handle.close().catch(() => undefined);
201
+ return { ok: false, reason };
202
+ }
203
+ if (isRunStageWaiting(outcome)) {
204
+ await activityChain;
205
+ await handle.close({ park: true }).catch(() => undefined);
206
+ return { waiting: true };
207
+ }
208
+ try {
209
+ return await finalizeStageResult({
210
+ store,
211
+ runId,
212
+ stageId: stage.id,
213
+ result: outcome,
214
+ activityChain,
215
+ attemptCtx,
216
+ });
217
+ }
218
+ finally {
219
+ await handle.close().catch(() => undefined);
220
+ }
221
+ }
@@ -0,0 +1,6 @@
1
+ import { type RunStageOutcome } from "./stageRunner.js";
2
+ import { type StageWorkerInput, type StageWorkerResult } from "./stageWorkerProtocol.js";
3
+ export type { StageWorkerInput, StageWorkerResult } from "./stageWorkerProtocol.js";
4
+ export declare function runStageWorker(input: StageWorkerInput): Promise<RunStageOutcome>;
5
+ export declare function sendWorkerResult(result: StageWorkerResult): void;
6
+ export declare function exitForOutcome(outcome: RunStageOutcome): never;
@@ -0,0 +1,109 @@
1
+ import { PiAgentAdapter } from "../agent/piAdapter.js";
2
+ import { loadPipeline } from "../config/loadPipeline.js";
3
+ import { loadTaskFromYaml } from "../config/loadTask.js";
4
+ import { createRunStore } from "../runstore/createStore.js";
5
+ import { bindPiAgentDirEnv, rootsForStageWorker, withResolvedAuthPath, } from "./stageRoots.js";
6
+ import { runStage, isRunStageWaiting, } from "./stageRunner.js";
7
+ import { openStageAttempt } from "./stageAttemptBootstrap.js";
8
+ import { attemptContext } from "./stageAttemptContext.js";
9
+ import { outcomeToWorkerResult, STAGE_WORKER_EXIT, } from "./stageWorkerProtocol.js";
10
+ export async function runStageWorker(input) {
11
+ const store = createRunStore({ rootDir: input.rootDir });
12
+ const meta = await store.readRunMeta(input.runId);
13
+ const taskYaml = await store.readTaskYaml(input.runId);
14
+ const task = loadTaskFromYaml(taskYaml, `run ${input.runId} task`);
15
+ const loaded = await loadPipeline(meta.pipeline_id, { cwd: input.rootDir });
16
+ const stage = loaded.stages.find((s) => s.id === input.stageId);
17
+ if (!stage) {
18
+ return {
19
+ ok: false,
20
+ reason: `Stage ${input.stageId} not in pipeline ${meta.pipeline_id}`,
21
+ };
22
+ }
23
+ const agent = new PiAgentAdapter();
24
+ const workspaceDir = store.getWorkspaceDir(input.runId);
25
+ const checkoutRoot = meta.checkout_root;
26
+ const mode = input.mode ?? "run";
27
+ const attempt = input.attempt ?? 1;
28
+ const attemptCtx = input.attempt !== undefined ? attemptContext(input.attempt) : undefined;
29
+ const eventOptions = { attempt };
30
+ const roots = withResolvedAuthPath(rootsForStageWorker(workspaceDir, input.stageId, stage.model, checkoutRoot, attemptCtx), input.rootDir);
31
+ const unbindAgentDir = bindPiAgentDirEnv(roots.agentDir);
32
+ try {
33
+ if (mode === "resume") {
34
+ const opened = await openStageAttempt({
35
+ agent,
36
+ store,
37
+ runId: input.runId,
38
+ stage,
39
+ task,
40
+ dag: loaded.dag,
41
+ checkoutRoot,
42
+ workspaceDir,
43
+ factoryCwd: input.rootDir,
44
+ attemptCtx,
45
+ operatorCatalog: input.operatorCatalog,
46
+ roots,
47
+ resumeToken: input.sessionFilePath,
48
+ onActivity: (event) => {
49
+ void store.appendStageEvent(input.runId, input.stageId, event, eventOptions);
50
+ },
51
+ });
52
+ if (!opened.ok) {
53
+ return { ok: false, reason: opened.reason };
54
+ }
55
+ opened.handle.deliverAnswer(input.resumeAnswer);
56
+ const outcome = await runStage({
57
+ agent,
58
+ store,
59
+ runId: input.runId,
60
+ stage,
61
+ task,
62
+ dag: loaded.dag,
63
+ checkoutRoot,
64
+ workspaceDir,
65
+ skipStarted: true,
66
+ existingHandle: opened.handle,
67
+ roots,
68
+ attemptCtx,
69
+ factoryCwd: input.rootDir,
70
+ operatorCatalog: input.operatorCatalog,
71
+ });
72
+ return outcome;
73
+ }
74
+ return runStage({
75
+ agent,
76
+ store,
77
+ runId: input.runId,
78
+ stage,
79
+ task,
80
+ dag: loaded.dag,
81
+ checkoutRoot,
82
+ workspaceDir,
83
+ workerMode: true,
84
+ roots,
85
+ attemptCtx,
86
+ factoryCwd: input.rootDir,
87
+ operatorCatalog: input.operatorCatalog,
88
+ });
89
+ }
90
+ finally {
91
+ unbindAgentDir();
92
+ }
93
+ }
94
+ export function sendWorkerResult(result) {
95
+ if (typeof process.send === "function") {
96
+ process.send(result);
97
+ }
98
+ }
99
+ export function exitForOutcome(outcome) {
100
+ const message = outcomeToWorkerResult(outcome);
101
+ sendWorkerResult(message);
102
+ if (isRunStageWaiting(outcome)) {
103
+ process.exit(STAGE_WORKER_EXIT.WAITING);
104
+ }
105
+ if (outcome.ok) {
106
+ process.exit(STAGE_WORKER_EXIT.SUCCEEDED);
107
+ }
108
+ process.exit(STAGE_WORKER_EXIT.FAILED);
109
+ }
@@ -0,0 +1,27 @@
1
+ import type { RunStageOutcome } from "./stageRunner.js";
2
+ import type { OperatorCatalog } from "./stageAttemptBootstrap.js";
3
+ export declare const SF_STAGE_WORKER = "SF_STAGE_WORKER";
4
+ export declare const STAGE_WORKER_EXIT: {
5
+ readonly SUCCEEDED: 0;
6
+ readonly FAILED: 1;
7
+ readonly WAITING: 2;
8
+ };
9
+ export type StageWorkerInput = {
10
+ runId: string;
11
+ stageId: string;
12
+ rootDir: string;
13
+ mode?: "run" | "resume";
14
+ resumeAnswer?: unknown;
15
+ attempt?: number;
16
+ sessionFilePath?: string;
17
+ operatorCatalog?: OperatorCatalog;
18
+ };
19
+ export type StageWorkerResult = {
20
+ type: "succeeded";
21
+ } | {
22
+ type: "failed";
23
+ reason: string;
24
+ } | {
25
+ type: "waiting";
26
+ };
27
+ export declare function outcomeToWorkerResult(outcome: RunStageOutcome): StageWorkerResult;
@@ -0,0 +1,16 @@
1
+ export const SF_STAGE_WORKER = "SF_STAGE_WORKER";
2
+ export const STAGE_WORKER_EXIT = {
3
+ SUCCEEDED: 0,
4
+ FAILED: 1,
5
+ WAITING: 2,
6
+ };
7
+ export function outcomeToWorkerResult(outcome) {
8
+ if ("waiting" in outcome && outcome.waiting) {
9
+ return { type: "waiting" };
10
+ }
11
+ const result = outcome;
12
+ if (result.ok) {
13
+ return { type: "succeeded" };
14
+ }
15
+ return { type: "failed", reason: result.reason };
16
+ }
@@ -0,0 +1,15 @@
1
+ import type { TaskFile } from "../types/task.js";
2
+ export type StartTaskInput = {
3
+ task?: string | TaskFile;
4
+ taskYaml?: string;
5
+ };
6
+ export type ResolvedTaskInput = {
7
+ kind: "path";
8
+ taskPath: string;
9
+ } | {
10
+ kind: "yaml";
11
+ taskYaml: string;
12
+ };
13
+ export declare function isTaskFile(value: unknown): value is TaskFile;
14
+ export declare function taskFileToYaml(task: TaskFile): string;
15
+ export declare function resolveStartTaskInput(input: StartTaskInput, cwd: string): ResolvedTaskInput;
@@ -0,0 +1,38 @@
1
+ import { stringify as stringifyYaml } from "yaml";
2
+ import { parseTaskFile } from "../config/loadTask.js";
3
+ import { resolveTaskPath } from "./pipelineRunner.js";
4
+ export function isTaskFile(value) {
5
+ return parseTaskFile(value, "task").ok;
6
+ }
7
+ export function taskFileToYaml(task) {
8
+ const doc = {
9
+ id: task.id,
10
+ goal: task.goal,
11
+ };
12
+ if (task.context !== undefined)
13
+ doc.context = task.context;
14
+ if (task.constraints !== undefined)
15
+ doc.constraints = task.constraints;
16
+ if (task.checkout !== undefined)
17
+ doc.checkout = task.checkout;
18
+ return stringifyYaml(doc);
19
+ }
20
+ export function resolveStartTaskInput(input, cwd) {
21
+ if (typeof input.taskYaml === "string") {
22
+ if (input.taskYaml.trim().length === 0) {
23
+ throw new Error("taskYaml must be a non-empty string");
24
+ }
25
+ return { kind: "yaml", taskYaml: input.taskYaml };
26
+ }
27
+ if (typeof input.task === "string") {
28
+ if (input.task.trim().length === 0) {
29
+ throw new Error("task path must be a non-empty string");
30
+ }
31
+ return { kind: "path", taskPath: resolveTaskPath(input.task, cwd) };
32
+ }
33
+ const parsed = parseTaskFile(input.task, "task");
34
+ if (parsed.ok) {
35
+ return { kind: "yaml", taskYaml: taskFileToYaml(parsed.value) };
36
+ }
37
+ throw new Error("task path, task object, or taskYaml is required");
38
+ }
@@ -0,0 +1,31 @@
1
+ import { type Server } from "node:http";
2
+ import type { AgentPort } from "../agent/port.js";
3
+ import { type ProviderAuthContext } from "../agent/providerAuth.js";
4
+ import { type RunStoreKind } from "../runstore/createStore.js";
5
+ import type { RunStore } from "../runstore/port.js";
6
+ import { RunManager } from "../runtime/runManager.js";
7
+ declare const DEFAULT_PORT = 3847;
8
+ export type UiServerOptions = {
9
+ agent: AgentPort;
10
+ cwd?: string;
11
+ agentDir?: string;
12
+ rootDir?: string;
13
+ store?: RunStore;
14
+ storeKind?: RunStoreKind;
15
+ port?: number;
16
+ host?: string;
17
+ uiDistDir?: string;
18
+ maxConcurrent?: number;
19
+ providerAuthContext?: ProviderAuthContext;
20
+ };
21
+ export declare function defaultUiDistDir(): string;
22
+ export declare function startUiServer(options: UiServerOptions): Promise<{
23
+ server: Server;
24
+ port: number;
25
+ host: string;
26
+ url: string;
27
+ mcpUrl: string;
28
+ manager: RunManager;
29
+ store: RunStore;
30
+ }>;
31
+ export { DEFAULT_PORT };