@osolmaz/pi-workflows 0.11.2 → 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.
- package/README.md +11 -7
- package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
- package/dist/builtins/autoimplement-command-batches.js +258 -0
- package/dist/builtins/autoimplement-command-batches.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +29 -3
- package/dist/builtins/autoimplement.workflow.js +876 -231
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +3 -1
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +2 -0
- package/dist/builtins/index.js +1 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/sanity-check-session.d.ts +17 -0
- package/dist/builtins/sanity-check-session.js +168 -0
- package/dist/builtins/sanity-check-session.js.map +1 -0
- package/dist/builtins/sanity-check.workflow.d.ts +83 -0
- package/dist/builtins/sanity-check.workflow.js +398 -0
- package/dist/builtins/sanity-check.workflow.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +90 -2
- package/dist/controllers/sqlite.js +380 -5
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/decision-channels.js +7 -7
- package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
- package/dist/extension/deferred-turn-coordinator.js +143 -0
- package/dist/extension/deferred-turn-coordinator.js.map +1 -0
- package/dist/extension/deferred-turn.d.ts +44 -0
- package/dist/extension/deferred-turn.js +110 -0
- package/dist/extension/deferred-turn.js.map +1 -0
- package/dist/extension/index.js +286 -47
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/step-message.d.ts +1 -0
- package/dist/extension/step-message.js.map +1 -1
- package/dist/herdr/setup.js +4 -4
- package/dist/workflows/command-batch.d.ts +38 -0
- package/dist/workflows/command-batch.js +176 -0
- package/dist/workflows/command-batch.js.map +1 -0
- package/dist/workflows/engine.js +8 -4
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -0
- package/dist/workflows/index.js +1 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +2 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/shell.d.ts +4 -0
- package/dist/workflows/shell.js +6 -0
- package/dist/workflows/shell.js.map +1 -1
- package/dist/workflows/store.js +3 -1
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +4 -3
- package/docs/2026-08-18-herdr-piw-plan.md +1 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
- package/docs/CONTROLLERS.md +2 -2
- package/docs/DEFERRED_TURNS.md +298 -0
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +7 -7
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +3 -3
- package/docs/MONITOR.md +2 -2
- package/docs/WORKFLOW_COMPOSITION.md +2 -2
- package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
- package/docs/WORKFLOW_UPDATES.md +28 -5
- package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
- package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
- package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
- package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
- package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
- package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +175 -0
- package/docs/run-bundles.md +3 -1
- package/docs/workflows.md +62 -19
- package/examples/workflows/command-batch.workflow.ts +50 -0
- package/examples/workflows/sanity-check.workflow.ts +1 -0
- package/herdr-plugin.toml +3 -3
- package/package.json +1 -1
- package/schemas/decision-presentation-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v2.schema.json +1 -1
- package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
- package/schemas/human-decision-cancellation-v1.schema.json +1 -1
- package/schemas/human-decision-continuation-v1.schema.json +1 -1
- package/schemas/human-decision-delivery-v1.schema.json +1 -1
- package/schemas/human-decision-delivery-v2.schema.json +1 -1
- package/schemas/human-decision-receipt-v1.schema.json +1 -1
- package/schemas/human-decision-receipt-v2.schema.json +1 -1
- package/schemas/human-decision-request-v1.schema.json +1 -1
- package/schemas/human-decision-request-v2.schema.json +1 -1
- package/schemas/human-decision-resolution-v1.schema.json +1 -1
- package/schemas/human-decision-resolution-v2.schema.json +1 -1
- package/schemas/human-decision-settlement-v1.schema.json +1 -1
- package/skills/autodoc/SKILL.md +33 -2
- package/skills/autoimplement/SKILL.md +66 -12
- package/skills/autoplan/SKILL.md +26 -2
- package/skills/monitor/SKILL.md +35 -21
- package/skills/pi-workflows/SKILL.md +19 -5
- package/src/builtins/autoimplement-command-batches.ts +364 -0
- package/src/builtins/autoimplement.workflow.ts +1065 -261
- package/src/builtins/catalog.ts +3 -1
- package/src/builtins/index.ts +13 -0
- package/src/builtins/sanity-check-session.ts +205 -0
- package/src/builtins/sanity-check.workflow.ts +624 -0
- package/src/controllers/sqlite.ts +580 -7
- package/src/extension/decision-channels.ts +7 -7
- package/src/extension/deferred-turn-coordinator.ts +171 -0
- package/src/extension/deferred-turn.ts +166 -0
- package/src/extension/index.ts +359 -49
- package/src/extension/step-message.ts +1 -0
- package/src/herdr/setup.ts +4 -4
- package/src/workflows/command-batch.ts +254 -0
- package/src/workflows/engine.ts +8 -5
- package/src/workflows/index.ts +15 -0
- package/src/workflows/schema.ts +2 -1
- package/src/workflows/shell.ts +10 -0
- package/src/workflows/store.ts +3 -1
- package/src/workflows/types.ts +4 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { CancelledError, TimeoutError, errorMessage, isAbortLikeError } from "./errors.js";
|
|
3
|
+
import { runShellAction, shellOutputTruncation, shellResultFromError } from "./shell.js";
|
|
4
|
+
import type { MaybePromise, ShellActionResult } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export const COMMAND_BATCH_RESULT_SCHEMA = "pi-workflows.command-batch-result.v1";
|
|
7
|
+
export const MAX_COMMAND_BATCH_ITEMS = 64;
|
|
8
|
+
export const MAX_COMMAND_BATCH_CONCURRENCY = 8;
|
|
9
|
+
export const MAX_COMMAND_BATCH_TIMEOUT_MS = 60 * 60_000;
|
|
10
|
+
export const MAX_COMMAND_BATCH_OUTPUT_CHARS = 1_000_000;
|
|
11
|
+
|
|
12
|
+
const MAX_ERROR_CHARS = 2_000;
|
|
13
|
+
const ITEM_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
|
|
14
|
+
|
|
15
|
+
export type CommandBatchItem = {
|
|
16
|
+
id: string;
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
cwd: string;
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
maxOutputChars: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type CommandBatchRequest = {
|
|
25
|
+
items: CommandBatchItem[];
|
|
26
|
+
maxConcurrency: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type CommandBatchItemOutcome = "succeeded" | "failed" | "timedOut" | "cancelled";
|
|
30
|
+
|
|
31
|
+
export type CommandBatchItemResult = ShellActionResult & {
|
|
32
|
+
id: string;
|
|
33
|
+
outcome: CommandBatchItemOutcome;
|
|
34
|
+
stdoutTruncated: boolean;
|
|
35
|
+
stderrTruncated: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type CommandBatchResult = {
|
|
40
|
+
schema: typeof COMMAND_BATCH_RESULT_SCHEMA;
|
|
41
|
+
items: CommandBatchItemResult[];
|
|
42
|
+
completed: number;
|
|
43
|
+
total: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type RunCommandBatchOptions = {
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
onItemSettled?: (
|
|
49
|
+
result: CommandBatchItemResult,
|
|
50
|
+
completed: number,
|
|
51
|
+
total: number,
|
|
52
|
+
) => MaybePromise<void>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function validateCommandBatchRequest(value: unknown): CommandBatchRequest {
|
|
56
|
+
const request = requireRecord(value, "command batch request");
|
|
57
|
+
for (const key of Object.keys(request)) {
|
|
58
|
+
if (key !== "items" && key !== "maxConcurrency") {
|
|
59
|
+
throw new Error(`command batch request.${key} is not supported`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(request.items)) {
|
|
63
|
+
throw new Error("command batch items must be an array");
|
|
64
|
+
}
|
|
65
|
+
if (request.items.length > MAX_COMMAND_BATCH_ITEMS) {
|
|
66
|
+
throw new Error(`command batch items must contain at most ${MAX_COMMAND_BATCH_ITEMS} entries`);
|
|
67
|
+
}
|
|
68
|
+
const ids = new Set<string>();
|
|
69
|
+
const items = request.items.map((item, index) => {
|
|
70
|
+
const normalized = validateItem(item, index);
|
|
71
|
+
if (ids.has(normalized.id)) {
|
|
72
|
+
throw new Error(`command batch item id is duplicated: ${normalized.id}`);
|
|
73
|
+
}
|
|
74
|
+
ids.add(normalized.id);
|
|
75
|
+
return normalized;
|
|
76
|
+
});
|
|
77
|
+
const maxConcurrency = positiveInteger(
|
|
78
|
+
request.maxConcurrency,
|
|
79
|
+
"command batch maxConcurrency",
|
|
80
|
+
MAX_COMMAND_BATCH_CONCURRENCY,
|
|
81
|
+
);
|
|
82
|
+
return { items, maxConcurrency };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function runCommandBatch(
|
|
86
|
+
input: CommandBatchRequest,
|
|
87
|
+
options: RunCommandBatchOptions = {},
|
|
88
|
+
): Promise<CommandBatchResult> {
|
|
89
|
+
const request = validateCommandBatchRequest(input);
|
|
90
|
+
const total = request.items.length;
|
|
91
|
+
if (total === 0) {
|
|
92
|
+
return { schema: COMMAND_BATCH_RESULT_SCHEMA, items: [], completed: 0, total: 0 };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const results = Array.from<CommandBatchItemResult | undefined>({ length: total });
|
|
96
|
+
const signal = options.signal;
|
|
97
|
+
let nextIndex = 0;
|
|
98
|
+
let completed = 0;
|
|
99
|
+
|
|
100
|
+
const worker = async () => {
|
|
101
|
+
while (!signal?.aborted) {
|
|
102
|
+
const index = nextIndex;
|
|
103
|
+
if (index >= total) return;
|
|
104
|
+
nextIndex += 1;
|
|
105
|
+
if (signal?.aborted) return;
|
|
106
|
+
|
|
107
|
+
const item = request.items[index];
|
|
108
|
+
if (item === undefined) return;
|
|
109
|
+
const result = await runItem(item, signal);
|
|
110
|
+
results[index] = result;
|
|
111
|
+
completed += 1;
|
|
112
|
+
try {
|
|
113
|
+
await options.onItemSettled?.(result, completed, total);
|
|
114
|
+
} catch {
|
|
115
|
+
// Completion callbacks are observational and cannot change batch execution.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const workerCount = Math.min(request.maxConcurrency, total);
|
|
121
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
122
|
+
|
|
123
|
+
for (let index = 0; index < total; index += 1) {
|
|
124
|
+
if (results[index] !== undefined) continue;
|
|
125
|
+
const item = request.items[index];
|
|
126
|
+
if (item !== undefined) results[index] = cancelledResult(item);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
schema: COMMAND_BATCH_RESULT_SCHEMA,
|
|
131
|
+
items: results as CommandBatchItemResult[],
|
|
132
|
+
completed,
|
|
133
|
+
total,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function validateItem(value: unknown, index: number): CommandBatchItem {
|
|
138
|
+
const item = requireRecord(value, `command batch items[${index}]`);
|
|
139
|
+
const allowed = new Set(["id", "command", "args", "cwd", "timeoutMs", "maxOutputChars"]);
|
|
140
|
+
for (const key of Object.keys(item)) {
|
|
141
|
+
if (!allowed.has(key)) throw new Error(`command batch items[${index}].${key} is not supported`);
|
|
142
|
+
}
|
|
143
|
+
const id = requireString(item.id, `command batch items[${index}].id`);
|
|
144
|
+
if (!ITEM_ID_PATTERN.test(id)) {
|
|
145
|
+
throw new Error(`command batch items[${index}].id is invalid`);
|
|
146
|
+
}
|
|
147
|
+
const command = requireString(item.command, `command batch items[${index}].command`);
|
|
148
|
+
if (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")) {
|
|
149
|
+
throw new Error(`command batch items[${index}].args must be an array of strings`);
|
|
150
|
+
}
|
|
151
|
+
const cwd = requireString(item.cwd, `command batch items[${index}].cwd`);
|
|
152
|
+
if (!path.isAbsolute(cwd)) {
|
|
153
|
+
throw new Error(`command batch items[${index}].cwd must be absolute`);
|
|
154
|
+
}
|
|
155
|
+
const timeoutMs = positiveInteger(
|
|
156
|
+
item.timeoutMs,
|
|
157
|
+
`command batch items[${index}].timeoutMs`,
|
|
158
|
+
MAX_COMMAND_BATCH_TIMEOUT_MS,
|
|
159
|
+
);
|
|
160
|
+
const maxOutputChars = positiveInteger(
|
|
161
|
+
item.maxOutputChars,
|
|
162
|
+
`command batch items[${index}].maxOutputChars`,
|
|
163
|
+
MAX_COMMAND_BATCH_OUTPUT_CHARS,
|
|
164
|
+
);
|
|
165
|
+
return { id, command, args: [...item.args] as string[], cwd, timeoutMs, maxOutputChars };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function runItem(
|
|
169
|
+
item: CommandBatchItem,
|
|
170
|
+
signal?: AbortSignal,
|
|
171
|
+
): Promise<CommandBatchItemResult> {
|
|
172
|
+
try {
|
|
173
|
+
const result = await runShellAction(
|
|
174
|
+
{
|
|
175
|
+
command: item.command,
|
|
176
|
+
args: item.args,
|
|
177
|
+
cwd: item.cwd,
|
|
178
|
+
timeoutMs: item.timeoutMs,
|
|
179
|
+
maxOutputChars: item.maxOutputChars,
|
|
180
|
+
},
|
|
181
|
+
signal,
|
|
182
|
+
);
|
|
183
|
+
return itemResult(item.id, "succeeded", result);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const result = shellResultFromError(error) ?? emptyShellResult(item);
|
|
186
|
+
const outcome =
|
|
187
|
+
error instanceof TimeoutError
|
|
188
|
+
? "timedOut"
|
|
189
|
+
: error instanceof CancelledError || isAbortLikeError(error)
|
|
190
|
+
? "cancelled"
|
|
191
|
+
: "failed";
|
|
192
|
+
return itemResult(item.id, outcome, result, boundedError(error));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function itemResult(
|
|
197
|
+
id: string,
|
|
198
|
+
outcome: CommandBatchItemOutcome,
|
|
199
|
+
result: ShellActionResult,
|
|
200
|
+
error?: string,
|
|
201
|
+
): CommandBatchItemResult {
|
|
202
|
+
const truncation = shellOutputTruncation(result);
|
|
203
|
+
return {
|
|
204
|
+
id,
|
|
205
|
+
outcome,
|
|
206
|
+
...result,
|
|
207
|
+
stdoutTruncated: truncation.stdout,
|
|
208
|
+
stderrTruncated: truncation.stderr,
|
|
209
|
+
...(error !== undefined ? { error } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function cancelledResult(item: CommandBatchItem): CommandBatchItemResult {
|
|
214
|
+
return itemResult(item.id, "cancelled", emptyShellResult(item), "Command was not started");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function emptyShellResult(item: CommandBatchItem): ShellActionResult {
|
|
218
|
+
return {
|
|
219
|
+
command: item.command,
|
|
220
|
+
args: [...item.args],
|
|
221
|
+
cwd: item.cwd,
|
|
222
|
+
stdout: "",
|
|
223
|
+
stderr: "",
|
|
224
|
+
exitCode: null,
|
|
225
|
+
signal: null,
|
|
226
|
+
durationMs: 0,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function boundedError(error: unknown): string {
|
|
231
|
+
const message = errorMessage(error);
|
|
232
|
+
return message.length <= MAX_ERROR_CHARS ? message : `${message.slice(0, MAX_ERROR_CHARS)}…`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function positiveInteger(value: unknown, label: string, maximum: number): number {
|
|
236
|
+
if (!Number.isSafeInteger(value) || (value as number) < 1 || (value as number) > maximum) {
|
|
237
|
+
throw new Error(`${label} must be an integer from 1 through ${maximum}`);
|
|
238
|
+
}
|
|
239
|
+
return value as number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
|
243
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
244
|
+
throw new Error(`${label} must be an object`);
|
|
245
|
+
}
|
|
246
|
+
return value as Record<string, unknown>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function requireString(value: unknown, label: string): string {
|
|
250
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
251
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
package/src/workflows/engine.ts
CHANGED
|
@@ -983,9 +983,11 @@ export class WorkflowEngine {
|
|
|
983
983
|
if (abort.signal.aborted) {
|
|
984
984
|
throw abortError(abort.signal);
|
|
985
985
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
986
|
+
if (timeoutMs !== null) {
|
|
987
|
+
timer = setTimeout(() => {
|
|
988
|
+
abort.abort(new TimeoutError(timeoutMs));
|
|
989
|
+
}, timeoutMs);
|
|
990
|
+
}
|
|
989
991
|
this.activeAttempt = { runDir, state, nodeId, attemptId, signal: abort.signal };
|
|
990
992
|
const dispatched = this.dispatchNode(
|
|
991
993
|
workflow,
|
|
@@ -1047,8 +1049,9 @@ export class WorkflowEngine {
|
|
|
1047
1049
|
node: WorkflowNodeDefinition,
|
|
1048
1050
|
context: WorkflowNodeContext,
|
|
1049
1051
|
abort: AbortController,
|
|
1050
|
-
): Promise<number> {
|
|
1052
|
+
): Promise<number | null> {
|
|
1051
1053
|
const configured = node.timeoutMs;
|
|
1054
|
+
if (configured === null) return null;
|
|
1052
1055
|
if (typeof configured !== "function") {
|
|
1053
1056
|
return assertValidTimeout(configured ?? this.defaultNodeTimeoutMs);
|
|
1054
1057
|
}
|
|
@@ -1061,7 +1064,7 @@ export class WorkflowEngine {
|
|
|
1061
1064
|
Promise.resolve(configured(context)),
|
|
1062
1065
|
abortRejection(abort.signal),
|
|
1063
1066
|
]);
|
|
1064
|
-
return assertValidTimeout(resolved);
|
|
1067
|
+
return resolved === null ? null : assertValidTimeout(resolved);
|
|
1065
1068
|
} finally {
|
|
1066
1069
|
clearTimeout(timer);
|
|
1067
1070
|
}
|
package/src/workflows/index.ts
CHANGED
|
@@ -74,6 +74,21 @@ export {
|
|
|
74
74
|
type WorkflowSearchPaths,
|
|
75
75
|
} from "./loader.js";
|
|
76
76
|
export { renderShellCommand, runShellAction } from "./shell.js";
|
|
77
|
+
export {
|
|
78
|
+
COMMAND_BATCH_RESULT_SCHEMA,
|
|
79
|
+
MAX_COMMAND_BATCH_CONCURRENCY,
|
|
80
|
+
MAX_COMMAND_BATCH_ITEMS,
|
|
81
|
+
MAX_COMMAND_BATCH_OUTPUT_CHARS,
|
|
82
|
+
MAX_COMMAND_BATCH_TIMEOUT_MS,
|
|
83
|
+
runCommandBatch,
|
|
84
|
+
validateCommandBatchRequest,
|
|
85
|
+
type CommandBatchItem,
|
|
86
|
+
type CommandBatchItemOutcome,
|
|
87
|
+
type CommandBatchItemResult,
|
|
88
|
+
type CommandBatchRequest,
|
|
89
|
+
type CommandBatchResult,
|
|
90
|
+
type RunCommandBatchOptions,
|
|
91
|
+
} from "./command-batch.js";
|
|
77
92
|
export {
|
|
78
93
|
estimateProgress,
|
|
79
94
|
formatProgressLine,
|
package/src/workflows/schema.ts
CHANGED
|
@@ -35,10 +35,11 @@ function assertOptionalFunction(value: unknown, description: string): void {
|
|
|
35
35
|
function assertCommonNodeFields(node: WorkflowNodeDefinition, nodeId: string): void {
|
|
36
36
|
if (
|
|
37
37
|
node.timeoutMs !== undefined &&
|
|
38
|
+
node.timeoutMs !== null &&
|
|
38
39
|
typeof node.timeoutMs !== "function" &&
|
|
39
40
|
(typeof node.timeoutMs !== "number" || !Number.isFinite(node.timeoutMs) || node.timeoutMs <= 0)
|
|
40
41
|
) {
|
|
41
|
-
fail(`node ${nodeId} timeoutMs must be a finite positive number or function`);
|
|
42
|
+
fail(`node ${nodeId} timeoutMs must be null, a finite positive number, or a function`);
|
|
42
43
|
}
|
|
43
44
|
if (node.statusDetail !== undefined && typeof node.statusDetail !== "string") {
|
|
44
45
|
fail(`node ${nodeId} statusDetail must be a string`);
|
package/src/workflows/shell.ts
CHANGED
|
@@ -26,6 +26,16 @@ export function renderShellCommand(command: string, args: string[]): string {
|
|
|
26
26
|
return renderedArgs.length > 0 ? `${command} ${renderedArgs}` : command;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export function shellOutputTruncation(result: ShellActionResult): {
|
|
30
|
+
stdout: boolean;
|
|
31
|
+
stderr: boolean;
|
|
32
|
+
} {
|
|
33
|
+
return {
|
|
34
|
+
stdout: result.stdout.endsWith(TRUNCATION_MARKER),
|
|
35
|
+
stderr: result.stderr.endsWith(TRUNCATION_MARKER),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
function shellFailure(
|
|
30
40
|
spec: ShellActionExecution,
|
|
31
41
|
args: string[],
|
package/src/workflows/store.ts
CHANGED
|
@@ -1417,7 +1417,9 @@ function snapshotNode(
|
|
|
1417
1417
|
: exit !== undefined
|
|
1418
1418
|
? { includeTransition: "exit" as const }
|
|
1419
1419
|
: {}),
|
|
1420
|
-
...(typeof node.timeoutMs === "number"
|
|
1420
|
+
...(typeof node.timeoutMs === "number" || node.timeoutMs === null
|
|
1421
|
+
? { timeoutMs: node.timeoutMs }
|
|
1422
|
+
: {}),
|
|
1421
1423
|
...(node.statusDetail !== undefined ? { statusDetail: node.statusDetail } : {}),
|
|
1422
1424
|
};
|
|
1423
1425
|
if (node.nodeType === "agent" && node.expectedOutput !== undefined) {
|
package/src/workflows/types.ts
CHANGED
|
@@ -71,9 +71,10 @@ export type WorkflowProgressData = {
|
|
|
71
71
|
export type WorkflowNodeCommon = {
|
|
72
72
|
/**
|
|
73
73
|
* Per-node timeout or a callback that derives it from the run context.
|
|
74
|
-
*
|
|
74
|
+
* Null disables the wall-clock deadline. Omission falls back to the engine
|
|
75
|
+
* default (15 minutes).
|
|
75
76
|
*/
|
|
76
|
-
timeoutMs?: number | ((context: WorkflowNodeContext) => MaybePromise<number>);
|
|
77
|
+
timeoutMs?: number | null | ((context: WorkflowNodeContext) => MaybePromise<number | null>);
|
|
77
78
|
/** Short human-readable label shown in the viewer while the node runs. */
|
|
78
79
|
statusDetail?: string;
|
|
79
80
|
};
|
|
@@ -715,7 +716,7 @@ export type WorkflowRunState = {
|
|
|
715
716
|
|
|
716
717
|
export type WorkflowNodeSnapshot = {
|
|
717
718
|
nodeType: WorkflowNodeDefinition["nodeType"];
|
|
718
|
-
timeoutMs?: number;
|
|
719
|
+
timeoutMs?: number | null;
|
|
719
720
|
statusDetail?: string;
|
|
720
721
|
summary?: string;
|
|
721
722
|
expectedOutput?: string;
|