@quintinshaw/pi-dynamic-workflows 1.7.1 → 1.9.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 +6 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/task-panel.d.ts +24 -0
- package/dist/task-panel.js +82 -0
- package/dist/workflow-commands.js +12 -0
- package/dist/workflow-manager.d.ts +3 -0
- package/dist/workflow-manager.js +3 -0
- package/dist/workflow-tool.js +4 -1
- package/dist/workflow-ui.d.ts +110 -0
- package/dist/workflow-ui.js +426 -0
- package/dist/workflow.d.ts +21 -0
- package/dist/workflow.js +54 -19
- package/extensions/workflow.ts +9 -3
- package/package.json +1 -1
- package/src/index.ts +11 -0
- package/src/task-panel.ts +103 -0
- package/src/workflow-commands.ts +12 -0
- package/src/workflow-manager.ts +5 -0
- package/src/workflow-tool.ts +4 -1
- package/src/workflow-ui.ts +496 -0
- package/src/workflow.ts +71 -27
package/src/workflow.ts
CHANGED
|
@@ -32,6 +32,19 @@ export interface JournalEntry {
|
|
|
32
32
|
result: unknown;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Global resources shared across a run and any workflow() nested inside it, so
|
|
37
|
+
* the 16-concurrent / 1000-total caps and the token budget hold across nesting
|
|
38
|
+
* instead of each level getting its own limiter and counters.
|
|
39
|
+
*/
|
|
40
|
+
export interface SharedRuntime {
|
|
41
|
+
limiter: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
42
|
+
agentCount: number;
|
|
43
|
+
spent: number;
|
|
44
|
+
tokenUsage: { input: number; output: number; total: number; cost: number };
|
|
45
|
+
depth: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
export interface WorkflowRunOptions extends WorkflowAgentOptions {
|
|
36
49
|
args?: unknown;
|
|
37
50
|
agent?: Pick<WorkflowAgent, "run">;
|
|
@@ -52,6 +65,10 @@ export interface WorkflowRunOptions extends WorkflowAgentOptions {
|
|
|
52
65
|
resumeFromRunId?: string;
|
|
53
66
|
/** Called after each live agent completes so the caller can persist the journal. */
|
|
54
67
|
onAgentJournal?: (entry: JournalEntry) => void;
|
|
68
|
+
/** Internal: shared runtime inherited by a nested workflow() call. */
|
|
69
|
+
sharedRuntime?: SharedRuntime;
|
|
70
|
+
/** Resolve a saved-workflow name to its script, enabling `workflow('name', args)`. */
|
|
71
|
+
loadSavedWorkflow?: (name: string) => string | undefined;
|
|
55
72
|
onLog?: (message: string) => void;
|
|
56
73
|
onPhase?: (title: string) => void;
|
|
57
74
|
onAgentStart?: (event: { label: string; phase?: string; prompt: string; model?: string }) => void;
|
|
@@ -90,16 +107,8 @@ interface RuntimeState {
|
|
|
90
107
|
currentPhase?: string;
|
|
91
108
|
logs: string[];
|
|
92
109
|
phases: string[];
|
|
93
|
-
agentCount: number;
|
|
94
110
|
/** Monotonic, assigned at lexical agent() call time — the stable resume key. */
|
|
95
111
|
callSeq: number;
|
|
96
|
-
spent: number;
|
|
97
|
-
tokenUsage: {
|
|
98
|
-
input: number;
|
|
99
|
-
output: number;
|
|
100
|
-
total: number;
|
|
101
|
-
cost: number;
|
|
102
|
-
};
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
type AnyNode = Node & { [key: string]: any; start: number; end: number };
|
|
@@ -130,10 +139,7 @@ export async function runWorkflow<T = unknown>(
|
|
|
130
139
|
const state: RuntimeState = {
|
|
131
140
|
logs: [],
|
|
132
141
|
phases: [],
|
|
133
|
-
agentCount: 0,
|
|
134
142
|
callSeq: 0,
|
|
135
|
-
spent: 0,
|
|
136
|
-
tokenUsage: { input: 0, output: 0, total: 0, cost: 0 },
|
|
137
143
|
};
|
|
138
144
|
|
|
139
145
|
const agentRunner = options.agent ?? new WorkflowAgent(options);
|
|
@@ -141,7 +147,15 @@ export async function runWorkflow<T = unknown>(
|
|
|
141
147
|
1,
|
|
142
148
|
Math.min(options.concurrency ?? Math.max(1, (globalThis.navigator?.hardwareConcurrency ?? 8) - 2), MAX_CONCURRENCY),
|
|
143
149
|
);
|
|
144
|
-
|
|
150
|
+
// Global caps + budget are shared with any nested workflow() so they hold across nesting.
|
|
151
|
+
const shared: SharedRuntime = options.sharedRuntime ?? {
|
|
152
|
+
limiter: createLimiter(concurrency),
|
|
153
|
+
agentCount: 0,
|
|
154
|
+
spent: 0,
|
|
155
|
+
tokenUsage: { input: 0, output: 0, total: 0, cost: 0 },
|
|
156
|
+
depth: 0,
|
|
157
|
+
};
|
|
158
|
+
const limiter = shared.limiter;
|
|
145
159
|
|
|
146
160
|
const log = (message: string) => {
|
|
147
161
|
const text = String(message);
|
|
@@ -157,8 +171,8 @@ export async function runWorkflow<T = unknown>(
|
|
|
157
171
|
|
|
158
172
|
const budget = Object.freeze({
|
|
159
173
|
total: options.tokenBudget ?? null,
|
|
160
|
-
spent: () =>
|
|
161
|
-
remaining: () => (options.tokenBudget == null ? Infinity : Math.max(0, options.tokenBudget -
|
|
174
|
+
spent: () => shared.spent,
|
|
175
|
+
remaining: () => (options.tokenBudget == null ? Infinity : Math.max(0, options.tokenBudget - shared.spent)),
|
|
162
176
|
});
|
|
163
177
|
|
|
164
178
|
const throwIfAborted = () => {
|
|
@@ -171,7 +185,7 @@ export async function runWorkflow<T = unknown>(
|
|
|
171
185
|
throwIfAborted();
|
|
172
186
|
|
|
173
187
|
// Check agent limit
|
|
174
|
-
if (
|
|
188
|
+
if (shared.agentCount >= maxAgents) {
|
|
175
189
|
throw new WorkflowError(
|
|
176
190
|
`Agent limit exceeded (${maxAgents}). Use maxAgents option to increase the limit.`,
|
|
177
191
|
WorkflowErrorCode.AGENT_LIMIT_EXCEEDED,
|
|
@@ -199,16 +213,16 @@ export async function runWorkflow<T = unknown>(
|
|
|
199
213
|
// consuming a concurrency slot, tokens, or a real subagent run.
|
|
200
214
|
const cached = options.resumeJournal?.get(callIndex);
|
|
201
215
|
if (cached && cached.hash === callHash) {
|
|
202
|
-
|
|
203
|
-
const label = requestedLabel || defaultAgentLabel(assignedPhase,
|
|
216
|
+
shared.agentCount++;
|
|
217
|
+
const label = requestedLabel || defaultAgentLabel(assignedPhase, shared.agentCount);
|
|
204
218
|
options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: modelSpec });
|
|
205
219
|
options.onAgentEnd?.({ label, phase: assignedPhase, result: cached.result, tokens: 0 });
|
|
206
220
|
return cached.result;
|
|
207
221
|
}
|
|
208
222
|
|
|
209
223
|
return limiter(async () => {
|
|
210
|
-
|
|
211
|
-
const label = requestedLabel || defaultAgentLabel(assignedPhase,
|
|
224
|
+
shared.agentCount++;
|
|
225
|
+
const label = requestedLabel || defaultAgentLabel(assignedPhase, shared.agentCount);
|
|
212
226
|
const timeout = agentOptions.timeoutMs ?? agentTimeoutMs;
|
|
213
227
|
|
|
214
228
|
options.onAgentStart?.({ label, phase: assignedPhase, prompt, model: modelSpec });
|
|
@@ -227,12 +241,12 @@ export async function runWorkflow<T = unknown>(
|
|
|
227
241
|
const recordTokens = (result: unknown): number => {
|
|
228
242
|
const tokens = usage && usage.total > 0 ? usage.total : estimateTokens(result) + estimateTokens(prompt);
|
|
229
243
|
if (usage) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
shared.tokenUsage.input += usage.input;
|
|
245
|
+
shared.tokenUsage.output += usage.output;
|
|
246
|
+
shared.tokenUsage.cost += usage.cost;
|
|
233
247
|
}
|
|
234
|
-
|
|
235
|
-
|
|
248
|
+
shared.tokenUsage.total += tokens;
|
|
249
|
+
shared.spent += tokens;
|
|
236
250
|
return tokens;
|
|
237
251
|
};
|
|
238
252
|
|
|
@@ -331,10 +345,40 @@ export async function runWorkflow<T = unknown>(
|
|
|
331
345
|
);
|
|
332
346
|
};
|
|
333
347
|
|
|
348
|
+
// Nested workflow(): run a saved workflow (or a raw script) inline, sharing this
|
|
349
|
+
// run's limiter/counters/budget so the global caps hold. One level deep only.
|
|
350
|
+
const workflowFn = async (nameOrScript: string, childArgs?: unknown) => {
|
|
351
|
+
throwIfAborted();
|
|
352
|
+
if (shared.depth >= 1) {
|
|
353
|
+
throw new WorkflowError("workflow() can nest only one level deep", WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, {
|
|
354
|
+
recoverable: false,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const resolved = options.loadSavedWorkflow?.(String(nameOrScript));
|
|
358
|
+
const childScript = resolved ?? String(nameOrScript);
|
|
359
|
+
shared.depth++;
|
|
360
|
+
try {
|
|
361
|
+
const child = await runWorkflow(childScript, {
|
|
362
|
+
...options,
|
|
363
|
+
args: childArgs,
|
|
364
|
+
sharedRuntime: shared,
|
|
365
|
+
// A nested run is its own script; never reuse the parent's resume journal.
|
|
366
|
+
resumeJournal: undefined,
|
|
367
|
+
resumeFromRunId: undefined,
|
|
368
|
+
runId: `${runId}-nested${shared.depth}`,
|
|
369
|
+
persistLogs: false,
|
|
370
|
+
});
|
|
371
|
+
return child.result;
|
|
372
|
+
} finally {
|
|
373
|
+
shared.depth--;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
334
377
|
const context = vm.createContext({
|
|
335
378
|
agent,
|
|
336
379
|
parallel,
|
|
337
380
|
pipeline,
|
|
381
|
+
workflow: workflowFn,
|
|
338
382
|
log,
|
|
339
383
|
phase,
|
|
340
384
|
args: options.args,
|
|
@@ -369,17 +413,17 @@ export async function runWorkflow<T = unknown>(
|
|
|
369
413
|
}
|
|
370
414
|
|
|
371
415
|
// Emit final token usage
|
|
372
|
-
options.onTokenUsage?.(
|
|
416
|
+
options.onTokenUsage?.(shared.tokenUsage);
|
|
373
417
|
|
|
374
418
|
return {
|
|
375
419
|
meta,
|
|
376
420
|
result: result as T,
|
|
377
421
|
logs: state.logs,
|
|
378
422
|
phases: state.phases,
|
|
379
|
-
agentCount:
|
|
423
|
+
agentCount: shared.agentCount,
|
|
380
424
|
durationMs: Date.now() - started,
|
|
381
425
|
runId,
|
|
382
|
-
tokenUsage:
|
|
426
|
+
tokenUsage: shared.tokenUsage,
|
|
383
427
|
};
|
|
384
428
|
}
|
|
385
429
|
|