stageflow 0.9.0 → 0.13.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 (167) hide show
  1. package/README.md +6 -1
  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 +331 -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 +12 -7
  15. package/dist/agent/piAdapter.js +194 -44
  16. package/dist/agent/piIsolatedMcp.d.ts +23 -0
  17. package/dist/agent/piIsolatedMcp.js +223 -0
  18. package/dist/agent/piIsolatedMcpProbe.d.ts +15 -0
  19. package/dist/agent/piIsolatedMcpProbe.js +292 -0
  20. package/dist/agent/port.d.ts +52 -1
  21. package/dist/agent/port.js +1 -1
  22. package/dist/agent/resolveAgentPort.d.ts +26 -0
  23. package/dist/agent/resolveAgentPort.js +39 -0
  24. package/dist/cli/runCommand.js +2 -2
  25. package/dist/cli/runsCommand.d.ts +1 -1
  26. package/dist/cli/runsCommand.js +184 -2
  27. package/dist/cli.d.ts +1 -1
  28. package/dist/cli.js +11 -5
  29. package/dist/config/browseCatalog.js +1 -1
  30. package/dist/config/createPipeline.d.ts +2 -1
  31. package/dist/config/createPipeline.js +34 -9
  32. package/dist/config/createStage.js +14 -9
  33. package/dist/config/loadPipeline.js +11 -1
  34. package/dist/config/loadStage.d.ts +1 -0
  35. package/dist/config/loadStage.js +207 -2
  36. package/dist/config/loadStageflowManifest.js +14 -1
  37. package/dist/config/mergePipelineIncludes.d.ts +1 -0
  38. package/dist/config/mergePipelineIncludes.js +14 -1
  39. package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
  40. package/dist/config/normalizePipelineStageEntry.js +56 -7
  41. package/dist/config/parseCompletionContract.d.ts +8 -0
  42. package/dist/config/parseCompletionContract.js +218 -0
  43. package/dist/config/parsePreEmitChecks.d.ts +3 -0
  44. package/dist/config/parsePreEmitChecks.js +65 -0
  45. package/dist/config/pipelineNeeds.d.ts +19 -0
  46. package/dist/config/pipelineNeeds.js +151 -0
  47. package/dist/config/pipelineStageKeys.js +17 -1
  48. package/dist/config/resolveForkEmitContext.d.ts +6 -2
  49. package/dist/config/resolveForkEmitContext.js +9 -2
  50. package/dist/config/resolvePipelineDag.d.ts +2 -1
  51. package/dist/config/resolvePipelineDag.js +182 -39
  52. package/dist/config/resolveStageMcpServers.d.ts +35 -0
  53. package/dist/config/resolveStageMcpServers.js +207 -0
  54. package/dist/config/validateCatalog.d.ts +1 -1
  55. package/dist/config/validateCatalog.js +51 -0
  56. package/dist/config/validateCompletionContract.d.ts +4 -0
  57. package/dist/config/validateCompletionContract.js +52 -0
  58. package/dist/envelope/check.d.ts +1 -1
  59. package/dist/envelope/check.js +86 -13
  60. package/dist/envelope/cloneForks.d.ts +1 -1
  61. package/dist/envelope/cloneForks.js +24 -9
  62. package/dist/envelope/feedbackLoop.d.ts +4 -0
  63. package/dist/envelope/feedbackLoop.js +31 -0
  64. package/dist/envelope/payloadSchema.d.ts +7 -1
  65. package/dist/envelope/payloadSchema.js +170 -13
  66. package/dist/envelope/preEmitChecks.d.ts +29 -0
  67. package/dist/envelope/preEmitChecks.js +57 -0
  68. package/dist/hitl/qaTrail.d.ts +25 -0
  69. package/dist/hitl/qaTrail.js +51 -0
  70. package/dist/mcp/catalogTools.js +4 -2
  71. package/dist/mcp/controlTools.js +72 -0
  72. package/dist/mcp/readArtifact.d.ts +2 -0
  73. package/dist/mcp/readArtifact.js +25 -1
  74. package/dist/mcp/waitingGates.d.ts +2 -1
  75. package/dist/mcp/waitingGates.js +17 -1
  76. package/dist/package-meta.d.ts +1 -1
  77. package/dist/package-meta.js +1 -1
  78. package/dist/projection/projectRun.d.ts +4 -1
  79. package/dist/projection/projectRun.js +7 -0
  80. package/dist/prompt/feedbackLoopContext.d.ts +2 -0
  81. package/dist/prompt/feedbackLoopContext.js +17 -0
  82. package/dist/prompt/priorEnvelope.d.ts +2 -2
  83. package/dist/prompt/priorEnvelope.js +4 -1
  84. package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
  85. package/dist/runstore/pipelineDagSnapshot.js +51 -11
  86. package/dist/runstore/port.d.ts +185 -3
  87. package/dist/runstore/port.js +42 -3
  88. package/dist/runstore/runProjection.d.ts +7 -3
  89. package/dist/runstore/runProjection.js +30 -15
  90. package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
  91. package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
  92. package/dist/runstore/sqlite/schema.d.ts +1 -1
  93. package/dist/runstore/sqlite/schema.js +99 -0
  94. package/dist/runstore/stageInstanceId.d.ts +1 -1
  95. package/dist/runstore/stageInstanceId.js +3 -2
  96. package/dist/runstore/trackProjection.js +59 -27
  97. package/dist/runstore/verificationHistory.d.ts +19 -0
  98. package/dist/runstore/verificationHistory.js +30 -0
  99. package/dist/runtime/cloneSchedule.d.ts +40 -4
  100. package/dist/runtime/cloneSchedule.js +212 -28
  101. package/dist/runtime/completionCheckRunner.d.ts +137 -0
  102. package/dist/runtime/completionCheckRunner.js +365 -0
  103. package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
  104. package/dist/runtime/completionCheckoutBaseline.js +44 -0
  105. package/dist/runtime/envelopeRouting.d.ts +10 -3
  106. package/dist/runtime/envelopeRouting.js +146 -5
  107. package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
  108. package/dist/runtime/feedbackLoopCoordinator.js +446 -0
  109. package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
  110. package/dist/runtime/feedbackLoopDecision.js +207 -0
  111. package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
  112. package/dist/runtime/feedbackLoopRoute.js +13 -0
  113. package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
  114. package/dist/runtime/feedbackLoopSchedule.js +72 -0
  115. package/dist/runtime/forkGeneration.d.ts +72 -0
  116. package/dist/runtime/forkGeneration.js +295 -0
  117. package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
  118. package/dist/runtime/gitCheckoutCapability.js +97 -0
  119. package/dist/runtime/manualRecoveryState.d.ts +40 -0
  120. package/dist/runtime/manualRecoveryState.js +73 -0
  121. package/dist/runtime/pipelineScheduler.d.ts +13 -1
  122. package/dist/runtime/pipelineScheduler.js +328 -45
  123. package/dist/runtime/replayLifecycle.d.ts +107 -0
  124. package/dist/runtime/replayLifecycle.js +168 -0
  125. package/dist/runtime/runManager.d.ts +54 -1
  126. package/dist/runtime/runManager.js +471 -6
  127. package/dist/runtime/runRetryCoordinator.d.ts +6 -1
  128. package/dist/runtime/runRetryCoordinator.js +43 -9
  129. package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
  130. package/dist/runtime/stageAttemptBootstrap.js +117 -3
  131. package/dist/runtime/stageProcessLauncher.d.ts +1 -1
  132. package/dist/runtime/stageRecovery.js +1 -1
  133. package/dist/runtime/stageRunner.d.ts +4 -1
  134. package/dist/runtime/stageRunner.js +29 -3
  135. package/dist/runtime/stageWorker.js +69 -2
  136. package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
  137. package/dist/runtime/verifiedStageExecution.d.ts +31 -0
  138. package/dist/runtime/verifiedStageExecution.js +127 -0
  139. package/dist/server/bootstrap.js +1 -0
  140. package/dist/server/http.js +137 -4
  141. package/dist/server/operatorResults.js +2 -0
  142. package/dist/server/projectMcpRoutes.d.ts +6 -0
  143. package/dist/server/projectMcpRoutes.js +38 -0
  144. package/dist/tools/askOperator.d.ts +161 -5
  145. package/dist/tools/askOperator.js +58 -25
  146. package/dist/tools/emitStageEnvelope.d.ts +12 -12
  147. package/dist/tools/emitStageEnvelope.js +112 -24
  148. package/dist/types/completion.d.ts +43 -0
  149. package/dist/types/completion.js +1 -0
  150. package/dist/types/envelope.d.ts +18 -0
  151. package/dist/types/forkChoice.d.ts +9 -4
  152. package/dist/types/forkChoice.js +1 -1
  153. package/dist/types/pipeline.d.ts +41 -2
  154. package/dist/types/preEmitCheck.d.ts +18 -0
  155. package/dist/types/preEmitCheck.js +1 -0
  156. package/dist/types/stage.d.ts +14 -0
  157. package/dist/types/stageflowManifest.d.ts +2 -0
  158. package/dist/ui/assets/index-Yzh42vhf.js +118 -0
  159. package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
  160. package/dist/ui/index.html +2 -2
  161. package/package.json +5 -1
  162. package/skills/stageflow-author/SKILL.md +20 -2
  163. package/skills/stageflow-author/references/catalog-mapping.md +88 -3
  164. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  165. package/skills/stageflow-author/references/stage-prompt-template.md +63 -4
  166. package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -1
  167. package/dist/ui/assets/index-CSoGDA3A.js +0 -118
