stageflow 0.9.0 → 0.12.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 (151) hide show
  1. package/README.md +1 -0
  2. package/dist/agent/activity.d.ts +9 -0
  3. package/dist/agent/agentBackend.d.ts +32 -0
  4. package/dist/agent/agentBackend.js +38 -0
  5. package/dist/agent/claudeActivity.d.ts +15 -0
  6. package/dist/agent/claudeActivity.js +58 -0
  7. package/dist/agent/claudeAdapter.d.ts +5 -0
  8. package/dist/agent/claudeAdapter.js +301 -0
  9. package/dist/agent/claudeSession.d.ts +18 -0
  10. package/dist/agent/claudeSession.js +54 -0
  11. package/dist/agent/claudeTools.d.ts +89 -0
  12. package/dist/agent/claudeTools.js +135 -0
  13. package/dist/agent/fakeAgent.js +24 -4
  14. package/dist/agent/piAdapter.d.ts +6 -3
  15. package/dist/agent/piAdapter.js +129 -21
  16. package/dist/agent/port.d.ts +50 -1
  17. package/dist/agent/resolveAgentPort.d.ts +26 -0
  18. package/dist/agent/resolveAgentPort.js +39 -0
  19. package/dist/cli/runCommand.js +2 -2
  20. package/dist/cli/runsCommand.d.ts +1 -1
  21. package/dist/cli/runsCommand.js +184 -2
  22. package/dist/cli.d.ts +1 -1
  23. package/dist/cli.js +11 -5
  24. package/dist/config/browseCatalog.js +1 -1
  25. package/dist/config/createPipeline.d.ts +2 -1
  26. package/dist/config/createPipeline.js +34 -9
  27. package/dist/config/createStage.js +14 -9
  28. package/dist/config/loadPipeline.js +10 -1
  29. package/dist/config/loadStage.js +138 -2
  30. package/dist/config/loadStageflowManifest.js +14 -1
  31. package/dist/config/mergePipelineIncludes.d.ts +1 -0
  32. package/dist/config/mergePipelineIncludes.js +14 -1
  33. package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
  34. package/dist/config/normalizePipelineStageEntry.js +49 -6
  35. package/dist/config/parseCompletionContract.d.ts +8 -0
  36. package/dist/config/parseCompletionContract.js +218 -0
  37. package/dist/config/parsePreEmitChecks.d.ts +3 -0
  38. package/dist/config/parsePreEmitChecks.js +65 -0
  39. package/dist/config/pipelineNeeds.d.ts +19 -0
  40. package/dist/config/pipelineNeeds.js +151 -0
  41. package/dist/config/pipelineStageKeys.js +16 -1
  42. package/dist/config/resolveForkEmitContext.d.ts +6 -2
  43. package/dist/config/resolveForkEmitContext.js +9 -2
  44. package/dist/config/resolvePipelineDag.d.ts +2 -1
  45. package/dist/config/resolvePipelineDag.js +182 -39
  46. package/dist/config/validateCatalog.d.ts +1 -1
  47. package/dist/config/validateCompletionContract.d.ts +4 -0
  48. package/dist/config/validateCompletionContract.js +52 -0
  49. package/dist/envelope/check.d.ts +1 -1
  50. package/dist/envelope/check.js +86 -13
  51. package/dist/envelope/cloneForks.d.ts +1 -1
  52. package/dist/envelope/cloneForks.js +24 -9
  53. package/dist/envelope/feedbackLoop.d.ts +4 -0
  54. package/dist/envelope/feedbackLoop.js +31 -0
  55. package/dist/envelope/payloadSchema.d.ts +7 -1
  56. package/dist/envelope/payloadSchema.js +170 -13
  57. package/dist/envelope/preEmitChecks.d.ts +29 -0
  58. package/dist/envelope/preEmitChecks.js +57 -0
  59. package/dist/hitl/qaTrail.d.ts +25 -0
  60. package/dist/hitl/qaTrail.js +51 -0
  61. package/dist/mcp/catalogTools.js +4 -2
  62. package/dist/mcp/controlTools.js +72 -0
  63. package/dist/mcp/waitingGates.d.ts +2 -1
  64. package/dist/mcp/waitingGates.js +17 -1
  65. package/dist/package-meta.d.ts +1 -1
  66. package/dist/package-meta.js +1 -1
  67. package/dist/projection/projectRun.d.ts +4 -1
  68. package/dist/projection/projectRun.js +7 -0
  69. package/dist/prompt/feedbackLoopContext.d.ts +2 -0
  70. package/dist/prompt/feedbackLoopContext.js +17 -0
  71. package/dist/prompt/priorEnvelope.d.ts +2 -2
  72. package/dist/prompt/priorEnvelope.js +4 -1
  73. package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
  74. package/dist/runstore/pipelineDagSnapshot.js +51 -11
  75. package/dist/runstore/port.d.ts +185 -3
  76. package/dist/runstore/port.js +42 -3
  77. package/dist/runstore/runProjection.d.ts +7 -3
  78. package/dist/runstore/runProjection.js +30 -15
  79. package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
  80. package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
  81. package/dist/runstore/sqlite/schema.d.ts +1 -1
  82. package/dist/runstore/sqlite/schema.js +99 -0
  83. package/dist/runstore/stageInstanceId.d.ts +1 -1
  84. package/dist/runstore/stageInstanceId.js +3 -2
  85. package/dist/runstore/trackProjection.js +59 -27
  86. package/dist/runstore/verificationHistory.d.ts +19 -0
  87. package/dist/runstore/verificationHistory.js +30 -0
  88. package/dist/runtime/cloneSchedule.d.ts +40 -4
  89. package/dist/runtime/cloneSchedule.js +212 -28
  90. package/dist/runtime/completionCheckRunner.d.ts +137 -0
  91. package/dist/runtime/completionCheckRunner.js +365 -0
  92. package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
  93. package/dist/runtime/completionCheckoutBaseline.js +44 -0
  94. package/dist/runtime/envelopeRouting.d.ts +10 -3
  95. package/dist/runtime/envelopeRouting.js +146 -5
  96. package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
  97. package/dist/runtime/feedbackLoopCoordinator.js +446 -0
  98. package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
  99. package/dist/runtime/feedbackLoopDecision.js +207 -0
  100. package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
  101. package/dist/runtime/feedbackLoopRoute.js +13 -0
  102. package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
  103. package/dist/runtime/feedbackLoopSchedule.js +72 -0
  104. package/dist/runtime/forkGeneration.d.ts +72 -0
  105. package/dist/runtime/forkGeneration.js +295 -0
  106. package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
  107. package/dist/runtime/gitCheckoutCapability.js +97 -0
  108. package/dist/runtime/manualRecoveryState.d.ts +40 -0
  109. package/dist/runtime/manualRecoveryState.js +73 -0
  110. package/dist/runtime/pipelineScheduler.d.ts +13 -1
  111. package/dist/runtime/pipelineScheduler.js +328 -45
  112. package/dist/runtime/replayLifecycle.d.ts +107 -0
  113. package/dist/runtime/replayLifecycle.js +168 -0
  114. package/dist/runtime/runManager.d.ts +54 -1
  115. package/dist/runtime/runManager.js +471 -6
  116. package/dist/runtime/runRetryCoordinator.d.ts +6 -1
  117. package/dist/runtime/runRetryCoordinator.js +43 -9
  118. package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
  119. package/dist/runtime/stageAttemptBootstrap.js +79 -1
  120. package/dist/runtime/stageProcessLauncher.d.ts +1 -1
  121. package/dist/runtime/stageRecovery.js +1 -1
  122. package/dist/runtime/stageRunner.d.ts +4 -1
  123. package/dist/runtime/stageRunner.js +29 -3
  124. package/dist/runtime/stageWorker.js +69 -2
  125. package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
  126. package/dist/runtime/verifiedStageExecution.d.ts +29 -0
  127. package/dist/runtime/verifiedStageExecution.js +119 -0
  128. package/dist/server/bootstrap.js +1 -0
  129. package/dist/server/http.js +114 -0
  130. package/dist/server/operatorResults.js +2 -0
  131. package/dist/tools/askOperator.d.ts +161 -5
  132. package/dist/tools/askOperator.js +58 -25
  133. package/dist/tools/emitStageEnvelope.d.ts +12 -12
  134. package/dist/tools/emitStageEnvelope.js +112 -24
  135. package/dist/types/completion.d.ts +43 -0
  136. package/dist/types/completion.js +1 -0
  137. package/dist/types/envelope.d.ts +18 -0
  138. package/dist/types/forkChoice.d.ts +9 -4
  139. package/dist/types/forkChoice.js +1 -1
  140. package/dist/types/pipeline.d.ts +39 -2
  141. package/dist/types/preEmitCheck.d.ts +18 -0
  142. package/dist/types/preEmitCheck.js +1 -0
  143. package/dist/types/stage.d.ts +13 -0
  144. package/dist/types/stageflowManifest.d.ts +2 -0
  145. package/dist/ui/assets/index-CXGz4nL7.js +118 -0
  146. package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
  147. package/dist/ui/index.html +2 -2
  148. package/package.json +2 -1
  149. package/skills/stageflow-author/references/catalog-mapping.md +26 -2
  150. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  151. package/dist/ui/assets/index-CSoGDA3A.js +0 -118
