@osolmaz/pi-workflows 0.5.3 → 0.6.1
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 +10 -2
- package/dist/builtins/catalog.js +1 -1
- package/dist/builtins/monitor.workflow.d.ts +25 -69
- package/dist/builtins/monitor.workflow.js +194 -123
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/extension/executor.d.ts +7 -2
- package/dist/extension/executor.js +20 -14
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/index.js +65 -13
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/step-message.d.ts +24 -0
- package/dist/extension/step-message.js +106 -0
- package/dist/extension/step-message.js.map +1 -0
- package/dist/extension/widget.d.ts +2 -2
- package/dist/extension/widget.js +64 -5
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +9 -0
- package/dist/extension/workflow-tool.js +10 -0
- package/dist/extension/workflow-tool.js.map +1 -1
- package/dist/host/rpc-bridge.js +25 -11
- package/dist/host/rpc-bridge.js.map +1 -1
- package/dist/host/rpc-executor.d.ts +2 -2
- package/dist/host/rpc-executor.js +23 -12
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/viewer/cli.js +1 -1
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +14 -0
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/tui.js +1 -1
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +88 -6
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/index.d.ts +4 -2
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/progress.d.ts +34 -0
- package/dist/workflows/progress.js +268 -0
- package/dist/workflows/progress.js.map +1 -0
- package/dist/workflows/schema.js +21 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/shell.d.ts +2 -2
- package/dist/workflows/shell.js +103 -25
- package/dist/workflows/shell.js.map +1 -1
- package/dist/workflows/store.d.ts +15 -2
- package/dist/workflows/store.js +44 -2
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +52 -1
- package/dist/workflows/updates.d.ts +15 -0
- package/dist/workflows/updates.js +188 -0
- package/dist/workflows/updates.js.map +1 -0
- package/docs/DESIGN_PHILOSOPHY.md +51 -0
- package/docs/MONITOR.md +282 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +141 -0
- package/docs/WORKFLOW_UPDATES.md +416 -0
- package/docs/development.md +7 -3
- package/docs/plans/2026-08-16-workflow-updates-plan.md +494 -0
- package/docs/run-bundles.md +10 -2
- package/docs/workflows.md +58 -17
- package/package.json +1 -1
- package/src/builtins/catalog.ts +1 -1
- package/src/builtins/monitor.workflow.ts +217 -148
- package/src/extension/executor.ts +36 -14
- package/src/extension/index.ts +95 -19
- package/src/extension/step-message.ts +145 -0
- package/src/extension/widget.ts +93 -4
- package/src/extension/workflow-tool.ts +22 -0
- package/src/host/rpc-bridge.ts +37 -14
- package/src/host/rpc-executor.ts +35 -14
- package/src/viewer/cli.ts +1 -1
- package/src/viewer/render.ts +27 -0
- package/src/viewer/tui.ts +1 -1
- package/src/workflows/engine.ts +117 -4
- package/src/workflows/index.ts +32 -0
- package/src/workflows/progress.ts +326 -0
- package/src/workflows/schema.ts +23 -1
- package/src/workflows/shell.ts +109 -26
- package/src/workflows/store.ts +67 -2
- package/src/workflows/types.ts +78 -1
- package/src/workflows/updates.ts +208 -0
package/src/host/rpc-executor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { errorMessage } from "../workflows/errors.js";
|
|
4
5
|
import type {
|
|
5
6
|
AgentStepExecutor,
|
|
6
7
|
AgentStepRequest,
|
|
@@ -23,6 +24,16 @@ type StepSubmission = {
|
|
|
23
24
|
output: unknown;
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
type StepUpdate = {
|
|
28
|
+
action: "update";
|
|
29
|
+
step: string;
|
|
30
|
+
attempt: string;
|
|
31
|
+
update: { type: string; key: string; data: Record<string, unknown> };
|
|
32
|
+
idempotencyKey?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type StepAction = StepSubmission | StepUpdate;
|
|
36
|
+
|
|
26
37
|
export type RpcStepExecutorOptions = {
|
|
27
38
|
cwd: string;
|
|
28
39
|
registry: HostProcessRegistry;
|
|
@@ -46,7 +57,7 @@ export class RpcStepExecutor implements AgentStepExecutor {
|
|
|
46
57
|
private child: ChildProcess | null = null;
|
|
47
58
|
private childExited: { code: number | null; signal: string | null } | null = null;
|
|
48
59
|
private stderrBuffer = "";
|
|
49
|
-
private
|
|
60
|
+
private actions: StepAction[] = [];
|
|
50
61
|
private submissionWaiters: Array<() => void> = [];
|
|
51
62
|
private stdoutBuffer = "";
|
|
52
63
|
|
|
@@ -179,13 +190,13 @@ export class RpcStepExecutor implements AgentStepExecutor {
|
|
|
179
190
|
continue;
|
|
180
191
|
}
|
|
181
192
|
try {
|
|
182
|
-
const parsed = JSON.parse(line.slice(RPC_SUBMISSION_PREFIX.length)) as
|
|
193
|
+
const parsed = JSON.parse(line.slice(RPC_SUBMISSION_PREFIX.length)) as StepAction;
|
|
183
194
|
if (
|
|
184
|
-
parsed.action === "submit" &&
|
|
195
|
+
(parsed.action === "submit" || parsed.action === "update") &&
|
|
185
196
|
typeof parsed.step === "string" &&
|
|
186
197
|
typeof parsed.attempt === "string"
|
|
187
198
|
) {
|
|
188
|
-
this.
|
|
199
|
+
this.actions.push(parsed);
|
|
189
200
|
}
|
|
190
201
|
} catch {
|
|
191
202
|
// A malformed marker line is ignored; the step times out instead.
|
|
@@ -209,10 +220,6 @@ export class RpcStepExecutor implements AgentStepExecutor {
|
|
|
209
220
|
if (child === null || this.childExited !== null) {
|
|
210
221
|
throw new Error("Headless pi session is not running");
|
|
211
222
|
}
|
|
212
|
-
const matching = this.takeMatchingSubmission(request);
|
|
213
|
-
if (matching !== undefined) {
|
|
214
|
-
return matching;
|
|
215
|
-
}
|
|
216
223
|
child.stdin?.write(`${JSON.stringify({ type: "prompt", message: prompt })}\n`);
|
|
217
224
|
for (;;) {
|
|
218
225
|
throwIfAborted(signal);
|
|
@@ -220,9 +227,23 @@ export class RpcStepExecutor implements AgentStepExecutor {
|
|
|
220
227
|
if (exited !== null) {
|
|
221
228
|
throw new Error(`Headless pi session exited mid-step (code ${exited.code})`);
|
|
222
229
|
}
|
|
223
|
-
const found = this.
|
|
224
|
-
if (found
|
|
225
|
-
|
|
230
|
+
const found = this.takeMatchingAction(request);
|
|
231
|
+
if (found?.action === "submit") return found;
|
|
232
|
+
if (found?.action === "update") {
|
|
233
|
+
if (request.publishUpdate === undefined) {
|
|
234
|
+
throw new Error("This workflow host does not support step updates");
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
await request.publishUpdate(found.update, found.idempotencyKey);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
child.stdin?.write(
|
|
240
|
+
`${JSON.stringify({
|
|
241
|
+
type: "prompt",
|
|
242
|
+
message: `Workflow update rejected: ${errorMessage(error)}. Correct the update and continue the same step.`,
|
|
243
|
+
})}\n`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
226
247
|
}
|
|
227
248
|
await new Promise<void>((resolve, reject) => {
|
|
228
249
|
const waiter = () => {
|
|
@@ -239,13 +260,13 @@ export class RpcStepExecutor implements AgentStepExecutor {
|
|
|
239
260
|
}
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
private
|
|
243
|
-
const index = this.
|
|
263
|
+
private takeMatchingAction(request: AgentStepRequest): StepAction | undefined {
|
|
264
|
+
const index = this.actions.findIndex(
|
|
244
265
|
(candidate) =>
|
|
245
266
|
candidate.step === request.contract.nodeId &&
|
|
246
267
|
candidate.attempt === request.contract.attemptId,
|
|
247
268
|
);
|
|
248
|
-
return index === -1 ? undefined : (this.
|
|
269
|
+
return index === -1 ? undefined : (this.actions.splice(index, 1)[0] as StepAction);
|
|
249
270
|
}
|
|
250
271
|
|
|
251
272
|
private sendAbortQuietly(): void {
|
package/src/viewer/cli.ts
CHANGED
|
@@ -138,7 +138,7 @@ async function printOnce(dir: string, runId: string | undefined): Promise<void>
|
|
|
138
138
|
if (!match) {
|
|
139
139
|
throw new Error(`Run not found: ${runId}`);
|
|
140
140
|
}
|
|
141
|
-
const bundle = await readRunBundle(match.runDir);
|
|
141
|
+
const bundle = await readRunBundle(match.runDir, { includeTrace: true });
|
|
142
142
|
if (!bundle) {
|
|
143
143
|
throw new Error(`Run bundle unreadable: ${match.runDir}`);
|
|
144
144
|
}
|
package/src/viewer/render.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { ansi, fitWidth, sanitizeText } from "../render/ansi.js";
|
|
|
2
2
|
import { formatDuration, runElapsedMs } from "../render/format.js";
|
|
3
3
|
import { renderGraphLines } from "../render/graph-render.js";
|
|
4
4
|
import { decodeValueWith } from "../workflows/artifacts.js";
|
|
5
|
+
import {
|
|
6
|
+
formatProgressLine,
|
|
7
|
+
progressRecordsFromTrace,
|
|
8
|
+
progressTracksFromRecords,
|
|
9
|
+
} from "../workflows/progress.js";
|
|
5
10
|
import type { LoadedRunBundle } from "../workflows/store.js";
|
|
6
11
|
import type { WorkflowRunStatus, WorkflowStepRecord } from "../workflows/types.js";
|
|
7
12
|
|
|
@@ -204,6 +209,28 @@ export function renderRunDetailLines(
|
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
|
|
212
|
+
const progressRecords =
|
|
213
|
+
bundle.traceEvents === undefined
|
|
214
|
+
? (state.updates ?? []).filter((update) => update.type === "progress")
|
|
215
|
+
: progressRecordsFromTrace(bundle.traceEvents);
|
|
216
|
+
const progress = progressTracksFromRecords(progressRecords, now);
|
|
217
|
+
if (progress.length > 0) {
|
|
218
|
+
lines.push("");
|
|
219
|
+
lines.push(ansi.bold("progress"));
|
|
220
|
+
for (const track of progress) {
|
|
221
|
+
lines.push(fitWidth(formatProgressLine(track.estimate, now), size.width));
|
|
222
|
+
const latest = track.samples.at(-1);
|
|
223
|
+
lines.push(
|
|
224
|
+
fitWidth(
|
|
225
|
+
ansi.dim(
|
|
226
|
+
` ${track.estimate.sampleCount} samples · ${track.estimate.confidence ?? "no"} confidence · updated ${latest?.at ?? "unknown"}`,
|
|
227
|
+
),
|
|
228
|
+
size.width,
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
207
234
|
if (steps.length > 0) {
|
|
208
235
|
lines.push("");
|
|
209
236
|
lines.push(ansi.bold("steps"));
|
package/src/viewer/tui.ts
CHANGED
|
@@ -62,7 +62,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
const renderDetail = async (runDir: string, size: ViewportSize): Promise<string[]> => {
|
|
65
|
-
const bundle = await readRunBundle(runDir);
|
|
65
|
+
const bundle = await readRunBundle(runDir, { includeTrace: true });
|
|
66
66
|
if (!bundle) {
|
|
67
67
|
return ["Run bundle disappeared. Press q to go back."];
|
|
68
68
|
}
|
package/src/workflows/engine.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
ConversationRange,
|
|
24
24
|
ShellActionNodeDefinition,
|
|
25
25
|
ShellActionResult,
|
|
26
|
+
WorkflowActionContext,
|
|
26
27
|
WorkflowActionReceipt,
|
|
27
28
|
WorkflowDefinition,
|
|
28
29
|
WorkflowEngineOptions,
|
|
@@ -36,7 +37,10 @@ import type {
|
|
|
36
37
|
WorkflowSource,
|
|
37
38
|
WorkflowStepRecord,
|
|
38
39
|
WorkflowTraceEventDraft,
|
|
40
|
+
WorkflowUpdateInput,
|
|
41
|
+
WorkflowUpdateReceipt,
|
|
39
42
|
} from "./types.js";
|
|
43
|
+
import { UpdateRateLimiter, updateReceipt, validateWorkflowUpdate } from "./updates.js";
|
|
40
44
|
|
|
41
45
|
const DEFAULT_NODE_TIMEOUT_MS = 15 * 60_000;
|
|
42
46
|
const DEFAULT_MAX_STEPS = 100;
|
|
@@ -83,6 +87,17 @@ export class WorkflowEngine {
|
|
|
83
87
|
private readonly onRunStarted?: WorkflowEngineOptions["onRunStarted"];
|
|
84
88
|
private readonly onRunFinishing?: WorkflowEngineOptions["onRunFinishing"];
|
|
85
89
|
private activeAbort: AbortController | null = null;
|
|
90
|
+
private activeAttempt:
|
|
91
|
+
| {
|
|
92
|
+
runDir: string;
|
|
93
|
+
state: WorkflowRunState;
|
|
94
|
+
nodeId: string;
|
|
95
|
+
attemptId: string;
|
|
96
|
+
signal: AbortSignal;
|
|
97
|
+
}
|
|
98
|
+
| undefined;
|
|
99
|
+
private readonly updateLimiters = new Map<string, UpdateRateLimiter>();
|
|
100
|
+
private readonly updatePublications = new Map<string, Promise<WorkflowUpdateReceipt>>();
|
|
86
101
|
private cancelled = false;
|
|
87
102
|
private parked = false;
|
|
88
103
|
private paused = false;
|
|
@@ -103,6 +118,59 @@ export class WorkflowEngine {
|
|
|
103
118
|
return this.store.outputRoot;
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
/** Publish a durable update for the currently active attempt without completing it. */
|
|
122
|
+
async publishUpdate(
|
|
123
|
+
step: string,
|
|
124
|
+
attempt: string,
|
|
125
|
+
input: WorkflowUpdateInput,
|
|
126
|
+
idempotencyKey?: string,
|
|
127
|
+
): Promise<WorkflowUpdateReceipt> {
|
|
128
|
+
const active = this.activeAttempt;
|
|
129
|
+
if (
|
|
130
|
+
active === undefined ||
|
|
131
|
+
active.nodeId !== step ||
|
|
132
|
+
active.attemptId !== attempt ||
|
|
133
|
+
active.signal.aborted
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Workflow step ${JSON.stringify(step)} attempt ${JSON.stringify(attempt)} is not active`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const receiptKey =
|
|
140
|
+
idempotencyKey === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: `${active.state.runId}:${active.attemptId}:${idempotencyKey}`;
|
|
143
|
+
if (receiptKey !== undefined) {
|
|
144
|
+
const prior = this.updatePublications.get(receiptKey);
|
|
145
|
+
if (prior !== undefined) return await prior;
|
|
146
|
+
}
|
|
147
|
+
const publication = (async () => {
|
|
148
|
+
const update = validateWorkflowUpdate(input);
|
|
149
|
+
let limiter = this.updateLimiters.get(active.state.runId);
|
|
150
|
+
if (limiter === undefined) {
|
|
151
|
+
limiter = new UpdateRateLimiter();
|
|
152
|
+
this.updateLimiters.set(active.state.runId, limiter);
|
|
153
|
+
}
|
|
154
|
+
limiter.take();
|
|
155
|
+
const { event, record } = await this.store.publishUpdate(
|
|
156
|
+
active.runDir,
|
|
157
|
+
active.state,
|
|
158
|
+
active.nodeId,
|
|
159
|
+
active.attemptId,
|
|
160
|
+
update,
|
|
161
|
+
{ signal: active.signal },
|
|
162
|
+
);
|
|
163
|
+
try {
|
|
164
|
+
this.onEvent?.(event, active.state);
|
|
165
|
+
} catch {
|
|
166
|
+
// UI and logging observers never determine workflow correctness.
|
|
167
|
+
}
|
|
168
|
+
return updateReceipt(record);
|
|
169
|
+
})();
|
|
170
|
+
if (receiptKey !== undefined) this.updatePublications.set(receiptKey, publication);
|
|
171
|
+
return await publication;
|
|
172
|
+
}
|
|
173
|
+
|
|
106
174
|
/** Abort the currently running node and mark the run cancelled. */
|
|
107
175
|
cancel(): void {
|
|
108
176
|
this.cancelled = true;
|
|
@@ -492,6 +560,7 @@ export class WorkflowEngine {
|
|
|
492
560
|
outputs: {},
|
|
493
561
|
results: {},
|
|
494
562
|
steps: [],
|
|
563
|
+
updates: [],
|
|
495
564
|
};
|
|
496
565
|
}
|
|
497
566
|
|
|
@@ -775,6 +844,7 @@ export class WorkflowEngine {
|
|
|
775
844
|
timer = setTimeout(() => {
|
|
776
845
|
abort.abort(new TimeoutError(timeoutMs));
|
|
777
846
|
}, timeoutMs);
|
|
847
|
+
this.activeAttempt = { runDir, state, nodeId, attemptId, signal: abort.signal };
|
|
778
848
|
const dispatched = this.dispatchNode(
|
|
779
849
|
workflow,
|
|
780
850
|
state,
|
|
@@ -815,9 +885,19 @@ export class WorkflowEngine {
|
|
|
815
885
|
if (timer !== undefined) {
|
|
816
886
|
clearTimeout(timer);
|
|
817
887
|
}
|
|
888
|
+
if (!abort.signal.aborted) {
|
|
889
|
+
abort.abort(new Error(`Workflow node ${nodeId} is no longer active`));
|
|
890
|
+
}
|
|
818
891
|
if (this.activeAbort === abort) {
|
|
819
892
|
this.activeAbort = null;
|
|
820
893
|
}
|
|
894
|
+
if (this.activeAttempt?.attemptId === attemptId) {
|
|
895
|
+
this.activeAttempt = undefined;
|
|
896
|
+
}
|
|
897
|
+
const receiptPrefix = `${state.runId}:${attemptId}:`;
|
|
898
|
+
for (const key of this.updatePublications.keys()) {
|
|
899
|
+
if (key.startsWith(receiptPrefix)) this.updatePublications.delete(key);
|
|
900
|
+
}
|
|
821
901
|
}
|
|
822
902
|
}
|
|
823
903
|
|
|
@@ -895,7 +975,7 @@ export class WorkflowEngine {
|
|
|
895
975
|
return { output: receipt, promptText: null };
|
|
896
976
|
}
|
|
897
977
|
case "action":
|
|
898
|
-
return await this.runActionNode(node, context, signal, meta);
|
|
978
|
+
return await this.runActionNode(node, context, nodeId, attemptId, signal, meta);
|
|
899
979
|
case "checkpoint":
|
|
900
980
|
return await runCheckpointNode(node, context);
|
|
901
981
|
}
|
|
@@ -955,7 +1035,17 @@ export class WorkflowEngine {
|
|
|
955
1035
|
...(node.expectedOutput !== undefined ? { expectedOutput: node.expectedOutput } : {}),
|
|
956
1036
|
},
|
|
957
1037
|
prompt,
|
|
1038
|
+
...(state.runTitle !== undefined || node.statusDetail !== undefined
|
|
1039
|
+
? {
|
|
1040
|
+
presentation: {
|
|
1041
|
+
...(state.runTitle !== undefined ? { runTitle: state.runTitle } : {}),
|
|
1042
|
+
...(node.statusDetail !== undefined ? { statusDetail: node.statusDetail } : {}),
|
|
1043
|
+
},
|
|
1044
|
+
}
|
|
1045
|
+
: {}),
|
|
958
1046
|
accept: async (output) => await this.acceptSubmission(node, context, output),
|
|
1047
|
+
publishUpdate: async (update, idempotencyKey) =>
|
|
1048
|
+
await this.publishUpdate(nodeId, attemptId, update, idempotencyKey),
|
|
959
1049
|
},
|
|
960
1050
|
signal,
|
|
961
1051
|
);
|
|
@@ -987,14 +1077,20 @@ export class WorkflowEngine {
|
|
|
987
1077
|
private async runActionNode(
|
|
988
1078
|
node: ActionNodeDefinition,
|
|
989
1079
|
context: WorkflowNodeContext,
|
|
1080
|
+
nodeId: string,
|
|
1081
|
+
attemptId: string,
|
|
990
1082
|
signal: AbortSignal,
|
|
991
1083
|
meta: NodeExecutionMeta,
|
|
992
1084
|
): Promise<NodeExecution> {
|
|
1085
|
+
const actionContext: WorkflowActionContext = {
|
|
1086
|
+
...context,
|
|
1087
|
+
publishUpdate: async (update) => await this.publishUpdate(nodeId, attemptId, update),
|
|
1088
|
+
};
|
|
993
1089
|
if ("exec" in node) {
|
|
994
|
-
return await runShellActionNode(node, context, signal, meta);
|
|
1090
|
+
return await runShellActionNode(node, context, actionContext, signal, meta);
|
|
995
1091
|
}
|
|
996
1092
|
meta.action = { actionType: "function" };
|
|
997
|
-
const output = await node.run(
|
|
1093
|
+
const output = await node.run(actionContext);
|
|
998
1094
|
return { output, promptText: null, action: { actionType: "function" } };
|
|
999
1095
|
}
|
|
1000
1096
|
|
|
@@ -1078,13 +1174,28 @@ function shellReceipt(result: ShellActionResult): WorkflowActionReceipt {
|
|
|
1078
1174
|
async function runShellActionNode(
|
|
1079
1175
|
node: ShellActionNodeDefinition,
|
|
1080
1176
|
context: WorkflowNodeContext,
|
|
1177
|
+
actionContext: WorkflowActionContext,
|
|
1081
1178
|
signal: AbortSignal,
|
|
1082
1179
|
meta: NodeExecutionMeta,
|
|
1083
1180
|
): Promise<NodeExecution> {
|
|
1084
1181
|
const spec = await node.exec(context);
|
|
1182
|
+
const streams = new Set(node.updates?.streams ?? ["stdout"]);
|
|
1085
1183
|
let result: ShellActionResult;
|
|
1086
1184
|
try {
|
|
1087
|
-
result = await runShellAction(
|
|
1185
|
+
result = await runShellAction(
|
|
1186
|
+
spec,
|
|
1187
|
+
signal,
|
|
1188
|
+
node.updates === undefined
|
|
1189
|
+
? undefined
|
|
1190
|
+
: async (line) => {
|
|
1191
|
+
if (!streams.has(line.stream)) return;
|
|
1192
|
+
const parsed = await node.updates?.parseLine(line, actionContext);
|
|
1193
|
+
if (parsed === undefined) return;
|
|
1194
|
+
for (const update of Array.isArray(parsed) ? parsed : [parsed]) {
|
|
1195
|
+
await actionContext.publishUpdate(update);
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
);
|
|
1088
1199
|
} catch (error) {
|
|
1089
1200
|
const failed = shellResultFromError(error);
|
|
1090
1201
|
if (failed) {
|
|
@@ -1191,6 +1302,8 @@ export function appendStepContract(
|
|
|
1191
1302
|
"---",
|
|
1192
1303
|
`Workflow step contract (workflow: ${workflowName}, step: ${nodeId}, attempt: ${attemptId})`,
|
|
1193
1304
|
"",
|
|
1305
|
+
"While this step is active, you may publish non-completing updates with:",
|
|
1306
|
+
`{"action": "update", "step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "update": {"type": "...", "key": "...", "data": {...}}}`,
|
|
1194
1307
|
"Complete this step by calling the `workflow` tool exactly once with:",
|
|
1195
1308
|
`{"action": "submit", "step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
|
|
1196
1309
|
`Expected output: ${expectedOutput ?? "a JSON object with your result"}`,
|
package/src/workflows/index.ts
CHANGED
|
@@ -29,6 +29,28 @@ export {
|
|
|
29
29
|
type WorkflowSearchPaths,
|
|
30
30
|
} from "./loader.js";
|
|
31
31
|
export { renderShellCommand, runShellAction } from "./shell.js";
|
|
32
|
+
export {
|
|
33
|
+
estimateProgress,
|
|
34
|
+
formatProgressLine,
|
|
35
|
+
formatProgressReport,
|
|
36
|
+
formatRemaining,
|
|
37
|
+
prioritizeProgressEstimates,
|
|
38
|
+
progressRecordsFromTrace,
|
|
39
|
+
progressTracksFromRecords,
|
|
40
|
+
type ProgressConfidence,
|
|
41
|
+
type ProgressEstimate,
|
|
42
|
+
type ProgressSample,
|
|
43
|
+
type ProgressTrackState,
|
|
44
|
+
} from "./progress.js";
|
|
45
|
+
export {
|
|
46
|
+
MAX_CURRENT_UPDATES,
|
|
47
|
+
MAX_UPDATE_DATA_BYTES,
|
|
48
|
+
UPDATE_RATE_BURST,
|
|
49
|
+
UPDATE_RATE_PER_SECOND,
|
|
50
|
+
updateProjection,
|
|
51
|
+
validateProgressData,
|
|
52
|
+
validateWorkflowUpdate,
|
|
53
|
+
} from "./updates.js";
|
|
32
54
|
export { sanitizeText, stripAnsi } from "./text.js";
|
|
33
55
|
export {
|
|
34
56
|
ARTIFACT_THRESHOLD_BYTES,
|
|
@@ -51,11 +73,13 @@ export {
|
|
|
51
73
|
readRunBundle,
|
|
52
74
|
workflowRunsBaseDir,
|
|
53
75
|
type LoadedRunBundle,
|
|
76
|
+
type ReadRunBundleOptions,
|
|
54
77
|
} from "./store.js";
|
|
55
78
|
export type {
|
|
56
79
|
AgentNodeDefinition,
|
|
57
80
|
AgentStepContract,
|
|
58
81
|
AgentStepExecutor,
|
|
82
|
+
AgentStepPresentation,
|
|
59
83
|
AgentStepRequest,
|
|
60
84
|
AgentStepSubmission,
|
|
61
85
|
ActionNodeDefinition,
|
|
@@ -70,6 +94,7 @@ export type {
|
|
|
70
94
|
ShellActionExecution,
|
|
71
95
|
ShellActionNodeDefinition,
|
|
72
96
|
ShellActionResult,
|
|
97
|
+
WorkflowActionContext,
|
|
73
98
|
WorkflowActionReceipt,
|
|
74
99
|
WorkflowDefinition,
|
|
75
100
|
WorkflowDefinitionSnapshot,
|
|
@@ -85,6 +110,8 @@ export type {
|
|
|
85
110
|
WorkflowNotificationRequest,
|
|
86
111
|
WorkflowNotificationSink,
|
|
87
112
|
WorkflowPresentationContext,
|
|
113
|
+
WorkflowProgressData,
|
|
114
|
+
WorkflowProgressStatus,
|
|
88
115
|
WorkflowRunManifest,
|
|
89
116
|
WorkflowRunResult,
|
|
90
117
|
WorkflowRunState,
|
|
@@ -95,4 +122,9 @@ export type {
|
|
|
95
122
|
WorkflowStepRecord,
|
|
96
123
|
WorkflowTraceEvent,
|
|
97
124
|
WorkflowTraceEventDraft,
|
|
125
|
+
WorkflowUpdateInput,
|
|
126
|
+
WorkflowUpdateReceipt,
|
|
127
|
+
WorkflowUpdateRecord,
|
|
128
|
+
ShellActionUpdates,
|
|
129
|
+
ShellUpdateLine,
|
|
98
130
|
} from "./types.js";
|