stageflow 0.18.0 → 0.23.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 (116) hide show
  1. package/README.md +2 -1
  2. package/dist/agent/activity.d.ts +9 -0
  3. package/dist/agent/activityObserver.d.ts +3 -0
  4. package/dist/agent/activityObserver.js +6 -1
  5. package/dist/agent/claudeAdapter.js +38 -0
  6. package/dist/agent/fakeAgent.js +3 -1
  7. package/dist/agent/piAdapter.js +40 -4
  8. package/dist/agent/port.d.ts +3 -1
  9. package/dist/agent/providerInspect.d.ts +19 -0
  10. package/dist/agent/providerInspect.js +35 -0
  11. package/dist/agent/stageTimeout.d.ts +9 -0
  12. package/dist/agent/stageTimeout.js +24 -0
  13. package/dist/agent/streamLogRedact.d.ts +2 -0
  14. package/dist/agent/streamLogRedact.js +13 -0
  15. package/dist/cli/artifactCommand.js +2 -2
  16. package/dist/cli/envelopeCommand.js +2 -2
  17. package/dist/cli/exportRunCommand.js +2 -2
  18. package/dist/cli/graphCommand.d.ts +15 -0
  19. package/dist/cli/graphCommand.js +79 -0
  20. package/dist/cli/graphRender.d.ts +2 -0
  21. package/dist/cli/graphRender.js +85 -0
  22. package/dist/cli/hostClient.d.ts +43 -0
  23. package/dist/cli/hostClient.js +182 -0
  24. package/dist/cli/providersCommand.js +5 -3
  25. package/dist/cli/runCommand.d.ts +6 -5
  26. package/dist/cli/runCommand.js +23 -31
  27. package/dist/cli/runOutput.d.ts +1 -1
  28. package/dist/cli/runsCommand.d.ts +4 -8
  29. package/dist/cli/runsCommand.js +65 -89
  30. package/dist/cli.js +13 -5
  31. package/dist/config/describePipeline.d.ts +26 -0
  32. package/dist/config/describePipeline.js +37 -0
  33. package/dist/config/loadPipeline.d.ts +13 -1
  34. package/dist/config/loadPipeline.js +34 -4
  35. package/dist/config/mergePipelineIncludes.d.ts +11 -0
  36. package/dist/config/mergePipelineIncludes.js +75 -0
  37. package/dist/config/validateCatalog.d.ts +2 -2
  38. package/dist/config/validateCatalog.js +24 -17
  39. package/dist/mcp/catalogTools.js +146 -52
  40. package/dist/mcp/controlTools.js +94 -19
  41. package/dist/mcp/deps.d.ts +3 -0
  42. package/dist/mcp/projectMcpTools.d.ts +3 -0
  43. package/dist/mcp/projectMcpTools.js +25 -0
  44. package/dist/mcp/providerTools.d.ts +3 -0
  45. package/dist/mcp/providerTools.js +17 -0
  46. package/dist/mcp/readArtifact.d.ts +9 -0
  47. package/dist/mcp/readArtifact.js +11 -0
  48. package/dist/mcp/server.js +9 -1
  49. package/dist/mcp/tailStreamLog.d.ts +8 -0
  50. package/dist/mcp/tailStreamLog.js +28 -0
  51. package/dist/mcp/toolResults.d.ts +17 -0
  52. package/dist/mcp/toolResults.js +15 -0
  53. package/dist/mcp/tools.js +4 -0
  54. package/dist/mcp/waitRun.js +4 -4
  55. package/dist/mcp/waitingGates.d.ts +1 -0
  56. package/dist/mcp/waitingGates.js +3 -2
  57. package/dist/package-meta.d.ts +1 -1
  58. package/dist/package-meta.js +1 -1
  59. package/dist/projection/projectRun.d.ts +3 -0
  60. package/dist/projection/projectRun.js +7 -0
  61. package/dist/runstore/port.d.ts +3 -1
  62. package/dist/runstore/port.js +2 -0
  63. package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -1
  64. package/dist/runstore/sqlite/SqliteRunStore.js +15 -1
  65. package/dist/runstore/workspaceLayout.d.ts +1 -0
  66. package/dist/runstore/workspaceLayout.js +3 -0
  67. package/dist/runtime/feedbackLoopCoordinator.d.ts +9 -1
  68. package/dist/runtime/feedbackLoopCoordinator.js +50 -20
  69. package/dist/runtime/feedbackLoopDecision.js +33 -1
  70. package/dist/runtime/forkGeneration.d.ts +4 -0
  71. package/dist/runtime/forkGeneration.js +16 -0
  72. package/dist/runtime/pipelineRunner.d.ts +3 -3
  73. package/dist/runtime/pipelineRunner.js +3 -1
  74. package/dist/runtime/pipelineScheduler.js +23 -1
  75. package/dist/runtime/reopenSkipped.d.ts +13 -0
  76. package/dist/runtime/reopenSkipped.js +56 -0
  77. package/dist/runtime/resumeReconstruct.js +2 -2
  78. package/dist/runtime/resumeTimedOut.d.ts +33 -0
  79. package/dist/runtime/resumeTimedOut.js +165 -0
  80. package/dist/runtime/runManager.d.ts +3 -1
  81. package/dist/runtime/runManager.js +110 -14
  82. package/dist/runtime/settingsFile.d.ts +2 -0
  83. package/dist/runtime/settingsFile.js +19 -0
  84. package/dist/runtime/stageAttemptBootstrap.d.ts +3 -0
  85. package/dist/runtime/stageAttemptBootstrap.js +8 -2
  86. package/dist/runtime/stageRecovery.js +2 -2
  87. package/dist/runtime/stageRunner.js +6 -2
  88. package/dist/runtime/stageStreamLog.d.ts +17 -0
  89. package/dist/runtime/stageStreamLog.js +89 -0
  90. package/dist/runtime/stageWorker.js +2 -1
  91. package/dist/server/bootstrap.js +9 -2
  92. package/dist/server/ensureGlobalService.d.ts +35 -0
  93. package/dist/server/ensureGlobalService.js +138 -0
  94. package/dist/server/http.d.ts +20 -1
  95. package/dist/server/http.js +452 -386
  96. package/dist/server/mcpHost.js +7 -8
  97. package/dist/server/operatorResults.d.ts +10 -0
  98. package/dist/server/operatorResults.js +24 -0
  99. package/dist/server/providerRoutes.d.ts +0 -6
  100. package/dist/server/providerRoutes.js +10 -18
  101. package/dist/tools/askOperator.d.ts +1 -0
  102. package/dist/tools/askOperator.js +14 -11
  103. package/dist/tools/emitStageEnvelope.d.ts +1 -0
  104. package/dist/tools/emitStageEnvelope.js +3 -1
  105. package/dist/tools/prepareToolArguments.d.ts +4 -0
  106. package/dist/tools/prepareToolArguments.js +79 -0
  107. package/dist/types/pipeline.d.ts +8 -0
  108. package/dist/ui/assets/{index-DsgVjLUS.css → index-CSsDued1.css} +1 -1
  109. package/dist/ui/assets/index-Da5EHU6M.js +119 -0
  110. package/dist/ui/index.html +2 -2
  111. package/package.json +1 -1
  112. package/skills/stageflow/references/control-surface.md +2 -2
  113. package/skills/stageflow-run/SKILL.md +27 -9
  114. package/skills/stageflow-run/references/mcp-call.md +1 -1
  115. package/skills/stageflow-run/scripts/mcp-call.mjs +9 -0
  116. package/dist/ui/assets/index-DwKGaQvQ.js +0 -119
