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
@@ -9,6 +9,7 @@ import { browseCatalog } from "../config/browseCatalog.js";
9
9
  import { listExtensions } from "../config/listExtensions.js";
10
10
  import { listSkills } from "../config/listSkills.js";
11
11
  import { readRunArtifact } from "../mcp/readArtifact.js";
12
+ import { readStageVerificationHistory } from "../runstore/verificationHistory.js";
12
13
  import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
13
14
  import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount, } from "../runtime/settingsFile.js";
14
15
  import { isTaskFile } from "../runtime/taskInput.js";
@@ -87,7 +88,10 @@ function isMutatingApi(method, pathname) {
87
88
  pathname === "/api/pipelines" ||
88
89
  /^\/api\/runs\/[^/]+\/rerun$/.test(pathname) ||
89
90
  /^\/api\/runs\/[^/]+\/stages\/[^/]+\/answer$/.test(pathname) ||
91
+ /^\/api\/runs\/[^/]+\/stages\/[^/]+\/feedback-decision$/.test(pathname) ||
90
92
  /^\/api\/runs\/[^/]+\/stages\/[^/]+\/retry$/.test(pathname) ||
93
+ /^\/api\/runs\/[^/]+\/stages\/[^/]+\/recovery$/.test(pathname) ||
94
+ /^\/api\/runs\/[^/]+\/stages\/[^/]+\/recovery\/stop$/.test(pathname) ||
91
95
  /^\/api\/runs\/[^/]+\/stages\/[^/]+\/abandon$/.test(pathname) ||
92
96
  /^\/api\/providers\/[^/]+\/login$/.test(pathname) ||
93
97
  /^\/api\/providers\/[^/]+\/login\/[^/]+\/answer$/.test(pathname) ||
@@ -166,6 +170,9 @@ function assertLoopbackHttpAccess(req, res) {
166
170
  }
167
171
  export function defaultUiDistDir() {
168
172
  const here = path.dirname(fileURLToPath(import.meta.url));
173
+ if (path.basename(path.dirname(here)) === "src") {
174
+ return path.resolve(here, "../../dist/ui");
175
+ }
169
176
  return path.resolve(here, "../ui");
170
177
  }
171
178
  export async function startUiServer(options) {
@@ -219,6 +226,20 @@ export async function startUiServer(options) {
219
226
  }
220
227
  return true;
221
228
  }
229
+ const verificationMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/verification$/);
230
+ if (method === "GET" && verificationMatch) {
231
+ const runId = decodeURIComponent(verificationMatch[1] ?? "");
232
+ const stageId = decodeURIComponent(verificationMatch[2] ?? "");
233
+ try {
234
+ json(res, 200, await readStageVerificationHistory(store, runId, stageId));
235
+ }
236
+ catch (err) {
237
+ const message = err instanceof Error ? err.message : String(err);
238
+ const notFound = /not found|no such/i.test(message);
239
+ json(res, notFound ? 404 : 500, { error: message });
240
+ }
241
+ return true;
242
+ }
222
243
  if (method === "GET" && pathname.startsWith("/api/runs/")) {
223
244
  const rest = pathname.slice("/api/runs/".length);
224
245
  if (rest && !rest.includes("/")) {
@@ -290,6 +311,55 @@ export async function startUiServer(options) {
290
311
  json(res, 202, { ok: true });
291
312
  return true;
292
313
  }
314
+ const feedbackDecisionMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/feedback-decision$/);
315
+ if (method === "POST" && feedbackDecisionMatch) {
316
+ const runId = decodeURIComponent(feedbackDecisionMatch[1] ?? "");
317
+ const stageId = decodeURIComponent(feedbackDecisionMatch[2] ?? "");
318
+ let body;
319
+ try {
320
+ body = await readJsonBody(req);
321
+ }
322
+ catch {
323
+ json(res, 400, { error: "Invalid JSON body" });
324
+ return true;
325
+ }
326
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
327
+ json(res, 400, { error: "Body must be a JSON object" });
328
+ return true;
329
+ }
330
+ const raw = body;
331
+ if (raw.decision !== "extend" &&
332
+ raw.decision !== "continue" &&
333
+ raw.decision !== "abandon") {
334
+ json(res, 400, {
335
+ error: "decision must be extend, continue, or abandon",
336
+ });
337
+ return true;
338
+ }
339
+ if (raw.loopId !== undefined && typeof raw.loopId !== "string") {
340
+ json(res, 400, { error: "loopId must be a string" });
341
+ return true;
342
+ }
343
+ if (raw.reason !== undefined && typeof raw.reason !== "string") {
344
+ json(res, 400, { error: "reason must be a string" });
345
+ return true;
346
+ }
347
+ const result = await manager.decideFeedbackLoop(runId, stageId, {
348
+ decision: raw.decision,
349
+ ...(raw.loopId !== undefined ? { loopId: raw.loopId } : {}),
350
+ ...(raw.reason !== undefined ? { reason: raw.reason } : {}),
351
+ });
352
+ if (!result.ok) {
353
+ json(res, result.status ?? 500, { error: result.reason });
354
+ return true;
355
+ }
356
+ json(res, 202, {
357
+ ok: true,
358
+ effect: result.effect,
359
+ loopId: result.loopId,
360
+ });
361
+ return true;
362
+ }
293
363
  const retryMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/retry$/);
