pi-ultracode 0.1.2 → 0.3.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.
@@ -0,0 +1,363 @@
1
+ import { WorkflowPolicyError } from "./admission.ts";
2
+
3
+ export const MAX_WORKFLOW_ARGS_BYTES = 1024 * 1024;
4
+ export const MAX_WORKFLOW_SCHEMA_BYTES = 256 * 1024;
5
+ export const MAX_WORKFLOW_SCHEMA_DEPTH = 64;
6
+ export const MAX_STRUCTURED_OUTPUT_BYTES = 2 * 1024 * 1024;
7
+ const MAX_JSON_VALUE_DEPTH = 128;
8
+ const MAX_JSON_VALUE_NODES = 65_536;
9
+ const MAX_WORKFLOW_SCHEMA_NODES = 4_096;
10
+
11
+ const SCHEMA_ANNOTATION_KEYS = ["title", "description", "default"] as const;
12
+ const OBJECT_SCHEMA_KEYS = ["properties", "required", "additionalProperties"] as const;
13
+ const ARRAY_SCHEMA_KEYS = ["items", "minItems", "maxItems", "uniqueItems"] as const;
14
+ const STRING_SCHEMA_KEYS = ["minLength", "maxLength"] as const;
15
+ const NUMBER_SCHEMA_KEYS = [
16
+ "minimum",
17
+ "maximum",
18
+ "exclusiveMinimum",
19
+ "exclusiveMaximum",
20
+ "multipleOf",
21
+ ] as const;
22
+ const JSON_SCHEMA_TYPES = new Set(["object", "array", "string", "number", "integer", "boolean", "null"]);
23
+ const REFERENCE_KEYWORDS = new Set(["$ref", "$recursiveRef", "$dynamicRef"]);
24
+
25
+ export function assertWorkflowArgsLimit(args: unknown): void {
26
+ if (args === undefined) return;
27
+ assertJsonByteLimit(args, "workflow args", MAX_WORKFLOW_ARGS_BYTES);
28
+ }
29
+
30
+ export function assertWorkflowSchemaLimit(schema: unknown): void {
31
+ if (schema === undefined || schema === null) return;
32
+ assertJsonByteLimit(schema, "workflow schema", MAX_WORKFLOW_SCHEMA_BYTES);
33
+ assertSchemaTreeBounds(schema);
34
+ if (!isRecord(schema)) {
35
+ throw new WorkflowPolicyError("workflow schema must be a plain JSON object");
36
+ }
37
+ validateSchemaNode(schema);
38
+ }
39
+
40
+ export function assertStructuredOutputLimit(value: unknown): void {
41
+ assertJsonByteLimit(value, "workflow structured output", MAX_STRUCTURED_OUTPUT_BYTES);
42
+ }
43
+
44
+ export function assertWorkflowOutputLimit(value: unknown, label = "workflow output"): void {
45
+ assertJsonByteLimit(value, label, MAX_STRUCTURED_OUTPUT_BYTES);
46
+ }
47
+
48
+ export function assertJsonByteLimit(value: unknown, label: string, maxBytes: number): void {
49
+ assertPlainJsonValue(value, label);
50
+ const serialized = JSON.stringify(value);
51
+ const bytes = Buffer.byteLength(serialized, "utf8");
52
+ if (bytes > maxBytes) {
53
+ throw new WorkflowPolicyError(`${label} exceeds ${maxBytes} bytes`);
54
+ }
55
+ }
56
+
57
+ function assertSchemaTreeBounds(root: unknown): void {
58
+ const stack: Array<{ value: unknown; depth: number }> = [{ value: root, depth: 0 }];
59
+ let nodes = 0;
60
+ while (stack.length > 0) {
61
+ const { value, depth } = stack.pop()!;
62
+ nodes++;
63
+ if (nodes > MAX_WORKFLOW_SCHEMA_NODES) {
64
+ throw new WorkflowPolicyError(`workflow schema exceeds ${MAX_WORKFLOW_SCHEMA_NODES} JSON nodes`);
65
+ }
66
+ if (depth > MAX_WORKFLOW_SCHEMA_DEPTH) {
67
+ throw new WorkflowPolicyError(
68
+ `workflow schema exceeds the ${MAX_WORKFLOW_SCHEMA_DEPTH}-level nesting limit`,
69
+ );
70
+ }
71
+ if (!value || typeof value !== "object") continue;
72
+ for (const child of Array.isArray(value) ? value : Object.values(value)) {
73
+ stack.push({ value: child, depth: depth + 1 });
74
+ }
75
+ }
76
+ }
77
+
78
+ function validateSchemaNode(node: Record<string, unknown>): void {
79
+ for (const key of Object.keys(node)) {
80
+ if (REFERENCE_KEYWORDS.has(key)) {
81
+ throw new WorkflowPolicyError(`workflow schema ${key} is not supported; inline the referenced schema`);
82
+ }
83
+ if (key === "pattern" || key === "patternProperties") {
84
+ throw new WorkflowPolicyError(
85
+ `workflow schema ${key} is not supported because JavaScript regex validation has no reliable execution bound`,
86
+ );
87
+ }
88
+ }
89
+
90
+ validateAnnotations(node);
91
+ const declaredTypes = validateSchemaTypes(node.type);
92
+ const composites = ["anyOf", "allOf"].filter((key) => Object.hasOwn(node, key));
93
+ if (Object.hasOwn(node, "oneOf")) {
94
+ throw new WorkflowPolicyError("workflow schema oneOf is not supported; use non-overlapping anyOf branches");
95
+ }
96
+ if (composites.length > 1) {
97
+ throw new WorkflowPolicyError("workflow schema may use only one of anyOf or allOf at each node");
98
+ }
99
+
100
+ const hasEnum = Object.hasOwn(node, "enum");
101
+ const hasConst = Object.hasOwn(node, "const");
102
+ if (hasEnum && hasConst) {
103
+ throw new WorkflowPolicyError("workflow schema cannot combine enum and const at one node");
104
+ }
105
+ if (composites.length > 0 && (hasEnum || hasConst || declaredTypes.length > 0)) {
106
+ throw new WorkflowPolicyError(
107
+ "workflow schema anyOf/allOf nodes cannot also declare type, enum, or const",
108
+ );
109
+ }
110
+
111
+ if (composites.length > 0) {
112
+ const key = composites[0];
113
+ assertOnlySchemaKeys(node, [...SCHEMA_ANNOTATION_KEYS, key]);
114
+ const branches = node[key];
115
+ if (!Array.isArray(branches) || branches.length === 0) {
116
+ throw new WorkflowPolicyError(`workflow schema ${key} must be a non-empty array of schemas`);
117
+ }
118
+ for (const branch of branches) validateSchemaChild(branch, key);
119
+ return;
120
+ }
121
+
122
+ if (hasEnum || hasConst) {
123
+ assertOnlySchemaKeys(node, [...SCHEMA_ANNOTATION_KEYS, "type", hasEnum ? "enum" : "const"]);
124
+ const values = hasEnum ? node.enum : [node.const];
125
+ if (!Array.isArray(values) || values.length === 0) {
126
+ throw new WorkflowPolicyError("workflow schema enum must be a non-empty array");
127
+ }
128
+ if (declaredTypes.length > 0) {
129
+ for (const value of values) {
130
+ if (!declaredTypes.some((type) => matchesJsonType(value, type))) {
131
+ throw new WorkflowPolicyError("workflow schema enum/const value does not match its declared type");
132
+ }
133
+ }
134
+ }
135
+ return;
136
+ }
137
+
138
+ const inferredTypes = declaredTypes.length > 0 ? declaredTypes : inferSchemaTypes(node);
139
+ const allowed = new Set<string>([...SCHEMA_ANNOTATION_KEYS, "type"]);
140
+ for (const type of inferredTypes) {
141
+ if (type === "object") for (const key of OBJECT_SCHEMA_KEYS) allowed.add(key);
142
+ if (type === "array") for (const key of ARRAY_SCHEMA_KEYS) allowed.add(key);
143
+ if (type === "string") for (const key of STRING_SCHEMA_KEYS) allowed.add(key);
144
+ if (type === "number" || type === "integer") for (const key of NUMBER_SCHEMA_KEYS) allowed.add(key);
145
+ }
146
+ assertOnlySchemaKeys(node, allowed);
147
+
148
+ if (inferredTypes.includes("object")) validateObjectSchema(node);
149
+ if (inferredTypes.includes("array")) validateArraySchema(node);
150
+ if (inferredTypes.includes("string")) {
151
+ validateNonNegativeInteger(node.minLength, "minLength");
152
+ validateNonNegativeInteger(node.maxLength, "maxLength");
153
+ }
154
+ if (inferredTypes.includes("number") || inferredTypes.includes("integer")) {
155
+ for (const key of NUMBER_SCHEMA_KEYS) validateFiniteNumber(node[key], key);
156
+ if (node.multipleOf !== undefined && (node.multipleOf as number) <= 0) {
157
+ throw new WorkflowPolicyError("workflow schema multipleOf must be greater than zero");
158
+ }
159
+ }
160
+ }
161
+
162
+ function validateObjectSchema(node: Record<string, unknown>): void {
163
+ const properties = node.properties;
164
+ if (properties !== undefined && !isRecord(properties)) {
165
+ throw new WorkflowPolicyError("workflow schema properties must be an object of schemas");
166
+ }
167
+ if (properties) {
168
+ for (const child of Object.values(properties)) validateSchemaChild(child, "properties");
169
+ }
170
+
171
+ const required = node.required;
172
+ if (required !== undefined) {
173
+ if (!Array.isArray(required) || required.some((key) => typeof key !== "string")) {
174
+ throw new WorkflowPolicyError("workflow schema required must be an array of property names");
175
+ }
176
+ if (new Set(required).size !== required.length) {
177
+ throw new WorkflowPolicyError("workflow schema required must not contain duplicates");
178
+ }
179
+ for (const key of required) {
180
+ if (!properties || !Object.hasOwn(properties, key)) {
181
+ throw new WorkflowPolicyError(`workflow schema required property ${JSON.stringify(key)} is not defined`);
182
+ }
183
+ }
184
+ }
185
+
186
+ const additional = node.additionalProperties;
187
+ if (additional !== undefined && typeof additional !== "boolean") {
188
+ validateSchemaChild(additional, "additionalProperties");
189
+ }
190
+ }
191
+
192
+ function validateArraySchema(node: Record<string, unknown>): void {
193
+ const items = node.items;
194
+ if (items !== undefined) {
195
+ if (Array.isArray(items)) {
196
+ for (const child of items) validateSchemaChild(child, "items");
197
+ } else {
198
+ validateSchemaChild(items, "items");
199
+ }
200
+ }
201
+ validateNonNegativeInteger(node.minItems, "minItems");
202
+ validateNonNegativeInteger(node.maxItems, "maxItems");
203
+ if (node.uniqueItems !== undefined && typeof node.uniqueItems !== "boolean") {
204
+ throw new WorkflowPolicyError("workflow schema uniqueItems must be a boolean");
205
+ }
206
+ }
207
+
208
+ function validateSchemaChild(value: unknown, keyword: string): void {
209
+ if (!isRecord(value)) {
210
+ throw new WorkflowPolicyError(`workflow schema ${keyword} must contain schema objects`);
211
+ }
212
+ validateSchemaNode(value);
213
+ }
214
+
215
+ function validateSchemaTypes(value: unknown): string[] {
216
+ if (value === undefined) return [];
217
+ const values = Array.isArray(value) ? value : [value];
218
+ if (values.length === 0 || values.some((type) => typeof type !== "string" || !JSON_SCHEMA_TYPES.has(type))) {
219
+ throw new WorkflowPolicyError("workflow schema type must contain supported JSON type names");
220
+ }
221
+ if (new Set(values).size !== values.length) {
222
+ throw new WorkflowPolicyError("workflow schema type must not contain duplicates");
223
+ }
224
+ return values as string[];
225
+ }
226
+
227
+ function inferSchemaTypes(node: Record<string, unknown>): string[] {
228
+ const object = OBJECT_SCHEMA_KEYS.some((key) => Object.hasOwn(node, key));
229
+ const array = ARRAY_SCHEMA_KEYS.some((key) => Object.hasOwn(node, key));
230
+ const scalar = [...STRING_SCHEMA_KEYS, ...NUMBER_SCHEMA_KEYS].find((key) => Object.hasOwn(node, key));
231
+ if (scalar) {
232
+ throw new WorkflowPolicyError(`workflow schema ${scalar} requires an explicit type`);
233
+ }
234
+ if (object && array) {
235
+ throw new WorkflowPolicyError("workflow schema keywords require one unambiguous declared type");
236
+ }
237
+ return object ? ["object"] : array ? ["array"] : [];
238
+ }
239
+
240
+ function validateAnnotations(node: Record<string, unknown>): void {
241
+ if (node.title !== undefined && typeof node.title !== "string") {
242
+ throw new WorkflowPolicyError("workflow schema title must be a string");
243
+ }
244
+ if (node.description !== undefined && typeof node.description !== "string") {
245
+ throw new WorkflowPolicyError("workflow schema description must be a string");
246
+ }
247
+ }
248
+
249
+ function validateNonNegativeInteger(value: unknown, keyword: string): void {
250
+ if (value === undefined) return;
251
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
252
+ throw new WorkflowPolicyError(`workflow schema ${keyword} must be a non-negative integer`);
253
+ }
254
+ }
255
+
256
+ function validateFiniteNumber(value: unknown, keyword: string): void {
257
+ if (value === undefined) return;
258
+ if (typeof value !== "number" || !Number.isFinite(value)) {
259
+ throw new WorkflowPolicyError(`workflow schema ${keyword} must be a finite number`);
260
+ }
261
+ }
262
+
263
+ function assertOnlySchemaKeys(node: Record<string, unknown>, allowedKeys: Iterable<string>): void {
264
+ const allowed = allowedKeys instanceof Set ? allowedKeys : new Set(allowedKeys);
265
+ for (const key of Object.keys(node)) {
266
+ if (!allowed.has(key)) {
267
+ throw new WorkflowPolicyError(`workflow schema contains unsupported keyword ${key}`);
268
+ }
269
+ }
270
+ }
271
+
272
+ function matchesJsonType(value: unknown, type: string): boolean {
273
+ switch (type) {
274
+ case "null": return value === null;
275
+ case "boolean": return typeof value === "boolean";
276
+ case "string": return typeof value === "string";
277
+ case "number": return typeof value === "number" && Number.isFinite(value);
278
+ case "integer": return typeof value === "number" && Number.isInteger(value);
279
+ case "array": return Array.isArray(value);
280
+ case "object": return isRecord(value);
281
+ default: return false;
282
+ }
283
+ }
284
+
285
+ function isRecord(value: unknown): value is Record<string, unknown> {
286
+ return !!value && typeof value === "object" && !Array.isArray(value);
287
+ }
288
+
289
+ function assertPlainJsonValue(root: unknown, label: string): void {
290
+ type Visit = { value: unknown; depth: number };
291
+ const seen = new WeakSet<object>();
292
+ const stack: Visit[] = [{ value: root, depth: 0 }];
293
+ let nodes = 0;
294
+ while (stack.length > 0) {
295
+ const visit = stack.pop()!;
296
+ const value = visit.value;
297
+ nodes++;
298
+ if (nodes > MAX_JSON_VALUE_NODES) {
299
+ throw new WorkflowPolicyError(`${label} exceeds ${MAX_JSON_VALUE_NODES} JSON nodes`);
300
+ }
301
+ if (value === null || typeof value === "string" || typeof value === "boolean") continue;
302
+ if (typeof value === "number") {
303
+ if (Number.isFinite(value) && !Object.is(value, -0)) continue;
304
+ throw new WorkflowPolicyError(`${label} must contain only durable finite JSON numbers (negative zero is unsupported)`);
305
+ }
306
+ if (!value || typeof value !== "object") {
307
+ throw new WorkflowPolicyError(`${label} must be a plain JSON value`);
308
+ }
309
+ if (visit.depth > MAX_JSON_VALUE_DEPTH) {
310
+ throw new WorkflowPolicyError(`${label} exceeds the ${MAX_JSON_VALUE_DEPTH}-level JSON nesting limit`);
311
+ }
312
+ if (seen.has(value)) {
313
+ throw new WorkflowPolicyError(`${label} must be a JSON tree without repeated object references`);
314
+ }
315
+ seen.add(value);
316
+
317
+ let descriptors: Record<string, PropertyDescriptor>;
318
+ let symbols: symbol[];
319
+ try {
320
+ descriptors = Object.getOwnPropertyDescriptors(value);
321
+ symbols = Object.getOwnPropertySymbols(value);
322
+ } catch (error) {
323
+ const detail = error instanceof Error ? error.message : String(error);
324
+ throw new WorkflowPolicyError(`${label} must be a plain JSON value: ${detail}`);
325
+ }
326
+ if (symbols.length > 0) throw new WorkflowPolicyError(`${label} must not contain symbol properties`);
327
+
328
+ if (Array.isArray(value)) {
329
+ const entries = Object.entries(descriptors).filter(([key]) => key !== "length");
330
+ if (entries.length !== value.length) {
331
+ throw new WorkflowPolicyError(`${label} arrays must not be sparse or contain extra properties`);
332
+ }
333
+ for (const [key, descriptor] of entries) {
334
+ const index = Number(key);
335
+ if (!Number.isInteger(index) || index < 0 || String(index) !== key || index >= value.length) {
336
+ throw new WorkflowPolicyError(`${label} arrays must not contain extra properties`);
337
+ }
338
+ if (!("value" in descriptor) || !descriptor.enumerable) {
339
+ throw new WorkflowPolicyError(`${label} must not contain accessors or hidden properties`);
340
+ }
341
+ stack.push({ value: descriptor.value, depth: visit.depth + 1 });
342
+ }
343
+ continue;
344
+ }
345
+
346
+ let prototype: object | null;
347
+ try {
348
+ prototype = Object.getPrototypeOf(value);
349
+ } catch (error) {
350
+ const detail = error instanceof Error ? error.message : String(error);
351
+ throw new WorkflowPolicyError(`${label} must be a plain JSON object: ${detail}`);
352
+ }
353
+ if (prototype !== Object.prototype && prototype !== null) {
354
+ throw new WorkflowPolicyError(`${label} must contain only plain JSON objects and arrays`);
355
+ }
356
+ for (const descriptor of Object.values(descriptors)) {
357
+ if (!("value" in descriptor) || !descriptor.enumerable) {
358
+ throw new WorkflowPolicyError(`${label} must not contain accessors or hidden properties`);
359
+ }
360
+ stack.push({ value: descriptor.value, depth: visit.depth + 1 });
361
+ }
362
+ }
363
+ }
@@ -12,6 +12,7 @@ import { randomBytes } from "node:crypto";
12
12
  import * as fs from "node:fs";