@@ -1,21 +1,20 @@
1
1
  import { bootstrapStageflowHost, } from "./bootstrap.js";
2
2
  import { createHttpHost, DEFAULT_PORT, } from "./createHttpHost.js";
3
+ import { createOperatorRoutes } from "./http.js";
3
4
  export async function startMcpServer(options) {
4
5
  const host = options.host ?? "127.0.0.1";
5
6
  const port = options.port ?? DEFAULT_PORT;
6
7
  const boot = await bootstrapStageflowHost(options);
8
+ const { manager, store, cwd, agentDir, rootDir } = boot;
9
+ const providerAuthContext = boot.providerAuthContext;
7
10
  return createHttpHost({
8
11
  boot,
9
12
  host,
10
13
  port,
11
- routes: async ({ method, pathname, res, json, boot: b }) => {
12
- if (method === "GET" && pathname === "/api/health") {
13
- json(res, 200, b.manager.getHealth());
14
- return true;
15
- }
16
- json(res, 404, { error: "Not found" });
17
- return true;
18
- },
14
+ // Headless daemon: same REST API surface as `sf ui` (this is what lets
15
+ // `sf run`/`sf runs *` talk to an auto-started `sf mcp` over HTTP), just
16
+ // without serving the console's static UI files.
17
+ routes: createOperatorRoutes({ manager, store, cwd, agentDir, rootDir, providerAuthContext }),
19
18
  });
20
19
  }