294
364
  if (method === "POST" && retryMatch) {
295
365
  await readJsonBody(req);
@@ -307,6 +377,50 @@ export async function startUiServer(options) {
307
377
  });
308
378
  return true;
309
379
  }
380
+ const recoverMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/recovery$/);
381
+ if (method === "POST" && recoverMatch) {
382
+ let body;
383
+ try {
384
+ body = await readJsonBody(req);
385
+ }
386
+ catch {
387
+ json(res, 400, { error: "Invalid JSON body" });
388
+ return true;
389
+ }
390
+ const guidance = body !== null && typeof body === "object" && !Array.isArray(body)
391
+ ? body.guidance
392
+ : undefined;
393
+ if (guidance !== undefined && typeof guidance !== "string") {
394
+ json(res, 400, { error: "guidance must be a string" });
395
+ return true;
396
+ }
397
+ const runId = decodeURIComponent(recoverMatch[1] ?? "");
398
+ const stageId = decodeURIComponent(recoverMatch[2] ?? "");
399
+ const result = await manager.recoverManualStage(runId, stageId, guidance);
400
+ if (!result.ok) {
401
+ json(res, result.status ?? 500, mapRetryStageFailure(result));
402
+ return true;
403
+ }
404
+ json(res, 202, {
405
+ runId: result.runId,
406
+ stageId: result.stageId,
407
+ attemptIndex: result.attemptIndex,
408
+ });
409
+ return true;
410
+ }
411
+ const stopRecoveryMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/recovery\/stop$/);
412
+ if (method === "POST" && stopRecoveryMatch) {
413
+ await readJsonBody(req);
414
+ const runId = decodeURIComponent(stopRecoveryMatch[1] ?? "");
415
+ const stageId = decodeURIComponent(stopRecoveryMatch[2] ?? "");
416
+ const result = await manager.stopManualRecovery(runId, stageId);
417
+ if (!result.ok) {
418
+ json(res, result.status ?? 500, { error: result.reason });
419
+ return true;
420
+ }
421
+ json(res, 202, result);
422
+ return true;
423
+ }
310
424
  const abandonMatch = pathname.match(/^\/api\/runs\/([^/]+)\/stages\/([^/]+)\/abandon$/);