@@ -30,29 +30,53 @@ const multiQuestionItemSchema = Type.Object({
30
30
  message: Type.String({ minLength: 1 }),
31
31
  id: Type.Optional(Type.String()),
32
32
  });
33
+ const freeTextParamsSchema = Type.Object({
34
+ kind: Type.Literal("free_text"),
35
+ message: Type.String({ minLength: 1 }),
36
+ id: Type.Optional(Type.String()),
37
+ });
38
+ const confirmParamsSchema = Type.Object({
39
+ kind: Type.Literal("confirm"),
40
+ message: Type.String({ minLength: 1 }),
41
+ id: Type.Optional(Type.String()),
42
+ });
43
+ const multiQuestionParamsSchema = Type.Object({
44
+ kind: Type.Literal("multi_question"),
45
+ questions: Type.Array(multiQuestionItemSchema, { minItems: 1 }),
46
+ id: Type.Optional(Type.String()),
47
+ });
48
+ const artifactBackedParamsSchema = Type.Object({
49
+ kind: Type.Literal("artifact_backed"),
50
+ message: Type.String({ minLength: 1 }),
51
+ artifacts: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
52
+ id: Type.Optional(Type.String()),
53
+ });
54
+ const KIND_PARAM_SCHEMAS = {
55
+ free_text: freeTextParamsSchema,
56
+ confirm: confirmParamsSchema,
57
+ multi_question: multiQuestionParamsSchema,
58
+ artifact_backed: artifactBackedParamsSchema,
59
+ };
33
60
  export const askOperatorParamsSchema = Type.Union([
34
- Type.Object({
35
- kind: Type.Literal("free_text"),
36
- message: Type.String({ minLength: 1 }),
37
- id: Type.Optional(Type.String()),
38
- }),
39
- Type.Object({
40
- kind: Type.Literal("confirm"),
41
- message: Type.String({ minLength: 1 }),
42
- id: Type.Optional(Type.String()),
43
- }),
44
- Type.Object({
45
- kind: Type.Literal("multi_question"),
46
- questions: Type.Array(multiQuestionItemSchema, { minItems: 1 }),
47
- id: Type.Optional(Type.String()),
48
- }),
49
- Type.Object({
50
- kind: Type.Literal("artifact_backed"),
51
- message: Type.String({ minLength: 1 }),
52
- artifacts: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
53
- id: Type.Optional(Type.String()),
54
- }),
61
+ freeTextParamsSchema,
62
+ confirmParamsSchema,
63
+ multiQuestionParamsSchema,
64
+ artifactBackedParamsSchema,
55
65
  ]);