21
20
  export { DEFAULT_PORT };
@@ -11,3 +11,13 @@ export declare function mapStartFailure(result: Extract<StartRunResult, {
11
11
  }>): {
12
12
  error: string;
13
13
  } & Record<string, unknown>;
14
+ export type StoreLookupPolicy = "run" | "artifact" | "envelope";
15
+ export type StoreLookupKind = "not_found" | "denied" | "error";
16
+ export type StoreLookupMapped = {
17
+ error: string;
18
+ status: number;
19
+ kind: StoreLookupKind;
20
+ };
21
+ export declare function mapStoreLookupError(err: unknown, opts: {
22
+ policy: StoreLookupPolicy;
23
+ }): StoreLookupMapped;
@@ -22,3 +22,27 @@ export function mapStartFailure(result) {
22
22
  const { ok: _ok, status: _status, reason, ...rest } = result;
23
23
  return { error: reason, ...rest };
24
24
  }
25
+ export function mapStoreLookupError(err, opts) {
26
+ const error = err instanceof Error ? err.message : String(err);
27
+ if (opts.policy === "artifact") {
28
+ if (error === "Artifact path denied") {
29
+ return { error, status: 400, kind: "denied" };
30
+ }
31
+ const notFound = error.startsWith("Run not found") ||
32
+ error.startsWith("Artifact not found") ||
33
+ /no such|not found/i.test(error);
34
+ return {
35
+ error,
36
+ status: notFound ? 404 : 400,
37
+ kind: notFound ? "not_found" : "error",
38
+ };
39
+ }
40
+ const notFound = opts.policy === "envelope"
41
+ ? /not found|no such|envelope/i.test(error)
42
+ : /not found|no such|unknown run/i.test(error);
43
+ return {
44
+ error,
45
+ status: notFound ? 404 : 500,
46
+ kind: notFound ? "not_found" : "error",
47
+ };
48
+ }
@@ -6,10 +6,4 @@ export type ProviderRoutesCtx = {
6
6
  json: (res: ServerResponse, status: number, body: unknown) => void;
7
7
  providerAuthContext?: ProviderAuthContext;
8
8
  };
9
- export declare function providerAuthErrorBody(err: unknown): {
10
- status: number;
11
- body: {
12
- error: string;
13
- };
14
- };
15
9
  export declare function handleProviderRoutes(req: IncomingMessage, res: ServerResponse, ctx: ProviderRoutesCtx): Promise<boolean>;
@@ -1,14 +1,6 @@
1
- import { defaultContext, detectPiHome, getAuthStatus, listProviders, loginWithApiKey, logoutProvider, ProviderAuthError, } from "../agent/providerAuth.js";
1
+ import { defaultContext, detectPiHome, getAuthStatus, listProviders, loginWithApiKey, logoutProvider, } from "../agent/providerAuth.js";
2
+ import { mapProviderAuthError } from "../agent/providerInspect.js";
2
3
  import { answerLoginSession, cancelLoginSession, getLoginSession, startOAuthLoginSession, } from "../agent/oauthSessionManager.js";
3
- export function providerAuthErrorBody(err) {
4
- if (err instanceof ProviderAuthError) {
5
- return { status: err.status, body: { error: err.message } };
6
- }
7
- return {
8
- status: 500,
9
- body: { error: "Provider auth operation failed" },
10
- };
11
- }
12
4
  export async function handleProviderRoutes(req, res, ctx) {
13
5
  const { cwd, readJsonBody, json } = ctx;
14
6
  const authCtx = ctx.providerAuthContext ?? defaultContext;
@@ -20,7 +12,7 @@ export async function handleProviderRoutes(req, res, ctx) {
20
12
  json(res, 200, detectPiHome(cwd));
21
13
  }
22
14
  catch (err) {
23
- const mapped = providerAuthErrorBody(err);
15
+ const mapped = mapProviderAuthError(err);
24
16
  json(res, mapped.status, mapped.body);
25
17
  }
26
18
  return true;
@@ -30,7 +22,7 @@ export async function handleProviderRoutes(req, res, ctx) {
30
22
  json(res, 200, await listProviders(cwd, authCtx));
31
23
  }
32
24
  catch (err) {
33
- const mapped = providerAuthErrorBody(err);
25
+ const mapped = mapProviderAuthError(err);
34
26
  json(res, mapped.status, mapped.body);
35
27
  }
36
28
  return true;
@@ -44,7 +36,7 @@ export async function handleProviderRoutes(req, res, ctx) {
44
36
  });