311
425
  if (method === "POST" && abandonMatch) {
312
426
  await readJsonBody(req);
@@ -9,6 +9,8 @@ export function inferRetryStageErrorCode(reason) {
9
9
  return "run_not_retryable";
10
10
  if (/stage is not failed/i.test(reason))
11
11
  return "stage_not_failed";
12
+ if (/manual recovery/i.test(reason))
13
+ return "manual_recovery_required";
12
14
  return undefined;
13
15
  }
14
16
  export function mapRetryStageFailure(result) {
@@ -107,6 +107,96 @@ export declare const askOperatorParamsSchema: Type.TUnion<[Type.TObject<{
107
107
  artifacts: Type.TArray<Type.TString>;
108
108
  id: Type.TOptional<Type.TString>;
109
109
  }>]>;
110
+ /**
111
+ * Narrows the ask_operator params schema to only the declared gate kinds
112
+ * (stage `gate_kinds` allowlist). Omitted/empty falls back to the full
113
+ * union so back-compat stages keep accepting every kind.
114
+ */
115
+ export declare function askOperatorParamsSchemaFor(allowedKinds?: readonly AskOperatorKind[]): Type.TObject<{
116
+ kind: Type.TLiteral<"free_text">;
117
+ message: Type.TString;
118
+ id: Type.TOptional<Type.TString>;
119
+ }> | Type.TObject<{
120
+ kind: Type.TLiteral<"confirm">;
121
+ message: Type.TString;
122
+ id: Type.TOptional<Type.TString>;
123
+ }> | Type.TObject<{
124
+ kind: Type.TLiteral<"multi_question">;
125
+ questions: Type.TArray<Type.TObject<{
126
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
127
+ message: Type.TString;
128
+ id: Type.TOptional<Type.TString>;
129
+ }>>;
130
+ id: Type.TOptional<Type.TString>;
131
+ }> | Type.TObject<{
132
+ kind: Type.TLiteral<"artifact_backed">;
133
+ message: Type.TString;
134
+ artifacts: Type.TArray<Type.TString>;
135
+ id: Type.TOptional<Type.TString>;
136
+ }> | Type.TUnion<[Type.TObject<{
137
+ kind: Type.TLiteral<"free_text">;
138
+ message: Type.TString;
139
+ id: Type.TOptional<Type.TString>;
140
+ }> | Type.TObject<{
141
+ kind: Type.TLiteral<"confirm">;
142
+ message: Type.TString;
143
+ id: Type.TOptional<Type.TString>;
144
+ }> | Type.TObject<{
145
+ kind: Type.TLiteral<"multi_question">;
146
+ questions: Type.TArray<Type.TObject<{
147
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
148
+ message: Type.TString;
149
+ id: Type.TOptional<Type.TString>;
150
+ }>>;
151
+ id: Type.TOptional<Type.TString>;
152
+ }> | Type.TObject<{
153
+ kind: Type.TLiteral<"artifact_backed">;
154
+ message: Type.TString;
155
+ artifacts: Type.TArray<Type.TString>;
156
+ id: Type.TOptional<Type.TString>;
157
+ }>, Type.TObject<{
158
+ kind: Type.TLiteral<"free_text">;
159
+ message: Type.TString;
160
+ id: Type.TOptional<Type.TString>;
161
+ }> | Type.TObject<{
162
+ kind: Type.TLiteral<"confirm">;
163
+ message: Type.TString;
164
+ id: Type.TOptional<Type.TString>;
165
+ }> | Type.TObject<{
166
+ kind: Type.TLiteral<"multi_question">;
167
+ questions: Type.TArray<Type.TObject<{
168
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
169
+ message: Type.TString;
170
+ id: Type.TOptional<Type.TString>;
171
+ }>>;
172
+ id: Type.TOptional<Type.TString>;
173
+ }> | Type.TObject<{
174
+ kind: Type.TLiteral<"artifact_backed">;
175
+ message: Type.TString;
176
+ artifacts: Type.TArray<Type.TString>;
177
+ id: Type.TOptional<Type.TString>;
178
+ }>, ...(Type.TObject<{
179
+ kind: Type.TLiteral<"free_text">;
180
+ message: Type.TString;
181
+ id: Type.TOptional<Type.TString>;
182
+ }> | Type.TObject<{
183
+ kind: Type.TLiteral<"confirm">;
184
+ message: Type.TString;
185
+ id: Type.TOptional<Type.TString>;
186
+ }> | Type.TObject<{
187
+ kind: Type.TLiteral<"multi_question">;
188
+ questions: Type.TArray<Type.TObject<{
189
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
190
+ message: Type.TString;
191
+ id: Type.TOptional<Type.TString>;
192
+ }>>;
193
+ id: Type.TOptional<Type.TString>;
194
+ }> | Type.TObject<{
195
+ kind: Type.TLiteral<"artifact_backed">;
196
+ message: Type.TString;
197
+ artifacts: Type.TArray<Type.TString>;
198
+ id: Type.TOptional<Type.TString>;
199
+ }>)[]]>;
110
200
  export declare const askOperatorAnswerSchema: Type.TUnion<[Type.TObject<{
111
201
  promptId: Type.TString;
112
202
  kind: Type.TLiteral<"free_text">;
@@ -142,25 +232,28 @@ export declare const parseAnswer: typeof parseAskOperatorAnswer;
142
232
  export type AskOperatorWaitBridge = {
143
233
  requestWait: (prompt: AskOperatorPrompt) => Promise<unknown>;
144
234
  };
145
- export type AskOperatorToolOptions = AskOperatorWaitBridge;
235
+ export type AskOperatorToolOptions = AskOperatorWaitBridge & {
236
+ allowedKinds?: readonly AskOperatorKind[];
237
+ };
146
238
  export type AskOperatorToolDetails = {
147
239
  prompt: AskOperatorPrompt | null;
148
240
  answer: AskOperatorAnswer | null;
149
241
  error: string;
150
242
  };
243
+ export declare function summarizeAnswer(answer: AskOperatorAnswer): string;
151
244
  export declare function createAskOperatorTool(options: AskOperatorToolOptions): {
152
245
  name: string;
153
246
  label: string;
154
247
  description: string;
155
- parameters: Type.TUnion<[Type.TObject<{
248
+ parameters: Type.TObject<{
156
249
  kind: Type.TLiteral<"free_text">;
157
250
  message: Type.TString;
158
251
  id: Type.TOptional<Type.TString>;
159
- }>, Type.TObject<{
252
+ }> | Type.TObject<{
160
253
  kind: Type.TLiteral<"confirm">;
161
254
  message: Type.TString;
162
255
  id: Type.TOptional<Type.TString>;
163
- }>, Type.TObject<{
256
+ }> | Type.TObject<{
164
257
  kind: Type.TLiteral<"multi_question">;
165
258
  questions: Type.TArray<Type.TObject<{
166
259
  kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
@@ -168,12 +261,75 @@ export declare function createAskOperatorTool(options: AskOperatorToolOptions):
168
261
  id: Type.TOptional<Type.TString>;
169
262
  }>>;
170
263
  id: Type.TOptional<Type.TString>;
264
+ }> | Type.TObject<{
265
+ kind: Type.TLiteral<"artifact_backed">;
266
+ message: Type.TString;
267
+ artifacts: Type.TArray<Type.TString>;
268
+ id: Type.TOptional<Type.TString>;
269
+ }> | Type.TUnion<[Type.TObject<{
270
+ kind: Type.TLiteral<"free_text">;
271
+ message: Type.TString;
272
+ id: Type.TOptional<Type.TString>;
273
+ }> | Type.TObject<{
274
+ kind: Type.TLiteral<"confirm">;
275
+ message: Type.TString;
276
+ id: Type.TOptional<Type.TString>;
277
+ }> | Type.TObject<{
278
+ kind: Type.TLiteral<"multi_question">;
279
+ questions: Type.TArray<Type.TObject<{
280
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
281
+ message: Type.TString;
282
+ id: Type.TOptional<Type.TString>;
283
+ }>>;
284
+ id: Type.TOptional<Type.TString>;
285
+ }> | Type.TObject<{
286
+ kind: Type.TLiteral<"artifact_backed">;
287
+ message: Type.TString;
288
+ artifacts: Type.TArray<Type.TString>;
289
+ id: Type.TOptional<Type.TString>;
171
290
  }>, Type.TObject<{
291
+ kind: Type.TLiteral<"free_text">;
292
+ message: Type.TString;
293
+ id: Type.TOptional<Type.TString>;
294
+ }> | Type.TObject<{
295
+ kind: Type.TLiteral<"confirm">;
296
+ message: Type.TString;
297
+ id: Type.TOptional<Type.TString>;
298
+ }> | Type.TObject<{
299
+ kind: Type.TLiteral<"multi_question">;
300
+ questions: Type.TArray<Type.TObject<{
301
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
302
+ message: Type.TString;
303
+ id: Type.TOptional<Type.TString>;
304
+ }>>;
305
+ id: Type.TOptional<Type.TString>;
306
+ }> | Type.TObject<{
307
+ kind: Type.TLiteral<"artifact_backed">;
308
+ message: Type.TString;
309
+ artifacts: Type.TArray<Type.TString>;
310
+ id: Type.TOptional<Type.TString>;
311
+ }>, ...(Type.TObject<{
312
+ kind: Type.TLiteral<"free_text">;
313
+ message: Type.TString;
314
+ id: Type.TOptional<Type.TString>;
315
+ }> | Type.TObject<{
316
+ kind: Type.TLiteral<"confirm">;
317
+ message: Type.TString;
318
+ id: Type.TOptional<Type.TString>;
319
+ }> | Type.TObject<{
320
+ kind: Type.TLiteral<"multi_question">;
321
+ questions: Type.TArray<Type.TObject<{
322
+ kind: Type.TUnion<[Type.TLiteral<"free_text">, Type.TLiteral<"confirm">]>;
323
+ message: Type.TString;
324
+ id: Type.TOptional<Type.TString>;
325
+ }>>;
326
+ id: Type.TOptional<Type.TString>;
327
+ }> | Type.TObject<{
172
328
  kind: Type.TLiteral<"artifact_backed">;
173
329
  message: Type.TString;
174
330
  artifacts: Type.TArray<Type.TString>;
175
331
  id: Type.TOptional<Type.TString>;
176
- }>]>;
332
+ }>)[]]>;
177
333
  execute: (_toolCallId: string, params: unknown) => Promise<{
178
334
  content: {
179
335
  type: "text";
@@ -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
  }>;