stageflow 0.3.0 → 0.9.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 (164) hide show
  1. package/README.md +44 -14
  2. package/dist/agent/activity.d.ts +2 -0
  3. package/dist/agent/cursorProvider.d.ts +1 -10
  4. package/dist/agent/cursorProvider.js +18 -10
  5. package/dist/agent/fakeAgent.js +11 -7
  6. package/dist/agent/piAdapter.d.ts +2 -1
  7. package/dist/agent/piAdapter.js +28 -25
  8. package/dist/agent/port.d.ts +6 -1
  9. package/dist/agent/port.js +3 -0
  10. package/dist/cli/artifactCommand.d.ts +10 -0
  11. package/dist/cli/artifactCommand.js +120 -0
  12. package/dist/cli/envelopeCommand.d.ts +32 -0
  13. package/dist/cli/envelopeCommand.js +285 -0
  14. package/dist/cli/exportRunCommand.d.ts +10 -0
  15. package/dist/cli/exportRunCommand.js +150 -0
  16. package/dist/cli/handoffFormat.d.ts +22 -0
  17. package/dist/cli/handoffFormat.js +61 -0
  18. package/dist/cli/runCommand.d.ts +5 -1
  19. package/dist/cli/runCommand.js +36 -5
  20. package/dist/cli/runOutput.d.ts +4 -1
  21. package/dist/cli/runOutput.js +36 -8
  22. package/dist/cli/runsCommand.d.ts +22 -0
  23. package/dist/cli/runsCommand.js +591 -0
  24. package/dist/cli/skillsCommand.d.ts +16 -0
  25. package/dist/cli/skillsCommand.js +459 -0
  26. package/dist/cli/validateOutput.d.ts +2 -2
  27. package/dist/cli/validateOutput.js +2 -2
  28. package/dist/cli.js +110 -5
  29. package/dist/config/loadPipeline.js +5 -1
  30. package/dist/config/normalizePipelineStageEntry.d.ts +2 -0
  31. package/dist/config/normalizePipelineStageEntry.js +25 -1
  32. package/dist/config/pipelineStageKeys.js +1 -1
  33. package/dist/config/resolveForkEmitContext.d.ts +2 -1
  34. package/dist/config/resolveForkEmitContext.js +23 -1
  35. package/dist/config/resolvePipelineDag.js +36 -4
  36. package/dist/envelope/check.d.ts +2 -0
  37. package/dist/envelope/check.js +51 -0
  38. package/dist/envelope/cloneForks.d.ts +2 -0
  39. package/dist/envelope/cloneForks.js +62 -0
  40. package/dist/envelope/forkChoice.js +1 -1
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.js +3 -1
  43. package/dist/mcp/catalogTools.d.ts +3 -0
  44. package/dist/mcp/catalogTools.js +187 -0
  45. package/dist/mcp/controlTools.d.ts +3 -0
  46. package/dist/mcp/controlTools.js +147 -0
  47. package/dist/mcp/deps.d.ts +11 -0
  48. package/dist/mcp/deps.js +1 -0
  49. package/dist/mcp/projectRun.d.ts +1 -40
  50. package/dist/mcp/projectRun.js +1 -64
  51. package/dist/mcp/resources.d.ts +5 -0
  52. package/dist/mcp/resources.js +45 -0
  53. package/dist/mcp/server.d.ts +16 -3
  54. package/dist/mcp/server.js +159 -3
  55. package/dist/mcp/toolResults.d.ts +7 -0
  56. package/dist/mcp/toolResults.js +6 -0
  57. package/dist/mcp/tools.d.ts +2 -7
  58. package/dist/mcp/tools.js +52 -103
  59. package/dist/mcp/waitRun.d.ts +46 -0
  60. package/dist/mcp/waitRun.js +146 -0
  61. package/dist/mcp/waitingGates.d.ts +12 -0
  62. package/dist/mcp/waitingGates.js +83 -0
  63. package/dist/package-meta.d.ts +1 -0
  64. package/dist/package-meta.js +1 -0
  65. package/dist/projection/projectRun.d.ts +44 -0
  66. package/dist/projection/projectRun.js +71 -0
  67. package/dist/prompt/priorEnvelope.d.ts +1 -1
  68. package/dist/prompt/priorEnvelope.js +4 -1
  69. package/dist/runstore/paths.js +2 -0
  70. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  71. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  72. package/dist/runstore/port.d.ts +13 -2
  73. package/dist/runstore/port.js +4 -1
  74. package/dist/runstore/runProjection.d.ts +1 -1
  75. package/dist/runstore/runProjection.js +14 -3
  76. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  77. package/dist/runstore/sqlite/SqliteRunStore.js +35 -3
  78. package/dist/runstore/stageInstanceId.d.ts +4 -0
  79. package/dist/runstore/stageInstanceId.js +23 -0
  80. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  81. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  82. package/dist/runstore/trackProjection.js +28 -12
  83. package/dist/runstore/workspaceLayout.d.ts +1 -0
  84. package/dist/runstore/workspaceLayout.js +1 -1
  85. package/dist/runtime/cloneSchedule.d.ts +18 -0
  86. package/dist/runtime/cloneSchedule.js +233 -0
  87. package/dist/runtime/envelopeRouting.d.ts +2 -1
  88. package/dist/runtime/envelopeRouting.js +80 -13
  89. package/dist/runtime/pipelineScheduler.d.ts +6 -2
  90. package/dist/runtime/pipelineScheduler.js +166 -30
  91. package/dist/runtime/resumeReconstruct.js +8 -3
  92. package/dist/runtime/runChangeBus.d.ts +18 -0
  93. package/dist/runtime/runChangeBus.js +83 -0
  94. package/dist/runtime/runManager.d.ts +7 -0
  95. package/dist/runtime/runManager.js +31 -2
  96. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  97. package/dist/runtime/runRetryCoordinator.js +4 -1
  98. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  99. package/dist/runtime/stageAttemptBootstrap.js +17 -9
  100. package/dist/runtime/stageRunner.d.ts +1 -0
  101. package/dist/runtime/stageRunner.js +17 -15
  102. package/dist/runtime/stageWorker.js +11 -4
  103. package/dist/server/bootstrap.d.ts +32 -0
  104. package/dist/server/bootstrap.js +56 -0
  105. package/dist/server/createHttpHost.d.ts +34 -0
  106. package/dist/server/createHttpHost.js +70 -0
  107. package/dist/server/http.d.ts +6 -13
  108. package/dist/server/http.js +295 -374
  109. package/dist/server/mcpHost.d.ts +22 -0
  110. package/dist/server/mcpHost.js +21 -0
  111. package/dist/server/operatorResults.d.ts +13 -0
  112. package/dist/server/operatorResults.js +22 -0
  113. package/dist/tools/emitStageEnvelope.d.ts +11 -2
  114. package/dist/tools/emitStageEnvelope.js +22 -6
  115. package/dist/types/envelope.d.ts +2 -0
  116. package/dist/types/forkChoice.d.ts +22 -0
  117. package/dist/types/pipeline.d.ts +8 -0
  118. package/dist/ui/assets/{index-DefBlEvN.css → index-C3N7MXAC.css} +1 -1
  119. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  120. package/dist/ui/index.html +2 -2
  121. package/package.json +2 -1
  122. package/skills/install-suite.sh +107 -0
  123. package/skills/stageflow/SKILL.md +26 -0
  124. package/skills/stageflow/references/control-surface.md +28 -0
  125. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  126. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  127. package/skills/stageflow-author/SKILL.md +58 -0
  128. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  129. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  130. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  131. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  132. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  133. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  134. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  135. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  136. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  137. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  138. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  139. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  140. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  141. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  142. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  143. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  144. package/skills/stageflow-delegate/SKILL.md +18 -0
  145. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  146. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  147. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  148. package/skills/stageflow-run/SKILL.md +182 -0
  149. package/skills/stageflow-run/references/mcp-call.md +17 -0
  150. package/skills/stageflow-run/references/native-question-ui.md +78 -0
  151. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  152. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  153. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  154. package/skills/stageflow-session-capture/SKILL.md +94 -0
  155. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  156. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  157. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  158. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  159. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  160. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  161. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  162. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  163. package/skills/stageflow-setup/SKILL.md +110 -0
  164. package/dist/ui/assets/index-CFSzDZje.js +0 -118