45
37
  }
46
38
  catch (err) {
47
- const mapped = providerAuthErrorBody(err);
39
+ const mapped = mapProviderAuthError(err);
48
40
  json(res, mapped.status, mapped.body);
49
41
  }
50
42
  return true;
@@ -70,7 +62,7 @@ export async function handleProviderRoutes(req, res, ctx) {
70
62
  json(res, 200, { ok: true, session });
71
63
  }
72
64
  catch (err) {
73
- const mapped = providerAuthErrorBody(err);
65
+ const mapped = mapProviderAuthError(err);
74
66
  json(res, mapped.status, mapped.body);
75
67
  }
76
68
  return true;
@@ -89,7 +81,7 @@ export async function handleProviderRoutes(req, res, ctx) {
89
81
  json(res, 200, { ok: true, provider });
90
82
  }
91
83
  catch (err) {
92
- const mapped = providerAuthErrorBody(err);
84
+ const mapped = mapProviderAuthError(err);
93
85
  json(res, mapped.status, mapped.body);
94
86
  }
95
87
  return true;
@@ -135,7 +127,7 @@ export async function handleProviderRoutes(req, res, ctx) {
135
127
  json(res, 200, { ok: true, session });
136
128
  }
137
129
  catch (err) {
138
- const mapped = providerAuthErrorBody(err);
130
+ const mapped = mapProviderAuthError(err);
139
131
  json(res, mapped.status, mapped.body);
140
132
  }
141
133
  return true;
@@ -157,7 +149,7 @@ export async function handleProviderRoutes(req, res, ctx) {
157
149
  json(res, 200, { ok: true, session });
158
150
  }
159
151
  catch (err) {
160
- const mapped = providerAuthErrorBody(err);
152
+ const mapped = mapProviderAuthError(err);
161
153
  json(res, mapped.status, mapped.body);
162
154
  }
163
155
  return true;
@@ -177,7 +169,7 @@ export async function handleProviderRoutes(req, res, ctx) {
177
169
  json(res, 200, { ok: true, provider });
178
170
  }
179
171
  catch (err) {
180
- const mapped = providerAuthErrorBody(err);
172
+ const mapped = mapProviderAuthError(err);
181
173
  json(res, mapped.status, mapped.body);
182
174
  }
183
175
  return true;
@@ -330,6 +330,7 @@ export declare function createAskOperatorTool(options: AskOperatorToolOptions):
330
330
  artifacts: Type.TArray<Type.TString>;
331
331
  id: Type.TOptional<Type.TString>;
332
332
  }>)[]]>;