13
13
  import * as os from "node:os";
14
14
  import * as path from "node:path";
15
+ import { ensurePrivateArtifactDirectory, writeArtifactFile } from "./run-artifacts.ts";
15
16
 
16
17
  export interface Worktree {
17
18
  path: string;
@@ -247,11 +248,12 @@ export function writeRescuePatch(
247
248
  label: string,
248
249
  patch: string,
249
250
  ): string {
250
- fs.mkdirSync(dir, { recursive: true });
251
+ ensurePrivateArtifactDirectory(dir);
251
252
  const safeRun = runId.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 24) || "run";
252
253
  const safeLabel = label.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 40) || "agent";
253
- const file = path.join(dir, `${safeRun}-${id}-${safeLabel}.patch`);
254
- fs.writeFileSync(file, patch.endsWith("\n") ? patch : `${patch}\n`);
254
+ const nonce = randomBytes(6).toString("hex");
255
+ const file = path.join(dir, `${safeRun}-${id}-${safeLabel}-${nonce}.patch`);
256
+ writeArtifactFile(file, patch.endsWith("\n") ? patch : `${patch}\n`, { trustedRoot: dir });
255
257
  return file;
256
258
  }
257
259
 
@@ -1,5 +1,11 @@
1
1
  /**
2
2
  * Ambient globals available inside pi-ultracode workflow scripts.
3
+ * Orchestration promises must be directly awaited or returned; native promise
4
+ * chains/combinators and dynamic method calls are intentionally unavailable.
5
+ * Helpers containing orchestration must be directly declared functions/function
6
+ * variables or static methods on declared objects/stored class instances; aliases,
7
+ * member assignment,
8
+ * this-method forwarding, and temporary/awaited factory receivers fail closed.
3
9
  *
4
10
  * Add this to a saved workflow file for editor IntelliSense:
5
11
  *
@@ -17,7 +23,8 @@ declare global {
17
23
  }
18
24
 
19
25
  interface JsonSchema {
20
- type?: string | string[];
26
+ type?: "object" | "array" | "string" | "number" | "integer" | "boolean" | "null"
27
+ | Array<"object" | "array" | "string" | "number" | "integer" | "boolean" | "null">;
21
28
  properties?: Record<string, JsonSchema>;
22
29
  items?: JsonSchema | JsonSchema[];
23
30
  required?: string[];
@@ -25,9 +32,30 @@ declare global {
25
32
  enum?: unknown[];
26
33
  const?: unknown;
27
34
  anyOf?: JsonSchema[];
28
- oneOf?: JsonSchema[];
35
+ allOf?: JsonSchema[];
36
+ minItems?: number;
37
+ maxItems?: number;
38
+ uniqueItems?: boolean;
39
+ minLength?: number;
40
+ maxLength?: number;
41
+ minimum?: number;
42
+ maximum?: number;
43
+ exclusiveMinimum?: number;
44
+ exclusiveMaximum?: number;
45
+ multipleOf?: number;
46
+ title?: string;
29
47
  description?: string;
30
- [key: string]: unknown;
48
+ default?: unknown;
49
+ /** Unsupported: exact-one unions are not normalized by the bounded subset. */
50
+ oneOf?: never;
51
+ /** Unsupported: external/internal references are not resolved in workflows. */
52
+ $ref?: never;
53
+ $recursiveRef?: never;
54
+ $dynamicRef?: never;
55
+ /** Unsupported: JavaScript regex validation has no reliable execution bound. */
56
+ pattern?: never;
57
+ patternProperties?: never;
58
+ format?: never;
31
59
  }