@@ -1,5 +1,5 @@
1
1
  import { resolveSkillByName } from "../config/listSkills.js";
2
- import { resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
2
+ import { resolveCloneEmitContext, resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
3
3
  import { buildCompletedEnvelopesFromRun, resolvePriorEnvelope, } from "./envelopeRouting.js";
4
4
  import { resumeSessionFilePath } from "./stageAttemptContext.js";
5
5
  import { buildStageRoots, rootsForStageWorker, withResolvedAuthPath, } from "./stageRoots.js";
@@ -39,40 +39,48 @@ async function openStageWithOperatorCatalog(agent, input, catalog) {
39
39
  };
40
40
  }
41
41
  }
42
- function resolveAttemptRoots(input) {
42
+ function resolveAttemptRoots(input, stageId) {
43
43
  const baseRoots = input.roots ??
44
44
  (input.workerRoots
45
- ? rootsForStageWorker(input.workspaceDir, input.stage.id, input.stage.model, input.checkoutRoot, input.attemptCtx)
46
- : buildStageRoots(input.workspaceDir, input.stage.id, input.checkoutRoot, input.attemptCtx));
45
+ ? rootsForStageWorker(input.workspaceDir, stageId, input.stage.model, input.checkoutRoot, input.attemptCtx)
46
+ : buildStageRoots(input.workspaceDir, stageId, input.checkoutRoot, input.attemptCtx));
47
47
  return input.factoryCwd !== undefined
48
48
  ? withResolvedAuthPath(baseRoots, input.factoryCwd)
49
49
  : baseRoots;
50
50
  }