66
+ /**
67
+ * Narrows the ask_operator params schema to only the declared gate kinds
68
+ * (stage `gate_kinds` allowlist). Omitted/empty falls back to the full
69
+ * union so back-compat stages keep accepting every kind.
70
+ */
71
+ export function askOperatorParamsSchemaFor(allowedKinds) {
72
+ if (!allowedKinds || allowedKinds.length === 0) {
73
+ return askOperatorParamsSchema;
74
+ }
75
+ const schemas = allowedKinds.map((kind) => KIND_PARAM_SCHEMAS[kind]);
76
+ if (schemas.length === 1)
77
+ return schemas[0];
78
+ return Type.Union(schemas);
79
+ }
56
80
  const decisionSchema = Type.Union([
57
81
  Type.Literal("accept"),
58
82
  Type.Literal("reject"),
@@ -352,7 +376,7 @@ function toolResult(text, details, options) {
352
376
  ...(options?.isError ? { isError: true } : {}),
353
377
  };
354
378
  }
355
- function summarizeAnswer(answer) {
379
+ export function summarizeAnswer(answer) {
356
380
  if (answer.kind === "free_text") {
357
381
  return `Operator answered: ${answer.text}`;
358
382
  }
@@ -365,16 +389,25 @@ function summarizeAnswer(answer) {
365
389
  return `Operator answered ${count} question${count === 1 ? "" : "s"}`;
366
390
  }
367
391
  export function createAskOperatorTool(options) {
368
- const { requestWait } = options;
392
+ const { requestWait, allowedKinds } = options;
393
+ const allowed = allowedKinds && allowedKinds.length > 0
394
+ ? new Set(allowedKinds)
395
+ : null;
396
+ const kindList = allowed
397
+ ? [...allowed].join(", ")
398
+ : ASK_OPERATOR_KINDS.join(", ");
369
399
  return {
370
400
  name: "ask_operator",
371
401
  label: "Ask operator",
372
- description: "Ask the operator a question and wait for their answer. Supports free_text, confirm, multi_question, and artifact_backed prompts. Does not complete the stage — call emit_stage_envelope when finished.",
373
- parameters: askOperatorParamsSchema,
402
+ description: `Ask the operator a question and wait for their answer. Supports ${kindList} prompts. Does not complete the stage — call emit_stage_envelope when finished.`,
403
+ parameters: askOperatorParamsSchemaFor(allowedKinds),
374
404
  execute: async (_toolCallId, params) => {
375
405
  let prompt;
376
406
  try {
377
407
  const parsed = parseAskOperatorParams(params);
408
+ if (allowed && !allowed.has(parsed.kind)) {
409
+ throw new AskOperatorError(`unsupported prompt kind "${parsed.kind}" (allowed: ${kindList})`);
410
+ }
378
411
  prompt = normalizePromptIds(parsed);
379
412
  }
380
413
  catch (err) {
@@ -10,13 +10,16 @@
10
10
  * deadlocks waitForIdle so later pipeline stages never start.
11
11
  */
12
12
  import { Type } from "typebox";
13
+ import type { TSchema } from "typebox/type";
14
+ import { type PreEmitCheckOptions } from "../envelope/preEmitChecks.js";
13
15
  import type { StageEnvelope } from "../types/envelope.js";
14
- import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
16
+ import type { FeedbackLoopConfig } from "../types/pipeline.js";
17
+ import { type CloneEmitContext, type ForkEmitContext } from "../types/forkChoice.js";
15
18
  export type EmitCapture = {
16
19
  envelope?: StageEnvelope;
17
20
  error?: string;
18
21
  };
19
- export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext, cloneEmitContext?: CloneEmitContext): {
22
+ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloadSchema?: unknown, forkEmitContext?: ForkEmitContext, cloneEmitContext?: CloneEmitContext, preEmitCheckOptions?: PreEmitCheckOptions, feedbackLoopEmitContext?: FeedbackLoopConfig): {
20
23
  name: string;
21
24
  label: string;
22
25
  description: string;
@@ -24,17 +27,14 @@ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloa
24
27
  status: Type.TUnion<[Type.TLiteral<"success">, Type.TLiteral<"failure">]>;
25
28
  summary: Type.TString;
26
29
  artifacts: Type.TArray<Type.TString>;
27
- payload: Type.TSchema;
30
+ payload: Type.TOptional<TSchema>;
28
31
  fork_choice: Type.TArray<Type.TString>;
29
- clone_forks?: Type.TArray<Type.TObject<{
30
- successor_id: Type.TString;
31
- action: Type.TUnion<[Type.TLiteral<"skip">, Type.TLiteral<"once">, Type.TLiteral<"fanout">]>;
32
- envelope: Type.TOptional<Type.TUnknown>;
33
- mode: Type.TOptional<Type.TUnion<[Type.TLiteral<"parallel">, Type.TLiteral<"sequential">]>>;
34
- clones: Type.TOptional<Type.TArray<Type.TObject<{
35
- envelope: Type.TUnknown;
36
- }>>>;
37
- }>> | undefined;
32
+ clone_forks?: Type.TArray<TSchema> | undefined;
33
+ feedback_loop?: Type.TOptional<TSchema> | undefined;
34
+ checklist_attestations: Type.TOptional<Type.TArray<Type.TObject<{
35
+ check_id: Type.TString;
36
+ items: Type.TArray<Type.TString>;
37
+ }>>>;
38
38
  stage_id: Type.TOptional<Type.TString>;
39
39
  notes: Type.TOptional<Type.TString>;
40
40
  }>;
@@ -11,9 +11,87 @@
11
11
  */
12
12
  import { Type } from "typebox";
13
13
  import { assertRequiredEnvelope, isAdvancingEnvelope, } from "../envelope/check.js";
14
+ import { assertFeedbackLoopAction } from "../envelope/feedbackLoop.js";
14
15
  import { assertCloneForks } from "../envelope/cloneForks.js";
15
16
  import { normalizeForkChoice } from "../envelope/forkChoice.js";
16
17
  import { assertEnvelopePayload, compilePayloadSchema, } from "../envelope/payloadSchema.js";
18
+ import { assertPreEmitChecks, } from "../envelope/preEmitChecks.js";
19
+ import { CLONE_ACTIONS, } from "../types/forkChoice.js";
20
+ function stringLiteralsSchema(values) {
21
+ if (values.length === 1) {
22
+ return Type.Literal(values[0]);
23
+ }
24
+ return Type.Union(values.map((value) => Type.Literal(value)));
25
+ }
26
+ function stageEnvelopeSchema(payloadSchema) {
27
+ return Type.Object({
28
+ status: Type.Union([Type.Literal("success"), Type.Literal("failure")]),
29
+ summary: Type.String({ minLength: 1 }),
30
+ artifacts: Type.Array(Type.String()),
31
+ payload: Type.Optional(payloadSchema !== undefined
32
+ ? payloadSchema
33
+ : Type.Record(Type.String(), Type.Unknown())),
34
+ });
35
+ }
36
+ function feedbackLoopActionSchema(context) {
37
+ return Type.Union([
38
+ Type.Object({ action: Type.Literal("continue") }, { additionalProperties: false }),
39
+ Type.Object({
40
+ action: Type.Literal("send_back"),
41
+ target: stringLiteralsSchema([context.target]),
42
+ }, { additionalProperties: false }),
43
+ ]);
44
+ }
45
+ function cloneForksItemSchema(cloneEmitContext) {
46
+ const allowedActions = cloneEmitContext.allowedActions ?? [...CLONE_ACTIONS];
47
+ const allowSkip = allowedActions.includes("skip");
48
+ const allowOnce = allowedActions.includes("once");
49
+ const allowFanout = allowedActions.includes("fanout");
50
+ const variants = [];
51
+ for (const successor of cloneEmitContext.clonableSuccessors) {
52
+ const successorId = Type.Literal(successor.successorId);
53
+ const nestedPayload = successor.cloneInputSchema !== undefined
54
+ ? compilePayloadSchema(successor.cloneInputSchema)
55
+ : undefined;
56
+ const nestedEnvelope = stageEnvelopeSchema(nestedPayload);
57
+ if (allowSkip) {
58
+ variants.push(Type.Object({
59
+ successor_id: successorId,
60
+ action: Type.Literal("skip"),
61
+ }, { additionalProperties: false }));
62
+ }
63
+ if (allowOnce) {
64
+ variants.push(Type.Object({
65
+ successor_id: successorId,
66
+ action: Type.Literal("once"),
67
+ envelope: nestedEnvelope,
68
+ }));
69
+ }
70
+ if (allowFanout) {
71
+ variants.push(Type.Object({
72
+ successor_id: successorId,
73
+ action: Type.Literal("fanout"),
74
+ mode: Type.Union([
75
+ Type.Literal("parallel"),
76
+ Type.Literal("sequential"),
77
+ ]),
78
+ clones: Type.Array(Type.Object({
79
+ envelope: nestedEnvelope,
80
+ }), { minItems: 2, maxItems: successor.cloneCap }),
81
+ }));
82
+ }
83
+ }
84
+ if (variants.length === 0) {
85
+ return Type.Object({
86
+ successor_id: Type.String(),
87
+ action: stringLiteralsSchema(allowedActions),
88
+ });
89
+ }
90
+ if (variants.length === 1) {
91
+ return variants[0];
92
+ }
93
+ return Type.Union(variants);
94
+ }
17
95
  function toolResult(text, details, options) {
18
96
  return {
19
97
  content: [{ type: "text", text }],
@@ -22,23 +100,10 @@ function toolResult(text, details, options) {
22
100
  ...(options?.terminate ? { terminate: true } : {}),
23
101
  };
24
102
  }
25
- export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext, cloneEmitContext) {
103
+ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitContext, cloneEmitContext, preEmitCheckOptions, feedbackLoopEmitContext) {
26
104
  const compiledPayload = payloadSchema !== undefined
27
105
  ? compilePayloadSchema(payloadSchema)
28
106
  : undefined;
29
- const cloneForksSchema = Type.Array(Type.Object({
30
- successor_id: Type.String(),
31
- action: Type.Union([
32
- Type.Literal("skip"),
33
- Type.Literal("once"),
34
- Type.Literal("fanout"),
35
- ]),
36
- envelope: Type.Optional(Type.Unknown()),
37
- mode: Type.Optional(Type.Union([Type.Literal("parallel"), Type.Literal("sequential")])),
38
- clones: Type.Optional(Type.Array(Type.Object({
39
- envelope: Type.Unknown(),
40
- }))),
41
- }));
42
107
  return {
43
108
  name: "emit_stage_envelope",
44
109
  label: "Emit stage envelope",
@@ -47,13 +112,28 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
47
112
  status: Type.Union([Type.Literal("success"), Type.Literal("failure")]),
48
113
  summary: Type.String({ minLength: 1 }),
49
114
  artifacts: Type.Array(Type.String()),
50
- payload: compiledPayload !== undefined
115
+ payload: Type.Optional(compiledPayload !== undefined
51
116
  ? compiledPayload
52
- : Type.Optional(Type.Record(Type.String(), Type.Unknown())),
53
- fork_choice: forkEmitContext !== undefined
117
+ : Type.Record(Type.String(), Type.Unknown())),
118
+ fork_choice: forkEmitContext !== undefined && feedbackLoopEmitContext === undefined
54
119
  ? Type.Array(Type.String())
55
120
  : Type.Optional(Type.Array(Type.String())),
56
- ...(cloneEmitContext !== undefined ? { clone_forks: cloneForksSchema } : {}),
121
+ ...(cloneEmitContext !== undefined
122
+ ? {
123
+ clone_forks: feedbackLoopEmitContext === undefined
124
+ ? Type.Array(cloneForksItemSchema(cloneEmitContext))
125
+ : Type.Optional(Type.Array(cloneForksItemSchema(cloneEmitContext))),
126
+ }
127
+ : {}),
128
+ ...(feedbackLoopEmitContext !== undefined
129
+ ? {
130
+ feedback_loop: Type.Optional(feedbackLoopActionSchema(feedbackLoopEmitContext)),
131
+ }
132
+ : {}),
133
+ checklist_attestations: Type.Optional(Type.Array(Type.Object({
134
+ check_id: Type.String({ minLength: 1 }),
135
+ items: Type.Array(Type.String()),
136
+ }))),
57
137
  stage_id: Type.Optional(Type.String()),
58
138
  notes: Type.Optional(Type.String()),
59
139
  }),
@@ -62,15 +142,23 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
62
142
  return toolResult("Envelope already accepted; later emits are ignored.", { advancing: isAdvancingEnvelope(capture.envelope), error: "" }, { terminate: true });
63
143
  }
64
144
  try {
65
- const record = params;
66
- if (record.status !== "failure" && forkEmitContext !== undefined) {
67
- normalizeForkChoice(record.fork_choice, "emit", forkEmitContext);
145
+ const envelope = assertRequiredEnvelope(params);
146
+ assertFeedbackLoopAction(envelope, feedbackLoopEmitContext);
147
+ const isSendBack = envelope.feedback_loop?.action === "send_back";
148
+ if (envelope.status !== "failure" &&
149
+ !isSendBack &&
150
+ forkEmitContext !== undefined) {
151
+ normalizeForkChoice(envelope.fork_choice, "emit", forkEmitContext);
68
152
  }
69
- if (record.status !== "failure" && cloneEmitContext !== undefined) {
70
- assertCloneForks(params, cloneEmitContext);
153
+ if (envelope.status !== "failure" &&
154
+ !isSendBack &&
155
+ cloneEmitContext !== undefined) {
156
+ assertCloneForks(envelope, cloneEmitContext);
71
157
  }
72
- const envelope = assertRequiredEnvelope(params);
73
158
  assertEnvelopePayload(envelope, payloadSchema);
159
+ if (envelope.status !== "failure") {
160
+ await assertPreEmitChecks(envelope, preEmitCheckOptions);
161
+ }
74
162
  capture.envelope = envelope;
75
163
  const advancing = isAdvancingEnvelope(envelope);
76
164
  return toolResult(advancing
@@ -0,0 +1,43 @@
1
+ import type { StageGateKind } from "./stage.js";
2
+ export declare const COMPLETION_VERIFICATION_FAILURE_PREFIX = "Completion verification failed:";
3
+ export type CompletionCheck = {
4
+ id: string;
5
+ type: "command";
6
+ run: string;
7
+ cwd?: string;
8
+ timeout_ms?: number;
9
+ } | {
10
+ id: string;
11
+ type: "artifact";
12
+ path: string;
13
+ nonempty?: boolean;
14
+ } | {
15
+ id: string;
16
+ type: "checklist";
17
+ items: string[];
18
+ } | {
19
+ id: string;
20
+ type: "payload_schema";
21
+ } | {
22
+ id: string;
23
+ type: "gate";
24
+ kind: StageGateKind;
25
+ } | {
26
+ id: string;
27
+ type: "checkout_changes";
28
+ path_fields?: string[];
29
+ };
30
+ export type CompletionContract = {
31
+ mode: "all";
32
+ checks: CompletionCheck[];
33
+ };
34
+ export type RecoveryPolicy = {
35
+ mode: "repair";
36
+ max_attempts: number;
37
+ retry_safety: "idempotent";
38
+ include_failed_checks: boolean;
39
+ } | {
40
+ mode: "manual";
41
+ retry_safety: "idempotent" | "side_effecting";
42
+ include_failed_checks?: boolean;
43
+ };
@@ -0,0 +1 @@
1
+ export const COMPLETION_VERIFICATION_FAILURE_PREFIX = "Completion verification failed:";
@@ -1,5 +1,11 @@
1
1
  import type { CloneForkItem } from "./forkChoice.js";
2
2
  export type EnvelopeStatus = "success" | "failure";
3
+ export type FeedbackLoopAction = {
4
+ action: "continue";
5
+ } | {
6
+ action: "send_back";
7
+ target: string;
8
+ };
3
9
  export type StageEnvelope = {
4
10
  status: EnvelopeStatus;
5
11
  summary: string;
@@ -7,9 +13,21 @@ export type StageEnvelope = {
7
13
  payload?: Record<string, unknown>;
8
14
  fork_choice?: string[];
9
15
  clone_forks?: CloneForkItem[];
16
+ feedback_loop?: FeedbackLoopAction;
17
+ /** Agent's explicit acknowledgement of pipeline-owned checklist items. */
18
+ checklist_attestations?: Array<{
19
+ check_id: string;
20
+ items: string[];
21
+ }>;
10
22
  stage_id?: string;
11
23
  notes?: string;
12
24
  };
25
+ export type SyntheticSkippedEnvelope = {
26
+ status: "skipped";
27
+ summary: string;
28
+ artifacts: string[];
29
+ };
30
+ export type TerminalEnvelope = StageEnvelope | SyntheticSkippedEnvelope;
13
31
  export declare class EnvelopeError extends Error {
14
32
  constructor(message: string);
15
33
  }
@@ -7,6 +7,8 @@ export type ForkEmitContext = {
7
7
  immediateSuccessorIds: string[];
8
8
  forkShape: ForkChoiceShape | null;
9
9
  };
10
+ export declare const CLONE_ACTIONS: readonly ["skip", "once", "fanout"];
11
+ export type CloneAction = (typeof CLONE_ACTIONS)[number];
10
12
  export type CloneForkItem = {
11
13
  successor_id: string;
12
14
  action: "skip";
@@ -22,9 +24,12 @@ export type CloneForkItem = {
22
24
  envelope: StageEnvelope;
23
25
  }>;
24
26
  };
27
+ export type CloneSuccessorEmit = {
28
+ successorId: string;
29
+ cloneCap: number;
30
+ cloneInputSchema?: unknown;
31
+ };
25
32
  export type CloneEmitContext = {
26
- clonableSuccessors: Array<{
27
- successorId: string;
28
- cloneCap: number;
29
- }>;
33
+ clonableSuccessors: CloneSuccessorEmit[];
34
+ allowedActions?: CloneAction[];
30
35
  };
@@ -1 +1 @@
1
- export {};
1
+ export const CLONE_ACTIONS = ["skip", "once", "fanout"];
@@ -1,21 +1,47 @@
1
+ import type { CloneAction } from "./forkChoice.js";
1
2
  import type { StageConfig, StageGateKind } from "./stage.js";
3
+ import type { CompletionContract, RecoveryPolicy } from "./completion.js";
2
4
  export type PipelineConfig = {
3
5
  id: string;
4
6
  stages: string[];
7
+ /** Selects the AgentPort backend for every stage in this pipeline; overrides the global default. */
8
+ agent?: string;
5
9
  };
6
10
  export type PipelineForkConfig = {
7
11
  select: "one" | "subset";
8
12
  allow_none: boolean;
9
13
  };
14
+ export type NeedTerminalState = "succeeded" | "failed" | "skipped";
15
+ export type PipelineNeedEdge = {
16
+ id: string;
17
+ on: NeedTerminalState[];
18
+ };
19
+ export type PipelineNeedItem = string | {
20
+ id: string;
21
+ on?: NeedTerminalState[];
22
+ };
23
+ export type PipelineNeeds = string | PipelineNeedEdge[];
24
+ /** Runtime feedback-loop policy declared by the stage that can send work back. */
25
+ export type FeedbackLoopConfig = {
26
+ target: string;
27
+ max_replays: number;
28
+ on_max_replays: "require_continue" | "wait_for_human";
29
+ replay_session: "resume" | "new_session";
30
+ };
10
31
  export type PipelineStageRef = {
11
32
  id: string;
12
- needs?: string;
33
+ needs?: string | PipelineNeedItem[];
13
34
  fork?: {
14
35
  select: "one" | "subset";
15
36
  allow_none?: boolean;
16
37
  };
17
38
  clonable?: boolean;
18
39
  clone_cap?: number;
40
+ completion?: CompletionContract;
41
+ recovery?: RecoveryPolicy;
42
+ feedback_loop?: FeedbackLoopConfig;
43
+ /** Omitted means this stage is safe to include in a feedback replay. */
44
+ replay_safe?: boolean;
19
45
  };
20
46
  export type PipelineStageYamlEntry = PipelineStageRef & {
21
47
  uses?: string;
@@ -23,7 +49,10 @@ export type PipelineStageYamlEntry = PipelineStageRef & {
23
49
  model?: string;
24
50
  payload_schema?: unknown;
25
51
  gate_kinds?: StageGateKind[];
52
+ clone_input_schema?: unknown;
53
+ clone_actions?: CloneAction[];
26
54
  skill?: string;
55
+ mcp?: string[];
27
56
  };
28
57
  export type PipelineIncludeEntry = {
29
58
  local: string;
@@ -34,14 +63,19 @@ export type PipelineFragmentConfig = {
34
63
  };
35
64
  export type NormalizedPipelineStageEntry = {
36
65
  id: string;
37
- needs?: string;
66
+ needs?: PipelineNeeds;
38
67
  fork?: {
39
68
  select: "one" | "subset";
40
69
  allow_none?: boolean;
41
70
  };
42
71
  clonable?: boolean;
43
72
  clone_cap?: number;
73
+ completion?: CompletionContract;
74
+ recovery?: RecoveryPolicy;
75
+ feedback_loop?: FeedbackLoopConfig;
76
+ replay_safe?: boolean;
44
77
  skill?: string;
78
+ mcp?: string[];
45
79
  body: {
46
80
  kind: "inline";
47
81
  raw: Record<string, unknown>;
@@ -61,12 +95,17 @@ export type PipelineStageSource = {
61
95
  export type ResolvedPipelineStageNode = {
62
96
  id: string;
63
97
  needs: string | null;
98
+ needsEdges: PipelineNeedEdge[];
64
99
  ancestors: string[];
65
100
  stageIndex: number;
66
101
  fork?: PipelineForkConfig;
67
102
  clonable?: boolean;
68
103
  clone_cap?: number;
69
104
  definition_id?: string;
105
+ completion?: CompletionContract;
106
+ recovery?: RecoveryPolicy;
107
+ feedback_loop?: FeedbackLoopConfig;
108
+ replay_safe?: boolean;
70
109
  };
71
110
  export type ResolvedPipelineDag = {
72
111
  nodes: ResolvedPipelineStageNode[];
@@ -0,0 +1,18 @@
1
+ import type { StageGateKind } from "./stage.js";
2
+ /**
3
+ * In-session checks `emit_stage_envelope` must satisfy this attempt before a
4
+ * success emit is accepted. Distinct from `CompletionCheck`/`CompletionContract`
5
+ * (`src/types/completion.ts`): those run post-hoc, after a candidate envelope
6
+ * has already been captured, driven by the pipeline-wiring `completion` field.
7
+ * `PreEmitCheck` runs synchronously inside the agent's own `emit_stage_envelope`
8
+ * tool call, driven by the stage-body `pre_emit_checks` field.
9
+ */
10
+ export type PreEmitCheck = {
11
+ id: string;
12
+ type: "gate";
13
+ kind: StageGateKind;
14
+ } | {
15
+ id: string;
16
+ type: "artifact_declared";
17
+ basename: string;
18
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,5 @@
1
+ import type { PreEmitCheck } from "./preEmitCheck.js";
2
+ import type { CloneAction } from "./forkChoice.js";
1
3
  export declare const STAGE_GATE_KINDS: readonly ["free_text", "confirm", "multi_question", "artifact_backed"];
2
4
  export type StageGateKind = (typeof STAGE_GATE_KINDS)[number];
3
5
  export type StageConfig = {
@@ -8,5 +10,17 @@ export type StageConfig = {
8
10
  payload_schema?: unknown;
9
11
  /** Declared ask_operator kinds this stage is expected to stop on. */
10
12
  gate_kinds?: StageGateKind[];
13
+ /** In-session checks emit must satisfy this attempt before success is accepted. */
14
+ pre_emit_checks?: PreEmitCheck[];
15
+ clone_input_schema?: unknown;
16
+ clone_actions?: CloneAction[];
17
+ /** Optional stage wall-clock timeout in milliseconds (default 60 minutes). */
18
+ timeout_ms?: number;
11
19
  skill?: string;
20
+ mcp?: string[];
21
+ /**
22
+ * Selects the AgentPort backend for this stage, overriding pipeline/global.
23
+ * Parsed but not yet consulted — see STAGE_LEVEL_AGENT_OVERRIDE_ENABLED.
24
+ */
25
+ agent?: string;
12
26
  };
@@ -11,6 +11,8 @@ export type StageflowManifestCatalog = {
11
11
  export type StageflowManifest = {
12
12
  version: StageflowManifestVersion;
13
13
  catalog: StageflowManifestCatalog;
14
+ /** Default AgentPort backend for every pipeline/stage that doesn't override it. */
15
+ agent?: string;
14
16
  };
15
17
  export type LoadedManifest = {
16
18
  path: string;