333
+ prepareArguments: (args: unknown) => never;
333
334
  execute: (_toolCallId: string, params: unknown) => Promise<{
334
335
  content: {
335
336
  type: "text";
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { Type } from "typebox";
12
+ import { prepareAskOperatorArguments } from "./prepareToolArguments.js";
12
13
  export class AskOperatorError extends Error {
13
14
  constructor(message) {
14
15
  super(message);
@@ -168,10 +169,11 @@ function parseSubQuestion(value, path) {
168
169
  };
169
170
  }
170
171
  export function parseAskOperatorParams(value) {
171
- if (!isRecord(value)) {
172
+ const prepared = prepareAskOperatorArguments(value);
173
+ if (!isRecord(prepared)) {
172
174
  throw new AskOperatorError("ask_operator params must be an object");
173
175
  }
174
- const kind = value.kind;
176
+ const kind = prepared.kind;
175
177
  if (typeof kind !== "string") {
176
178
  throw new AskOperatorError("kind is required");
177
179
  }
@@ -181,36 +183,36 @@ export function parseAskOperatorParams(value) {
181
183
  kind !== "artifact_backed") {
182
184
  throw new AskOperatorError(`unsupported prompt kind "${kind}" (allowed: free_text, confirm, multi_question, artifact_backed)`);
183
185
  }
184
- const id = optionalId(value.id, "id");
186
+ const id = optionalId(prepared.id, "id");
185
187
  if (kind === "free_text" || kind === "confirm") {
186
188
  return {
187
189
  kind,
188
- message: requireNonEmptyString(value.message, "message"),
190
+ message: requireNonEmptyString(prepared.message, "message"),
189
191
  ...(id !== undefined ? { id } : {}),
190
192
  };
191
193
  }
192
194
  if (kind === "artifact_backed") {
193
- if (!Array.isArray(value.artifacts)) {
195
+ if (!Array.isArray(prepared.artifacts)) {
194
196
  throw new AskOperatorError("artifacts must be an array");
195
197
  }
196
- if (value.artifacts.length < 1) {
198
+ if (prepared.artifacts.length < 1) {
197
199
  throw new AskOperatorError("artifacts must contain at least one path");
198
200
  }
199
- const artifacts = value.artifacts.map((item, index) => requireNonEmptyString(item, `artifacts[${index}]`));
201
+ const artifacts = prepared.artifacts.map((item, index) => requireNonEmptyString(item, `artifacts[${index}]`));
200
202
  return {
201
203
  kind,
202
- message: requireNonEmptyString(value.message, "message"),
204
+ message: requireNonEmptyString(prepared.message, "message"),
203
205
  artifacts,
204
206
  ...(id !== undefined ? { id } : {}),
205
207
  };
206
208
  }
207
- if (!Array.isArray(value.questions)) {
209
+ if (!Array.isArray(prepared.questions)) {
208
210
  throw new AskOperatorError("questions must be an array");
209
211
  }
210
- if (value.questions.length < 1) {
212
+ if (prepared.questions.length < 1) {
211
213
  throw new AskOperatorError("questions must contain at least one item");
212
214
  }
213
- const questions = value.questions.map((item, index) => parseSubQuestion(item, `questions[${index}]`));
215
+ const questions = prepared.questions.map((item, index) => parseSubQuestion(item, `questions[${index}]`));
214
216
  return {
215
217
  kind,
216
218
  questions,
@@ -401,6 +403,7 @@ export function createAskOperatorTool(options) {
401
403
  label: "Ask operator",
402
404
  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
405
  parameters: askOperatorParamsSchemaFor(allowedKinds),
406
+ prepareArguments: prepareAskOperatorArguments,
404
407
  execute: async (_toolCallId, params) => {
405
408
  let prompt;
406
409
  try {
@@ -37,6 +37,7 @@ export declare function createEmitStageEnvelopeTool(capture: EmitCapture, payloa
37
37
  stage_id: Type.TOptional<Type.TString>;
38
38
  notes: Type.TOptional<Type.TString>;
39
39
  }>;
40
+ prepareArguments: (args: unknown) => never;
40
41
  execute: (_toolCallId: string, params: unknown) => Promise<{
41
42
  content: {
42
43
  type: "text";
@@ -15,6 +15,7 @@ import { assertFeedbackLoopAction } from "../envelope/feedbackLoop.js";
15
15
  import { normalizeForkChoice } from "../envelope/forkChoice.js";
16
16
  import { assertEnvelopePayload, compilePayloadSchema, } from "../envelope/payloadSchema.js";
17
17
  import { assertPreEmitChecks, } from "../envelope/preEmitChecks.js";
18
+ import { prepareEmitStageEnvelopeArguments } from "./prepareToolArguments.js";
18
19
  function stringLiteralsSchema(values) {
19
20
  if (values.length === 1) {
20
21
  return Type.Literal(values[0]);
@@ -78,12 +79,13 @@ export function createEmitStageEnvelopeTool(capture, payloadSchema, forkEmitCont
78
79
  stage_id: Type.Optional(Type.String()),
79
80
  notes: Type.Optional(Type.String()),
80
81
  }),
82
+ prepareArguments: prepareEmitStageEnvelopeArguments,
81
83
  execute: async (_toolCallId, params) => {
82
84
  if (capture.envelope) {
83
85
  return toolResult("Envelope already accepted; later emits are ignored.", { advancing: isAdvancingEnvelope(capture.envelope), error: "" }, { terminate: true });
84
86
  }
85
87
  try {
86
- const envelope = assertRequiredEnvelope(params);
88
+ const envelope = assertRequiredEnvelope(prepareEmitStageEnvelopeArguments(params));
87
89
  assertFeedbackLoopAction(envelope, feedbackLoopEmitContext);
88
90
  const isSendBack = envelope.feedback_loop?.action === "send_back";
89
91
  if (envelope.status !== "failure" &&
@@ -0,0 +1,4 @@
1
+ export declare function coerceJsonArrayField(args: Record<string, unknown>, key: string): void;
2
+ export declare function coerceJsonObjectField(args: Record<string, unknown>, key: string): void;
3
+ export declare function prepareAskOperatorArguments(input: unknown): unknown;
4
+ export declare function prepareEmitStageEnvelopeArguments(input: unknown): unknown;
@@ -0,0 +1,79 @@
1
+ function isRecord(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function parseJsonArrayString(value) {
5
+ if (typeof value !== "string")
6
+ return undefined;
7
+ const trimmed = value.trim();
8
+ if (!trimmed.startsWith("["))
9
+ return undefined;
10
+ try {
11
+ const parsed = JSON.parse(trimmed);
12
+ return Array.isArray(parsed) ? parsed : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ function parseJsonObjectString(value) {
19
+ if (typeof value !== "string")
20
+ return undefined;
21
+ const trimmed = value.trim();
22
+ if (!trimmed.startsWith("{"))
23
+ return undefined;
24
+ try {
25
+ const parsed = JSON.parse(trimmed);
26
+ return isRecord(parsed) ? parsed : undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ export function coerceJsonArrayField(args, key) {
33
+ const parsed = parseJsonArrayString(args[key]);
34
+ if (parsed !== undefined) {
35
+ args[key] = parsed;
36
+ }
37
+ }
38
+ export function coerceJsonObjectField(args, key) {
39
+ const parsed = parseJsonObjectString(args[key]);
40
+ if (parsed !== undefined) {
41
+ args[key] = parsed;
42
+ }
43
+ }
44
+ function coerceJsonObjectArrayElements(args, key) {
45
+ const value = args[key];
46
+ if (!Array.isArray(value))
47
+ return;
48
+ args[key] = value.map((item) => parseJsonObjectString(item) ?? item);
49
+ }
50
+ export function prepareAskOperatorArguments(input) {
51
+ if (!isRecord(input))
52
+ return input;
53
+ const args = { ...input };
54
+ coerceJsonArrayField(args, "questions");
55
+ coerceJsonObjectArrayElements(args, "questions");
56
+ coerceJsonArrayField(args, "artifacts");
57
+ return args;
58
+ }
59
+ export function prepareEmitStageEnvelopeArguments(input) {
60
+ if (!isRecord(input))
61
+ return input;
62
+ const args = { ...input };
63
+ coerceJsonObjectField(args, "payload");
64
+ coerceJsonObjectField(args, "feedback_loop");
65
+ coerceJsonArrayField(args, "artifacts");
66
+ coerceJsonArrayField(args, "fork_choice");
67
+ coerceJsonArrayField(args, "checklist_attestations");
68
+ coerceJsonObjectArrayElements(args, "checklist_attestations");
69
+ if (Array.isArray(args.checklist_attestations)) {
70
+ args.checklist_attestations = args.checklist_attestations.map((item) => {
71
+ if (!isRecord(item))
72
+ return item;
73
+ const next = { ...item };
74
+ coerceJsonArrayField(next, "items");
75
+ return next;
76
+ });
77
+ }
78
+ return args;
79
+ }
@@ -10,6 +10,14 @@ export type PipelineConfig = {
10
10
  /** Pipeline-file `$ref` root (`#/schemas/<name>`). */
11
11
  schemas?: Record<string, unknown>;
12
12
  };
13
+ /** An inline pipeline definition authored directly in a `start_run` call, with no backing file. */
14
+ export type InlinePipelineDefinition = {
15
+ id: string;
16
+ stages: Array<Record<string, unknown>>;
17
+ agent?: unknown;
18
+ model?: unknown;
19
+ schemas?: unknown;
20
+ };
13
21
  export type PipelineForkConfig = {
14
22
  select: "one" | "subset";
15
23
  allow_none: boolean;