32
60
 
33
61
  interface WorkflowAgentOptions {
@@ -35,7 +63,7 @@ declare global {
35
63
  label?: string;
36
64
  /** Assign this agent to a progress group explicitly. */
37
65
  phase?: string;
38
- /** JSON Schema for structured output; agent() then returns the validated object. */
66
+ /** Bounded JSON Schema subset (max 256 KiB / 64 levels); every agent output is capped at 2 MiB. */
39
67
  schema?: JsonSchema;
40
68
  /** Override model and optionally effort, e.g. "sonnet", "anthropic/...:high", or "gpt-5.6-sol:max". */
41
69
  model?: string;
@@ -45,25 +73,38 @@ declare global {
45
73
  agentType?: string;
46
74
  }
47
75
 
48
- interface WorkflowBudget {
49
- total: number | null;
50
- spent(): number;
51
- remaining(): number;
52
- }
76
+ /** Spawn a subagent. Its strict JSON result is capped at 2 MiB; ordinary started-run failures resolve null. */
77
+ function agent<T = string>(prompt: string, options?: WorkflowAgentOptions): Promise<T | null>;
53
78
 
54
- /** Spawn a subagent. Returns final text, or a validated object when opts.schema is set. */
55
- function agent<T = unknown>(prompt: string, options?: WorkflowAgentOptions): Promise<T>;
79
+ interface WorkflowParallelOptions {
80
+ /**
81
+ * Agent slots to reserve atomically before any thunk starts. Defaults to thunks.length.
82
+ * Must be at least thunks.length. Raise it when branches need multiple agent() calls;
83
+ * calls beyond the reserved amount fail the entire workflow instead of borrowing capacity.
84
+ */
85
+ reserveAgents?: number;
86
+ }
56
87
 
57
- /** Run independent tasks concurrently (a barrier). Pass functions, not promises. */
58
- function parallel<T = unknown>(thunks: Array<() => Promise<T>>): Promise<T[]>;
88
+ /**
89
+ * Run independent tasks concurrently; infer values from thunks and represent a
90
+ * branch throw as null. Workflow-wide cancellation/policy failures propagate and are
91
+ * not downgraded to null. The whole panel reserves agent slots before any branch starts.
92
+ */
93
+ function parallel<T = string>(thunks: Array<() => T | Promise<T>>, options?: WorkflowParallelOptions): Promise<Array<Awaited<T> | null>>;
59
94
 
60
- /** Run each item through sequential stages while items fan out (no barrier). */
61
- function pipeline<TItem = unknown, TResult = unknown>(
95
+ /**
96
+ * Fan items through sequential stages. A stage input can be null when a prior
97
+ * stage (including agent()) returned null; an ordinary throwing item branch ends as null.
98
+ * Workflow-wide cancellation and policy failures propagate and are not downgraded to null.
99
+ */
100
+ function pipeline<TItem = unknown, TResult = string>(
62
101
  items: TItem[],
63
- ...stages: Array<(previous: unknown, original: TItem, index: number) => TResult | Promise<TResult>>
64
- ): Promise<TResult[]>;
102
+ ...stages: Array<
103
+ (previous: unknown | null, original: TItem, index: number) => TResult | Promise<TResult>
104
+ >
105
+ ): Promise<Array<Awaited<TResult> | null>>;
65
106
 
66
- /** Run a saved workflow (by name) or { scriptPath } inline; one level of nesting. */
107
+ /** Run a saved workflow with strict JSON args (1 MiB) and output (2 MiB); one level of nesting. */
67
108
  function workflow<T = unknown>(nameOrRef: string | { scriptPath: string }, args?: unknown): Promise<T>;
68
109
 
69
110
  /** Mark the current phase for progress grouping. */
@@ -72,7 +113,7 @@ declare global {
72
113
  /** Append a workflow-level log line. */
73
114
  function log(message: unknown): void;
74
115
 
75
- /** JSON value passed via the tool's `args` parameter. */
116
+ /** JSON value passed via the tool's `args` parameter (max 1 MiB serialized). */
76
117
  const args: unknown;
77
118
 
78
119
  /** Working directory for the workflow and its subagents. */
@@ -80,7 +121,4 @@ declare global {
80
121
 
81
122
  /** Deterministic process shim exposing only cwd(). */
82
123
  const process: { cwd(): string };
83
-
84
- /** Real output-token budget tracker for the run. */
85
- const budget: WorkflowBudget;
86
124
  }