51
51
  export async function openStageAttempt(input) {
52
+ const stageId = input.stageId ?? input.stage.id;
53
+ const definitionId = input.stage.id;
52
54
  const completedEnvelopes = input.completedEnvelopes ??
53
- (await buildCompletedEnvelopesFromRun(input.store, input.runId));
55
+ (await buildCompletedEnvelopesFromRun(input.store, input.runId, undefined, input.dag));
54
56
  const priorResult = await resolvePriorEnvelope({
55
57
  dag: input.dag,
56
- stageId: input.stage.id,
58
+ stageId,
57
59
  completedEnvelopes,
58
60
  store: input.store,
59
61
  runId: input.runId,
60
62
  });
61
63
  if (!priorResult.ok)
62
64
  return priorResult;
63
- const roots = resolveAttemptRoots(input);
65
+ const roots = resolveAttemptRoots(input, stageId);
64
66
  const attempt = input.attemptCtx?.attempt ?? 1;
65
67
  const resumeToken = input.resumeToken ??
66
- resumeSessionFilePath(input.workspaceDir, input.stage.id, attempt);
67
- const forkEmitContext = resolveForkEmitContext(input.dag, input.stage.id);
68
+ resumeSessionFilePath(input.workspaceDir, stageId, attempt);
69
+ const forkEmitContext = resolveForkEmitContext(input.dag, definitionId);
70
+ const cloneEmitContext = resolveCloneEmitContext(input.dag, definitionId);
68
71
  const opened = await openStageWithOperatorCatalog(input.agent, {
69
72
  roots,
70
73
  stage: input.stage,
74
+ stageId,
71
75
  task: input.task,
72
76
  priorEnvelope: priorResult.prior,
77
+ ...(priorResult.joinPriors !== undefined
78
+ ? { priorEnvelopes: priorResult.joinPriors }
79
+ : {}),
73
80
  resumeToken,
74
81
  onActivity: input.onActivity,
75
82
  forkEmitContext,
83
+ cloneEmitContext,
76
84
  }, input.operatorCatalog);
77
85
  if (!opened.ok)
78
86
  return opened;
@@ -44,6 +44,7 @@ export type RunStageOptions = {
44
44
  operatorCatalog?: OperatorCatalog;
45
45
  completedEnvelopes?: Map<string, StageEnvelope>;
46
46
  skipGates?: boolean;
47
+ stageId?: string;
47
48
  };
48
49
  /**
49
50
  * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
@@ -114,28 +114,29 @@ export async function runStageYieldLoop(options) {
114
114
  }
115
115
  export async function runStage(options) {
116
116
  const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, } = options;
117
+ const stageId = options.stageId ?? stage.id;
117
118
  const attemptOpt = attemptCtx?.eventOptions();
118
119
  const baseRoots = rootsOverride ??
119
- buildStageRoots(workspaceDir ?? store.getWorkspaceDir(runId), stage.id, checkoutRoot, attemptCtx);
120
+ buildStageRoots(workspaceDir ?? store.getWorkspaceDir(runId), stageId, checkoutRoot, attemptCtx);
120
121
  const roots = factoryCwd !== undefined
121
122
  ? withResolvedAuthPath(baseRoots, factoryCwd)
122
123
  : baseRoots;
123
124
  const attempt = attemptCtx?.attempt ?? 1;
124
- await ensureStageExecutionForAttempt(store, runId, stage.id, attempt);
125
- await store.ensureAttemptWorkspace(runId, stage.id, attempt);
125
+ await ensureStageExecutionForAttempt(store, runId, stageId, attempt);
126
+ await store.ensureAttemptWorkspace(runId, stageId, attempt);
126
127
  if (!skipStarted) {
127
128
  await appendLifecycleEventAndPatchExecution({
128
129
  store,
129
130
  runId,
130
- stageId: stage.id,
131
+ stageId,
131
132
  event: { event: "started" },
132
133
  attemptCtx,
133
134
  });
134
135
  }
135
- console.error(`Running stage ${stage.id} (${stage.model})...`);
136
+ console.error(`Running stage ${stageId} (${stage.model})...`);
136
137
  let activityChain = Promise.resolve();
137
138
  const enqueueActivity = (event) => {
138
- activityChain = activityChain.then(() => store.appendStageEvent(runId, stage.id, event, attemptOpt));
139
+ activityChain = activityChain.then(() => store.appendStageEvent(runId, stageId, event, attemptOpt));
139
140
  };
140
141
  let handle;
141
142
  if (existingHandle) {
@@ -147,12 +148,13 @@ export async function runStage(options) {
147
148
  store,
148
149
  runId,
149
150
  stage,
151
+ stageId,
150
152
  task,
151
153
  dag: dag ?? {
152
154
  nodes: [
153
- { id: stage.id, needs: null, ancestors: [], stageIndex: 0 },
155
+ { id: stageId, needs: null, ancestors: [], stageIndex: 0 },
154
156
  ],
155
- roots: [stage.id],
157
+ roots: [stageId],
156
158
  childrenOf: {},
157
159
  },
158
160
  checkoutRoot,
@@ -170,11 +172,11 @@ export async function runStage(options) {
170
172
  await appendLifecycleEventAndPatchExecution({
171
173
  store,
172
174
  runId,
173
- stageId: stage.id,
175
+ stageId,
174
176
  event: { event: "failed", reason: opened.reason },
175
177
  attemptCtx,
176
178
  });
177
- console.error(`Stage ${stage.id} failed: ${opened.reason}`);
179
+ console.error(`Stage ${stageId} failed: ${opened.reason}`);
178
180
  return { ok: false, reason: opened.reason };
179
181
  }
180
182
  handle = opened.handle;
@@ -184,7 +186,7 @@ export async function runStage(options) {
184
186
  outcome = await runStageYieldLoop({
185
187
  handle,
186
188
  runId,
187
- stageId: stage.id,
189
+ stageId,
188
190
  hitl,
189
191
  workerMode,
190
192
  store: workerMode ? store : undefined,
@@ -195,15 +197,15 @@ export async function runStage(options) {
195
197
  catch (err) {
196
198
  const reason = err instanceof Error ? err.message : String(err);
197
199
  await activityChain;
198
- hitl?.clearLiveWait(runId, stage.id);
200
+ hitl?.clearLiveWait(runId, stageId);
199
201
  await appendLifecycleEventAndPatchExecution({
200
202
  store,
201
203
  runId,
202
- stageId: stage.id,
204
+ stageId,
203
205
  event: { event: "failed", reason },
204
206
  attemptCtx,
205
207
  });
206
- console.error(`Stage ${stage.id} failed: ${reason}`);
208
+ console.error(`Stage ${stageId} failed: ${reason}`);
207
209
  await handle.close().catch(() => undefined);
208
210
  return { ok: false, reason };
209
211
  }
@@ -216,7 +218,7 @@ export async function runStage(options) {
216
218
  return await finalizeStageResult({
217
219
  store,
218
220
  runId,
219
- stageId: stage.id,
221
+ stageId,
220
222
  result: outcome,
221
223
  activityChain,
222
224
  attemptCtx,
@@ -1,4 +1,5 @@
1
1
  import { PiAgentAdapter } from "../agent/piAdapter.js";
2
+ import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
2
3
  import { createRunStore } from "../runstore/createStore.js";
3
4
  import { loadRunContext } from "./resumeReconstruct.js";
4
5
  import { bindPiAgentDirEnv, rootsForStageWorker, withResolvedAuthPath, } from "./stageRoots.js";
@@ -9,13 +10,15 @@ import { outcomeToWorkerResult, STAGE_WORKER_EXIT, } from "./stageWorkerProtocol
9
10
  export async function runStageWorker(input) {
10
11
  const store = createRunStore({ rootDir: input.rootDir });
11
12
  const { meta, task, loaded } = await loadRunContext(store, input.runId, input.rootDir);
12
- const stage = loaded.stages.find((s) => s.id === input.stageId);
13
+ const definitionId = definitionIdForInstance(meta.pipeline_dag, input.stageId);
14
+ const stage = loaded.stages.find((s) => s.id === definitionId);
13
15
  if (!stage) {
14
16
  return {
15
17
  ok: false,
16
18
  reason: `Stage ${input.stageId} not in pipeline ${meta.pipeline_id}`,
17
19
  };
18
20
  }
21
+ const dag = meta.pipeline_dag ?? loaded.dag;
19
22
  const agent = new PiAgentAdapter();
20
23
  const workspaceDir = store.getWorkspaceDir(input.runId);
21
24
  const checkoutRoot = meta.checkout_root;
@@ -32,8 +35,9 @@ export async function runStageWorker(input) {
32
35
  store,
33
36
  runId: input.runId,
34
37
  stage,
38
+ stageId: input.stageId,
35
39
  task,
36
- dag: loaded.dag,
40
+ dag,
37
41
  checkoutRoot,
38
42
  workspaceDir,
39
43
  factoryCwd: input.rootDir,
@@ -54,12 +58,14 @@ export async function runStageWorker(input) {
54
58
  store,
55
59
  runId: input.runId,
56
60
  stage,
61
+ stageId: input.stageId,
57
62
  task,
58
- dag: loaded.dag,
63
+ dag,
59
64
  checkoutRoot,
60
65
  workspaceDir,
61
66
  skipStarted: true,
62
67
  existingHandle: opened.handle,
68
+ workerMode: true,
63
69
  roots,
64
70
  attemptCtx,
65
71
  factoryCwd: input.rootDir,
@@ -73,8 +79,9 @@ export async function runStageWorker(input) {
73
79
  store,
74
80
  runId: input.runId,
75
81
  stage,
82
+ stageId: input.stageId,
76
83
  task,
77
- dag: loaded.dag,
84
+ dag,
78
85
  checkoutRoot,
79
86
  workspaceDir,
80
87
  workerMode: true,
@@ -0,0 +1,32 @@
1
+ import type { AgentPort } from "../agent/port.js";
2
+ import type { ProviderAuthContext } from "../agent/providerAuth.js";
3
+ import { type RunStoreKind } from "../runstore/createStore.js";
4
+ import type { RunStore } from "../runstore/port.js";
5
+ import { RunManager } from "../runtime/runManager.js";
6
+ import { type RunChangeBus } from "../runtime/runChangeBus.js";
7
+ import { type McpHttpHandler } from "../mcp/server.js";
8
+ export type StageflowHostOptions = {
9
+ agent: AgentPort;
10
+ cwd?: string;
11
+ agentDir?: string;
12
+ rootDir?: string;
13
+ store?: RunStore;
14
+ storeKind?: RunStoreKind;
15
+ maxConcurrent?: number;
16
+ providerAuthContext?: ProviderAuthContext;
17
+ mcpStateless?: boolean;
18
+ runChangeBus?: RunChangeBus;
19
+ };
20
+ export type StageflowHostBootstrap = {
21
+ cwd: string;
22
+ agentDir: string;
23
+ rootDir: string;
24
+ isGitProject: boolean;
25
+ store: RunStore;
26
+ manager: RunManager;
27
+ runChangeBus: RunChangeBus;
28
+ mcpStateless: boolean;
29
+ providerAuthContext: ProviderAuthContext | undefined;
30
+ mcpHandler: McpHttpHandler;
31
+ };
32
+ export declare function bootstrapStageflowHost(options: StageflowHostOptions): Promise<StageflowHostBootstrap>;
@@ -0,0 +1,56 @@
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
+ import { createRunStore } from "../runstore/createStore.js";
3
+ import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
4
+ import { findProjectRoot } from "../project/findProjectRoot.js";
5
+ import { RunManager } from "../runtime/runManager.js";
6
+ import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped, wrapRunStoreWithChangeBus, } from "../runtime/runChangeBus.js";
7
+ import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
8
+ export async function bootstrapStageflowHost(options) {
9
+ const invocationCwd = options.cwd ?? process.cwd();
10
+ const ctx = await resolveStageflowContext(invocationCwd);
11
+ const cwd = ctx.invocationCwd;
12
+ const agentDir = options.agentDir ?? getAgentDir();
13
+ const rootDir = options.rootDir ?? ctx.projectRoot;
14
+ const isGitProject = options.rootDir !== undefined
15
+ ? findProjectRoot(rootDir) !== null
16
+ : ctx.isGitProject;
17
+ const rawStore = options.store ??
18
+ createRunStore({ rootDir, kind: options.storeKind });
19
+ const boundBus = isRunStoreWrapped(rawStore)
20
+ ? getRunChangeBusFromWrappedStore(rawStore)
21
+ : undefined;
22
+ if (boundBus !== undefined &&
23
+ options.runChangeBus !== undefined &&
24
+ options.runChangeBus !== boundBus) {
25
+ throw new Error("runChangeBus does not match the bus already bound to the provided store");
26
+ }
27
+ const runChangeBus = options.runChangeBus ?? boundBus ?? createRunChangeBus();
28
+ const store = wrapRunStoreWithChangeBus(rawStore, runChangeBus);
29
+ const manager = new RunManager({
30
+ agent: options.agent,
31
+ cwd,
32
+ projectRoot: rootDir,
33
+ isGitProject,
34
+ store,
35
+ maxConcurrent: options.maxConcurrent,
36
+ operatorCatalog: { cwd, agentDir },
37
+ });
38
+ await manager.attachWaitingStages();
39
+ await manager.reconcileOrphanedStages();
40
+ const mcpStateless = resolveMcpStateless({
41
+ mcpStateless: options.mcpStateless,
42
+ });
43
+ const mcpHandler = createMcpHttpHandler({ manager, store, cwd, runChangeBus }, { mcpStateless });
44
+ return {
45
+ cwd,
46
+ agentDir,
47
+ rootDir,
48
+ isGitProject,
49
+ store,
50
+ manager,
51
+ runChangeBus,
52
+ mcpStateless,
53
+ providerAuthContext: options.providerAuthContext,
54
+ mcpHandler,
55
+ };
56
+ }
@@ -0,0 +1,34 @@
1
+ import { type IncomingMessage, type ServerResponse, type Server } from "node:http";
2
+ import type { RunStore } from "../runstore/port.js";
3
+ import type { RunManager } from "../runtime/runManager.js";
4
+ import type { RunChangeBus } from "../runtime/runChangeBus.js";
5
+ import type { StageflowHostBootstrap } from "./bootstrap.js";
6
+ export declare const DEFAULT_PORT = 3847;
7
+ export declare function json(res: ServerResponse, status: number, body: unknown): void;
8
+ export type HttpHostRouteContext = {
9
+ req: IncomingMessage;
10
+ res: ServerResponse;
11
+ url: URL;
12
+ pathname: string;
13
+ method: string;
14
+ json: typeof json;
15
+ boot: StageflowHostBootstrap;
16
+ };
17
+ export type CreateHttpHostOptions = {
18
+ boot: StageflowHostBootstrap;
19
+ host: string;
20
+ port: number;
21
+ routes: (ctx: HttpHostRouteContext) => Promise<boolean | void>;
22
+ };
23
+ export type HttpHostEnvelope = {
24
+ server: Server;
25
+ port: number;
26
+ host: string;
27
+ url: string;
28
+ mcpUrl: string;
29
+ manager: RunManager;
30
+ store: RunStore;
31
+ runChangeBus: RunChangeBus;
32
+ mcpStateless: boolean;
33
+ };
34
+ export declare function createHttpHost(options: CreateHttpHostOptions): Promise<HttpHostEnvelope>;
@@ -0,0 +1,70 @@
1
+ import { createServer, } from "node:http";
2
+ import { localhostHostValidation, localhostOriginValidation, } from "@modelcontextprotocol/node";
3
+ export const DEFAULT_PORT = 3847;
4
+ export function json(res, status, body) {
5
+ const payload = JSON.stringify(body);
6
+ res.writeHead(status, {
7
+ "Content-Type": "application/json; charset=utf-8",
8
+ "Content-Length": Buffer.byteLength(payload),
9
+ });
10
+ res.end(payload);
11
+ }
12
+ export async function createHttpHost(options) {
13
+ const { boot, host, port, routes } = options;
14
+ const validateMcpHost = localhostHostValidation();
15
+ const validateMcpOrigin = localhostOriginValidation();
16
+ const server = createServer(async (req, res) => {
17
+ const method = req.method ?? "GET";
18
+ const url = new URL(req.url ?? "/", `http://${host}:${port}`);
19
+ const pathname = url.pathname;
20
+ if (pathname === "/mcp") {
21
+ if (!validateMcpHost(req, res) || !validateMcpOrigin(req, res)) {
22
+ return;
23
+ }
24
+ try {
25
+ await boot.mcpHandler.handle(req, res);
26
+ }
27
+ catch (err) {
28
+ if (!res.headersSent) {
29
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
30
+ res.end(err instanceof Error ? err.message : String(err));
31
+ }
32
+ }
33
+ return;
34
+ }
35
+ const handled = await routes({
36
+ req,
37
+ res,
38
+ url,
39
+ pathname,
40
+ method,
41
+ json,
42
+ boot,
43
+ });
44
+ if (handled)
45
+ return;
46
+ json(res, 404, { error: "Not found" });
47
+ });
48
+ server.requestTimeout = 0;
49
+ server.on("close", () => {
50
+ void boot.mcpHandler.close();
51
+ });
52
+ await new Promise((resolve, reject) => {
53
+ server.listen(port, host, () => resolve());
54
+ server.on("error", reject);
55
+ });
56
+ const address = server.address();
57
+ const boundPort = address && typeof address !== "string" ? address.port : port;
58
+ const url = `http://${host}:${boundPort}`;
59
+ return {
60
+ server,
61
+ port: boundPort,
62
+ host,
63
+ url,
64
+ mcpUrl: `${url}/mcp`,
65
+ manager: boot.manager,
66
+ store: boot.store,
67
+ runChangeBus: boot.runChangeBus,
68
+ mcpStateless: boot.mcpStateless,
69
+ };
70
+ }
@@ -1,10 +1,9 @@
1
- import { type Server } from "node:http";
2
1
  import type { AgentPort } from "../agent/port.js";
3
2
  import { type ProviderAuthContext } from "../agent/providerAuth.js";
4
- import { type RunStoreKind } from "../runstore/createStore.js";
3
+ import type { RunStoreKind } from "../runstore/createStore.js";
5
4
  import type { RunStore } from "../runstore/port.js";
6
- import { RunManager } from "../runtime/runManager.js";
7
- declare const DEFAULT_PORT = 3847;
5
+ import type { RunChangeBus } from "../runtime/runChangeBus.js";
6
+ import { DEFAULT_PORT, type HttpHostEnvelope } from "./createHttpHost.js";
8
7
  export type UiServerOptions = {
9
8
  agent: AgentPort;
10
9
  cwd?: string;
@@ -17,15 +16,9 @@ export type UiServerOptions = {
17
16
  uiDistDir?: string;
18
17
  maxConcurrent?: number;
19
18
  providerAuthContext?: ProviderAuthContext;
19
+ mcpStateless?: boolean;
20
+ runChangeBus?: RunChangeBus;
20
21
  };
21
22
  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
- }>;
23
+ export declare function startUiServer(options: UiServerOptions): Promise<HttpHostEnvelope>;
31
24
  export { DEFAULT_PORT };