@@ -1,6 +1,6 @@
1
1
  import type { RunStore } from "../runstore/port.js";
2
2
  import { RunManager } from "../runtime/runManager.js";
3
- export declare const RUNS_USAGE = "Usage:\n sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]\n sf runs show --run <runId> [--from <sf-run.json>] [--json]\n sf runs waiting [--run <runId>] [--json]\n sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]\n sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]\n sf runs retry --run <runId> --stage <stageId> [--json]\n sf runs abandon --run <runId> --stage <stageId> [--json]\n sf runs rerun --run <runId> [--json]";
3
+ export declare const RUNS_USAGE = "Usage:\n sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]\n sf runs show --run <runId> [--from <sf-run.json>] [--json]\n sf runs verify --run <runId> --stage <stageId> [--json]\n sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]\n sf runs waiting [--run <runId>] [--json]\n sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]\n sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]\n sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]\n sf runs retry --run <runId> --stage <stageId> [--json]\n sf runs abandon --run <runId> --stage <stageId> [--json]\n sf runs rerun --run <runId> [--json]";
4
4
  export type RunsCommandIo = {
5
5
  log: (line: string) => void;
6
6
  error: (line: string) => void;
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- import { PiAgentAdapter } from "../agent/piAdapter.js";
3
+ import { resolveGlobalOnlyAgentPort } from "../agent/resolveAgentPort.js";
4
4
  import { projectRun } from "../projection/projectRun.js";
5
5
  import { waitRun } from "../mcp/waitRun.js";
6
6
  import { projectWaitingGates } from "../mcp/waitingGates.js";
@@ -8,6 +8,7 @@ import { resolveOperatorCatalog } from "./operatorCatalog.js";
8
8
  import { completeCliRun } from "./runCommand.js";
9
9
  import { reportCliRun } from "./runOutput.js";
10
10
  import { createRunStore } from "../runstore/createStore.js";
11
+ import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
11
12
  import { RunManager } from "../runtime/runManager.js";
12
13
  import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
13
14
  import { DEFAULT_PORT } from "../server/createHttpHost.js";
@@ -16,9 +17,12 @@ import { AskOperatorError, parseAskOperatorAnswer, } from "../tools/askOperator.
16
17
  export const RUNS_USAGE = `Usage:
17
18
  sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
18
19
  sf runs show --run <runId> [--from <sf-run.json>] [--json]
20
+ sf runs verify --run <runId> --stage <stageId> [--json]
21
+ sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]
19
22
  sf runs waiting [--run <runId>] [--json]
20
23
  sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
21
24
  sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
25
+ sf runs feedback-decide --run <runId> --stage <sourceStageId> [--loop <loopId>] --decision extend|continue|abandon [--reason <text>] [--json]
22
26
  sf runs retry --run <runId> --stage <stageId> [--json]
23
27
  sf runs abandon --run <runId> --stage <stageId> [--json]
24
28
  sf runs rerun --run <runId> [--json]`;
@@ -41,6 +45,16 @@ const LIST_FLAGS = new Set([
41
45
  "-h",
42
46
  ]);
43
47
  const SHOW_FLAGS = new Set(["--run", "--from", "--json", "--help", "-h"]);
48
+ const VERIFY_FLAGS = new Set(["--run", "--stage", "--json", "--help", "-h"]);
49
+ const RECOVER_FLAGS = new Set([
50
+ "--run",
51
+ "--stage",
52
+ "--guidance",
53
+ "--stop",
54
+ "--json",
55
+ "--help",
56
+ "-h",
57
+ ]);
44
58
  const WAITING_FLAGS = new Set(["--run", "--json", "--help", "-h"]);
45
59
  const WAIT_FLAGS = new Set([
46
60
  "--run",
@@ -55,6 +69,17 @@ const ANSWER_FLAGS = new Set([
55
69
  "--run",
56
70
  "--stage",
57
71
  "--answer",
72
+ "--guidance",
73
+ "--json",
74
+ "--help",
75
+ "-h",
76
+ ]);
77
+ const FEEDBACK_DECIDE_FLAGS = new Set([
78
+ "--run",
79
+ "--stage",
80
+ "--loop",
81
+ "--decision",
82
+ "--reason",
58
83
  "--json",
59
84
  "--help",
60
85
  "-h",
@@ -72,6 +97,10 @@ const VALUE_FLAGS = new Set([
72
97
  "--timeout-ms",
73
98
  "--stage",
74
99
  "--answer",
100
+ "--loop",
101
+ "--decision",
102
+ "--reason",
103
+ "--guidance",
75
104
  ]);
76
105
  function flagsFor(subcommand) {
77
106
  switch (subcommand) {
@@ -79,12 +108,18 @@ function flagsFor(subcommand) {
79
108
  return LIST_FLAGS;
80
109
  case "show":
81
110
  return SHOW_FLAGS;
111
+ case "verify":
112
+ return VERIFY_FLAGS;
113
+ case "recover":
114
+ return RECOVER_FLAGS;
82
115
  case "waiting":
83
116
  return WAITING_FLAGS;
84
117
  case "wait":
85
118
  return WAIT_FLAGS;
86
119
  case "answer":
87
120
  return ANSWER_FLAGS;
121
+ case "feedback-decide":
122
+ return FEEDBACK_DECIDE_FLAGS;
88
123
  case "retry":
89
124
  return RETRY_FLAGS;
90
125
  case "abandon":
@@ -118,6 +153,11 @@ function parseRunsArgs(args) {
118
153
  let timeoutMs;
119
154
  let stageId;
120
155
  let answer;
156
+ let guidance;
157
+ let stop = false;
158
+ let loopId;
159
+ let decision;
160
+ let reason;
121
161
  for (let i = 1; i < args.length; i++) {
122
162
  const arg = args[i];
123
163
  if (arg === "--help" || arg === "-h") {
@@ -126,6 +166,11 @@ function parseRunsArgs(args) {
126
166
  else if (arg === "--json") {
127
167
  json = true;
128
168
  }
169
+ else if (arg === "--stop") {
170
+ if (!allowed.has(arg))
171
+ throw new Error(`Unknown flag: ${arg}`);
172
+ stop = true;
173
+ }
129
174
  else if (VALUE_FLAGS.has(arg)) {
130
175
  if (!allowed.has(arg)) {
131
176
  throw new Error(`Unknown flag: ${arg}`);
@@ -152,6 +197,14 @@ function parseRunsArgs(args) {
152
197
  stageId = value;
153
198
  else if (arg === "--answer")
154
199
  answer = value;
200
+ else if (arg === "--guidance")
201
+ guidance = value;
202
+ else if (arg === "--loop")
203
+ loopId = value;
204
+ else if (arg === "--decision")
205
+ decision = value;
206
+ else if (arg === "--reason")
207
+ reason = value;
155
208
  }
156
209
  else if (arg.startsWith("-")) {
157
210
  throw new Error(`Unknown flag: ${arg}`);
@@ -173,6 +226,11 @@ function parseRunsArgs(args) {
173
226
  timeoutMs,
174
227
  stageId,
175
228
  answer,
229
+ guidance,
230
+ stop,
231
+ loopId,
232
+ decision,
233
+ reason,
176
234
  };
177
235
  }
178
236
  function resolveRunIdFromFile(fromPath, cwd) {
@@ -275,6 +333,7 @@ export async function runRunsCommand(args, options = {}) {
275
333
  return resolvedStore;
276
334
  };
277
335
  const mutatingIo = out;
336
+ const agent = await resolveGlobalOnlyAgentPort(projectRoot);
278
337
  const buildManager = () => {
279
338
  const store = getStore();
280
339
  if (options.createManager)
@@ -285,7 +344,7 @@ export async function runRunsCommand(args, options = {}) {
285
344
  defaultCwd: cwd,
286
345
  });
287
346
  return new RunManager({
288
- agent: new PiAgentAdapter(),
347
+ agent,
289
348
  store,
290
349
  cwd,
291
350
  projectRoot,
@@ -359,6 +418,32 @@ export async function runRunsCommand(args, options = {}) {
359
418
  return 1;
360
419
  }
361
420
  }
421
+ case "verify": {
422
+ if (!parsed.runId)
423
+ return usageError(out, "Missing --run");
424
+ if (!parsed.stageId)
425
+ return usageError(out, "Missing --stage");
426
+ try {
427
+ const history = await readStageVerificationHistory(getStore(), parsed.runId, parsed.stageId);
428
+ if (parsed.json) {
429
+ printJson(out, history);
430
+ }
431
+ else {
432
+ for (const attempt of history.attempts) {
433
+ const checks = attempt.checks
434
+ .map((check) => `${check.check_id}:${check.status}`)
435
+ .join(", ");
436
+ out.log(`attempt ${attempt.attempt}\t${attempt.status}${checks ? `\t${checks}` : ""}`);
437
+ }
438
+ }
439
+ return 0;
440
+ }
441
+ catch (err) {
442
+ const message = err instanceof Error ? err.message : String(err);
443
+ out.error(message);
444
+ return 1;
445
+ }
446
+ }
362
447
  case "waiting": {
363
448
  const waiting = await projectWaitingGates(getStore(), {
364
449
  runId: parsed.runId,
@@ -512,6 +597,61 @@ export async function runRunsCommand(args, options = {}) {
512
597
  }
513
598
  return 0;
514
599
  }
600
+ case "feedback-decide": {
601
+ const blocked = await guardHost();
602
+ if (blocked !== undefined)
603
+ return blocked;
604
+ if (!parsed.runId) {
605
+ return usageError(out, "Missing --run");
606
+ }
607
+ if (!parsed.stageId) {
608
+ return usageError(out, "Missing --stage");
609
+ }
610
+ if (!parsed.decision) {
611
+ return usageError(out, "Missing --decision");
612
+ }
613
+ if (parsed.decision !== "extend" &&
614
+ parsed.decision !== "continue" &&
615
+ parsed.decision !== "abandon") {
616
+ const message = "--decision must be extend, continue, or abandon";
617
+ if (parsed.json) {
618
+ printJson(out, { error: message, status: 400 });
619
+ }
620
+ else {
621
+ out.error(message);
622
+ }
623
+ return 1;
624
+ }
625
+ const manager = buildManager();
626
+ const result = await manager.decideFeedbackLoop(parsed.runId, parsed.stageId, {
627
+ decision: parsed.decision,
628
+ ...(parsed.loopId !== undefined ? { loopId: parsed.loopId } : {}),
629
+ ...(parsed.reason !== undefined ? { reason: parsed.reason } : {}),
630
+ });
631
+ if (!result.ok) {
632
+ const payload = { error: result.reason };
633
+ if (result.status !== undefined)
634
+ payload.status = result.status;
635
+ if (parsed.json) {
636
+ printJson(out, payload);
637
+ }
638
+ else {
639
+ out.error(result.reason);
640
+ }
641
+ return 1;
642
+ }
643
+ if (parsed.json) {
644
+ printJson(out, {
645
+ ok: true,
646
+ effect: result.effect,
647
+ loopId: result.loopId,
648
+ });
649
+ }
650
+ else {
651
+ out.log(`ok\t${result.effect}\t${result.loopId}`);
652
+ }
653
+ return 0;
654
+ }
515
655
  case "retry": {
516
656
  const blocked = await guardHost();
517
657
  if (blocked !== undefined)
@@ -535,6 +675,48 @@ export async function runRunsCommand(args, options = {}) {
535
675
  }
536
676
  return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
537
677
  }
678
+ case "recover": {
679
+ const blocked = await guardHost();
680
+ if (blocked !== undefined)
681
+ return blocked;
682
+ if (!parsed.runId || !parsed.stageId) {
683
+ return usageError(out, "Missing --run and/or --stage");
684
+ }
685
+ const manager = buildManager();
686
+ if (parsed.stop) {
687
+ if (parsed.guidance !== undefined) {
688
+ return usageError(out, "--guidance cannot be used with --stop");
689
+ }
690
+ const result = await manager.stopManualRecovery(parsed.runId, parsed.stageId);
691
+ if (!result.ok) {
692
+ if (parsed.json) {
693
+ printJson(out, { error: result.reason, status: result.status });
694
+ }
695
+ else {
696
+ out.error(result.reason);
697
+ }
698
+ return 1;
699
+ }
700
+ if (parsed.json) {
701
+ printJson(out, result);
702
+ }
703
+ else {
704
+ out.log(`${result.runId}\t${result.stageId}\tstopped`);
705
+ }
706
+ return 0;
707
+ }
708
+ const result = await manager.recoverManualStageUntilStop(parsed.runId, parsed.stageId, parsed.guidance);
709
+ if (!result.ok) {
710
+ if (parsed.json) {
711
+ printJson(out, { ...mapRetryStageFailure(result), status: result.status });
712
+ }
713
+ else {
714
+ out.error(result.reason);
715
+ }
716
+ return 1;
717
+ }
718
+ return reportCliRun({ kind: "completion", result: result.pipeline }, { json: parsed.json, io: mutatingIo });
719
+ }
538
720
  case "abandon": {
539
721
  const blocked = await guardHost();
540
722
  if (blocked !== undefined)
package/dist/cli.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { OperatorCatalog } from "./runtime/stageAttemptBootstrap.js";
3
3
  export declare function parseRunStageArgs(argv: string[]): {
4
4
  runId: string;
5
5
  stageId: string;
6
- mode?: "run" | "resume";
6
+ mode?: "run" | "resume" | "feedback_resume" | "new_session";
7
7
  resumeAnswer?: unknown;
8
8
  attempt?: number;
9
9
  sessionFilePath?: string;
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { PiAgentAdapter } from "./agent/piAdapter.js";
6
+ import { globalAgentBackendFromManifest, resolveAgentPort } from "./agent/resolveAgentPort.js";
7
7
  import { ARTIFACT_USAGE, runArtifactCommand } from "./cli/artifactCommand.js";
8
8
  import { ENVELOPE_USAGE, runEnvelopeCommand, } from "./cli/envelopeCommand.js";
9
9
  import { EXPORT_RUN_USAGE, runExportRunCommand, } from "./cli/exportRunCommand.js";
@@ -31,6 +31,8 @@ const USAGE = `Usage:
31
31
  sf export-run --run <runId> [--from <sf-run.json>] [--out <file>]
32
32
  sf runs list [--status created|running|succeeded|failed] [--since <iso>] [--pipeline <id-or-path>] [--json]
33
33
  sf runs show --run <runId> [--from <sf-run.json>] [--json]
34
+ sf runs verify --run <runId> --stage <stageId> [--json]
35
+ sf runs recover --run <runId> --stage <stageId> [--guidance <text>] [--stop] [--json]
34
36
  sf runs waiting [--run <runId>] [--json]
35
37
  sf runs wait --run <runId> [--from <sf-run.json>] [--until any|waiting|terminal] [--timeout-ms <n>] [--json]
36
38
  sf runs answer --run <runId> --stage <stageId> [--answer '<json>'] [--json]
@@ -154,8 +156,11 @@ export function parseRunStageArgs(argv) {
154
156
  }
155
157
  else if (argv[i] === "--mode") {
156
158
  const raw = argv[++i];
157
- if (raw !== "run" && raw !== "resume") {
158
- throw new Error("--mode must be run or resume");
159
+ if (raw !== "run" &&
160
+ raw !== "resume" &&
161
+ raw !== "feedback_resume" &&
162
+ raw !== "new_session") {
163
+ throw new Error("--mode must be run, resume, feedback_resume, or new_session");
159
164
  }
160
165
  mode = raw;
161
166
  }
@@ -314,12 +319,13 @@ async function main(argv) {
314
319
  });
315
320
  }
316
321
  const store = createRunStore({ rootDir: ctx.projectRoot });
322
+ const globalAgent = globalAgentBackendFromManifest(ctx.manifest);
317
323
  if (parsed.command === "ui") {
318
324
  const mcpStateless = resolveMcpStateless({
319
325
  mcpStateless: parsed.mcpStateless,
320
326
  });
321
327
  const { url, mcpUrl } = await startUiServer({
322
- agent: new PiAgentAdapter(),
328
+ agent: resolveAgentPort({ global: globalAgent }),
323
329
  store,
324
330
  cwd: ctx.invocationCwd,
325
331
  rootDir: ctx.projectRoot,
@@ -337,7 +343,7 @@ async function main(argv) {
337
343
  mcpStateless: parsed.mcpStateless,
338
344
  });
339
345
  const { mcpUrl } = await startMcpServer({
340
- agent: new PiAgentAdapter(),
346
+ agent: resolveAgentPort({ global: globalAgent }),
341
347
  store,
342
348
  cwd: ctx.invocationCwd,
343
349
  rootDir: ctx.projectRoot,
@@ -14,7 +14,7 @@ function repoRelPath(projectRoot, absPath) {
14
14
  function mapStageListing(stage, source, projectRoot) {
15
15
  const listing = {
16
16
  id: stage.id,
17
- ...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
17
+ ...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
18
18
  };
19
19
  if (source?.kind === "inline") {
20
20
  listing.inline = true;
@@ -1,3 +1,4 @@
1
+ import type { PipelineNeeds } from "../types/pipeline.js";
1
2
  import type { StageGateKind } from "../types/stage.js";
2
3
  import type { PipelineListing } from "./listConfig.js";
3
4
  export type CreatePipelineStageInline = {
@@ -7,7 +8,7 @@ export type CreatePipelineStageInline = {
7
8
  };
8
9
  export type CreatePipelineStageRef = {
9
10
  id: string;
10
- needs?: string;
11
+ needs?: PipelineNeeds;
11
12
  uses?: string;
12
13
  inline?: CreatePipelineStageInline;
13
14
  };
@@ -1,5 +1,6 @@
1
1
  import { access, mkdir, readdir, unlink, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { parsePipelineNeeds } from "./pipelineNeeds.js";
3
4
  import { STAGE_ID_PATTERN } from "./createStage.js";
4
5
  import { inferIdFromUsesPath, normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
5
6
  import { readYamlObject } from "./readYamlObject.js";
@@ -149,14 +150,15 @@ function parseStageEntry(entry, index) {
149
150
  }
150
151
  const ref = { id };
151
152
  if (entry.needs !== undefined) {
152
- if (typeof entry.needs !== "string") {
153
+ const parsedNeeds = parsePipelineNeeds(entry.needs, id);
154
+ if (!parsedNeeds.ok) {
153
155
  return {
154
156
  ok: false,
155
157
  status: 400,
156
- error: `stages[${index}].needs must be a string`,
158
+ error: parsedNeeds.message.replace(/^stage "[^"]+": /, `stages[${index}].`),
157
159
  };
158
160
  }
159
- ref.needs = entry.needs;
161
+ ref.needs = parsedNeeds.value;
160
162
  }
161
163
  if (entry.uses !== undefined) {
162
164
  if (typeof entry.uses !== "string" || !entry.uses.trim()) {
@@ -224,6 +226,24 @@ export function parseCreatePipelineBody(body) {
224
226
  stages: refs,
225
227
  };
226
228
  }
229
+ function appendNeedsYaml(lines, needs) {
230
+ if (typeof needs === "string") {
231
+ lines.push(` needs: ${needs}`);
232
+ return;
233
+ }
234
+ lines.push(" needs:");
235
+ for (const edge of needs) {
236
+ if (edge.on.length === 1 && edge.on[0] === "succeeded") {
237
+ lines.push(` - ${edge.id}`);
238
+ continue;
239
+ }
240
+ lines.push(` - id: ${edge.id}`);
241
+ lines.push(" on:");
242
+ for (const state of edge.on) {
243
+ lines.push(` - ${state}`);
244
+ }
245
+ }
246
+ }
227
247
  function formatInlineYamlScalar(value) {
228
248
  if (value.includes("\n") ||
229
249
  /[:#"'&*!|>@`[\]{}]/.test(value) ||
@@ -245,13 +265,18 @@ export function pipelineConfigToYaml(pipeline, options) {
245
265
  }
246
266
  lines.push(` - id: ${stage.id}`);
247
267
  if (stage.needs) {
248
- lines.push(` needs: ${stage.needs}`);
268
+ appendNeedsYaml(lines, stage.needs);
249
269
  }
250
270
  if (stage.inline) {
251
- if (stage.inline.gate_kinds && stage.inline.gate_kinds.length > 0) {
252
- lines.push(" gate_kinds:");
253
- for (const kind of stage.inline.gate_kinds) {
254
- lines.push(` - ${kind}`);
271
+ if (stage.inline.gate_kinds !== undefined) {
272
+ if (stage.inline.gate_kinds.length === 0) {
273
+ lines.push(" gate_kinds: []");
274
+ }
275
+ else {
276
+ lines.push(" gate_kinds:");
277
+ for (const kind of stage.inline.gate_kinds) {
278
+ lines.push(` - ${kind}`);
279
+ }
255
280
  }
256
281
  }
257
282
  if (stage.inline.system_prompt.includes("\n")) {
@@ -364,7 +389,7 @@ function buildPipelineListing(projectRoot, relPath, loaded) {
364
389
  const source = loaded.stageSources?.[stage.id];
365
390
  const listing = {
366
391
  id: stage.id,
367
- ...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
392
+ ...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
368
393
  };
369
394
  if (source?.kind === "inline") {
370
395
  return { ...listing, inline: true };
@@ -22,7 +22,7 @@ function validateStageId(id) {
22
22
  function parseGateKinds(raw) {
23
23
  if (raw === undefined)
24
24
  return undefined;
25
- if (!Array.isArray(raw) || raw.length === 0)
25
+ if (!Array.isArray(raw))
26
26
  return "invalid";
27
27
  const kinds = [];
28
28
  for (const item of raw) {
@@ -71,7 +71,7 @@ export function parseCreateStageBody(body) {
71
71
  return {
72
72
  ok: false,
73
73
  status: 400,
74
- error: `gate_kinds must be a non-empty array of: ${STAGE_GATE_KINDS.join(", ")}`,
74
+ error: `gate_kinds must be an array of: ${STAGE_GATE_KINDS.join(", ")}`,
75
75
  };
76
76
  }
77
77
  return {
@@ -80,7 +80,7 @@ export function parseCreateStageBody(body) {
80
80
  id: body.id,
81
81
  system_prompt: body.system_prompt,
82
82
  model: body.model,
83
- ...(gateKinds ? { gate_kinds: gateKinds } : {}),
83
+ ...(gateKinds !== undefined ? { gate_kinds: gateKinds } : {}),
84
84
  };
85
85
  }
86
86
  function formatInlineYamlScalar(value) {
@@ -96,10 +96,15 @@ function formatInlineYamlScalar(value) {
96
96
  }
97
97
  export function stageConfigToYaml(stage) {
98
98
  const lines = [`id: ${stage.id}`];
99
- if (stage.gate_kinds && stage.gate_kinds.length > 0) {
100
- lines.push("gate_kinds:");
101
- for (const kind of stage.gate_kinds) {
102
- lines.push(` - ${kind}`);
99
+ if (stage.gate_kinds !== undefined) {
100
+ if (stage.gate_kinds.length === 0) {
101
+ lines.push("gate_kinds: []");
102
+ }
103
+ else {
104
+ lines.push("gate_kinds:");
105
+ for (const kind of stage.gate_kinds) {
106
+ lines.push(` - ${kind}`);
107
+ }
103
108
  }
104
109
  }
105
110
  if (stage.system_prompt.includes("\n")) {
@@ -186,7 +191,7 @@ export async function createStage(projectRoot, input) {
186
191
  id: input.id,
187
192
  system_prompt: input.system_prompt,
188
193
  model: input.model,
189
- ...(input.gate_kinds ? { gate_kinds: input.gate_kinds } : {}),
194
+ ...(input.gate_kinds !== undefined ? { gate_kinds: input.gate_kinds } : {}),
190
195
  }), "utf8");
191
196
  }
192
197
  catch (err) {
@@ -200,7 +205,7 @@ export async function createStage(projectRoot, input) {
200
205
  stage: {
201
206
  path: relPath,
202
207
  id: stage.id,
203
- ...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
208
+ ...(stage.gate_kinds !== undefined ? { gate_kinds: stage.gate_kinds } : {}),
204
209
  },
205
210
  };
206
211
  }
@@ -5,6 +5,7 @@ import { mergePipelineStages } from "./mergePipelineIncludes.js";
5
5
  import { normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
6
6
  import { loadStageFromObjectOutcome, loadStageOutcome } from "./loadStage.js";
7
7
  import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
8
+ import { validateCompletionContractForStage } from "./validateCompletionContract.js";
8
9
  export async function resolvePipelinePath(pipelinePath, cwd) {
9
10
  const resolved = path.normalize(path.resolve(cwd, pipelinePath));
10
11
  try {
@@ -21,7 +22,7 @@ async function loadPipelineFromPath(pipelinePath) {
21
22
  if (!mergeOutcome.ok) {
22
23
  return loadFailure(mergeOutcome.issues);
23
24
  }
24
- const { entries: rawEntries, pipelineId } = mergeOutcome.value;
25
+ const { entries: rawEntries, pipelineId, agent: pipelineAgent } = mergeOutcome.value;
25
26
  const ctx = { pipelineId, path: normalizedPipelinePath };
26
27
  const normalizeOutcome = normalizePipelineStageEntries(rawEntries, ctx);
27
28
  if (!normalizeOutcome.ok) {
@@ -106,7 +107,15 @@ async function loadPipelineFromPath(pipelinePath) {
106
107
  const pipeline = {
107
108
  id: pipelineId,
108
109
  stages: stageIds,
110
+ ...(pipelineAgent !== undefined ? { agent: pipelineAgent } : {}),
109
111
  };
112
+ const nodeById = new Map(dag.nodes.map((node) => [node.id, node]));
113
+ for (const stage of stages) {
114
+ const completion = nodeById.get(stage.id)?.completion;
115
+ const completionOutcome = validateCompletionContractForStage(stage, completion);
116
+ if (!completionOutcome.ok)
117
+ return loadFailure(completionOutcome.issues);
118
+ }
110
119
  return loadSuccess({
111
120
  pipeline,
112
121
  stages,