@signalridge/pi-workflows 1.5.0 → 1.7.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/CHANGELOG.md +227 -0
- package/README.md +207 -48
- package/package.json +2 -2
- package/skills/workflow-authoring/SKILL.md +44 -14
- package/skills/workflow-authoring/examples/defensive-json-parsing.js +1 -1
- package/skills/workflow-authoring/examples/phased-budgets.js +1 -1
- package/skills/workflow-authoring/examples/saved-nested-workflows.js +1 -1
- package/skills/workflow-authoring/examples/structured-output.js +1 -1
- package/skills/workflow-authoring/references/capabilities.md +7 -7
- package/skills/workflow-authoring/references/capability-details.md +24 -10
- package/skills/workflow-authoring/references/debugging.md +1 -1
- package/skills/workflow-authoring/references/lifecycle.md +5 -1
- package/skills/workflow-authoring/references/quality-helpers.md +2 -2
- package/skills/workflow-authoring/references/registry-ownership.md +4 -4
- package/skills/workflow-authoring/references/review.md +8 -4
- package/skills/workflow-authoring/references/runtime.md +2 -2
- package/skills/workflow-authoring/references/specialized-helpers.md +1 -1
- package/skills/workflow-authoring/references/versions.md +1 -1
- package/skills/workflow-patterns/SKILL.md +1 -1
- package/skills/workflow-review/SKILL.md +6 -4
- package/src/builtins.ts +23 -23
- package/src/capabilities.ts +37 -12
- package/src/engine.ts +70 -26
- package/src/index.ts +243 -70
- package/src/journal.ts +0 -14
- package/src/rpc-client.ts +14 -1
- package/src/runtime.ts +452 -103
- package/src/strengths.ts +93 -0
- package/src/workflow-settings.ts +95 -1
package/src/capabilities.ts
CHANGED
|
@@ -34,6 +34,14 @@ export const WORKFLOW_CAPABILITY_CONTRACT_VERSION = "2.0.0";
|
|
|
34
34
|
/**
|
|
35
35
|
* The runtime globals a script can rely on. `options` documents each helper's
|
|
36
36
|
* accepted inputs with defaults; `constraints` states the behavioral contract.
|
|
37
|
+
*
|
|
38
|
+
* The strength option's type is spelled out on each of the four helpers that
|
|
39
|
+
* take it, rather than derived from `WORKFLOW_STRENGTHS`, because this array
|
|
40
|
+
* must stay literal-only: `check:capabilities` evaluates it in an empty VM
|
|
41
|
+
* realm so the published contract can be read without executing this package.
|
|
42
|
+
* `strengths.test.ts` pins the four copies against the vocabulary the runtime
|
|
43
|
+
* enforces, which is the drift protection a shared constant would otherwise
|
|
44
|
+
* have provided.
|
|
37
45
|
*/
|
|
38
46
|
export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
39
47
|
{
|
|
@@ -45,17 +53,17 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
45
53
|
{ name: "phase", kind: "string", optional: true, default: "current phase" },
|
|
46
54
|
{ name: "schema", kind: "plain JSON Schema", optional: true, default: "none" },
|
|
47
55
|
{
|
|
48
|
-
name: "
|
|
49
|
-
kind: "
|
|
56
|
+
name: "strength",
|
|
57
|
+
kind: '"low" | "medium" | "high"',
|
|
50
58
|
optional: true,
|
|
51
|
-
default: "the agent's own tier, else agentTiers.defaultTier",
|
|
59
|
+
default: "no tier is sent; the agent's own tier, else agentTiers.defaultTier",
|
|
52
60
|
},
|
|
53
61
|
{ name: "isolation", kind: '"worktree"', optional: true, default: "pi-subagents worktree policy" },
|
|
54
62
|
{ name: "thread", kind: "string", optional: true, default: "fresh session per call" },
|
|
55
63
|
{ name: "toolset", kind: "string", optional: true, default: "configured host/agent toolset hint" },
|
|
56
64
|
{ name: "excludeTools", kind: "string[]", optional: true, default: "none" },
|
|
57
65
|
{ name: "agentType", kind: "string", optional: true, default: "general-purpose" },
|
|
58
|
-
{ name: "timeoutMs", kind: "number | null", optional: true, default: "
|
|
66
|
+
{ name: "timeoutMs", kind: "number | null", optional: true, default: "1800000 (30min); null disables" },
|
|
59
67
|
{ name: "retries", kind: "number", optional: true, default: "run retry count" },
|
|
60
68
|
],
|
|
61
69
|
constraints: [
|
|
@@ -64,11 +72,15 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
64
72
|
"resume replays only the longest unchanged prefix; the first miss and every later call execute live",
|
|
65
73
|
"replayed calls do not consume the real-dispatch maxAgents or token/phase budgets",
|
|
66
74
|
"spawnKeys rotate by generation on resume so pi-subagents never raises a fingerprint conflict",
|
|
67
|
-
"
|
|
75
|
+
"strength is this package's own vocabulary, not an agentTiers key; a word outside low/medium/high is rejected before dispatch",
|
|
76
|
+
"a strengths table alone binds a strength to an agent tier; an unmapped strength dispatches with no tier and takes the agent's own default",
|
|
77
|
+
"a call that names no strength also dispatches with no tier; there is no default strength, so an unlabelled call never outranks the agent type's own tier",
|
|
78
|
+
"an unconfigured host uses a shipped default table: each strength on the catalogue tier of the same name, where the host defines one",
|
|
68
79
|
"resolveAgentTier in pi-subagents owns model, thinking, clamping, availability, and the resolution snapshot",
|
|
69
|
-
"there is no per-call model or
|
|
80
|
+
"there is no per-call model, thinking, or tier: a strength is the only model policy a workflow can express",
|
|
70
81
|
"same-thread calls must be sequential and preserve a stable workflow thread name",
|
|
71
82
|
"threads cannot be combined with worktree isolation",
|
|
83
|
+
"an option name outside this list is rejected before dispatch, not ignored; tier, model, and thinking name their replacement",
|
|
72
84
|
],
|
|
73
85
|
},
|
|
74
86
|
{
|
|
@@ -136,6 +148,7 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
136
148
|
{ name: "reviewers", kind: "number", optional: true, default: "2" },
|
|
137
149
|
{ name: "threshold", kind: "number", optional: true, default: "0.5" },
|
|
138
150
|
{ name: "lens", kind: "string | string[]", optional: true, default: "none" },
|
|
151
|
+
{ name: "strength", kind: '"low" | "medium" | "high"', optional: true, default: '"low"' },
|
|
139
152
|
],
|
|
140
153
|
constraints: ["threshold comparison is inclusive; real is false when no reviewer succeeds"],
|
|
141
154
|
},
|
|
@@ -146,6 +159,7 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
146
159
|
options: [
|
|
147
160
|
{ name: "judges", kind: "number", optional: true, default: "3" },
|
|
148
161
|
{ name: "rubric", kind: "string", optional: true, default: "overall quality and correctness" },
|
|
162
|
+
{ name: "strength", kind: '"low" | "medium" | "high"', optional: true, default: '"low"' },
|
|
149
163
|
],
|
|
150
164
|
constraints: ["highest mean score wins with stable input index as the tie-break; empty input returns undefined"],
|
|
151
165
|
},
|
|
@@ -164,8 +178,8 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
164
178
|
{
|
|
165
179
|
name: "completenessCheck",
|
|
166
180
|
classification: "runtime-global",
|
|
167
|
-
signature: "completenessCheck(taskArgs, results) => Promise<{ complete, missing? } | null>",
|
|
168
|
-
options: [],
|
|
181
|
+
signature: "completenessCheck(taskArgs, results, options?) => Promise<{ complete, missing? } | null>",
|
|
182
|
+
options: [{ name: "strength", kind: '"low" | "medium" | "high"', optional: true, default: '"medium"' }],
|
|
169
183
|
constraints: ["only the first 4,000 characters of serialized result evidence are sent to the critic"],
|
|
170
184
|
},
|
|
171
185
|
{
|
|
@@ -199,6 +213,7 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
199
213
|
constraints: [
|
|
200
214
|
"consumes one agent slot and no tokens",
|
|
201
215
|
"journaled answers replay only within an unchanged resume prefix",
|
|
216
|
+
"an option name outside this list is rejected rather than ignored, since a dropped headless silently auto-approves",
|
|
202
217
|
],
|
|
203
218
|
},
|
|
204
219
|
{ name: "log", classification: "runtime-global", signature: "log(message) => void", options: [], constraints: [] },
|
|
@@ -269,9 +284,15 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
269
284
|
{
|
|
270
285
|
name: "concurrency",
|
|
271
286
|
classification: "workflow-tool-input",
|
|
272
|
-
signature: "concurrency?: number",
|
|
287
|
+
signature: "concurrency?: number = the host's pi-subagents maxConcurrent",
|
|
273
288
|
options: [],
|
|
274
|
-
constraints: [
|
|
289
|
+
constraints: [
|
|
290
|
+
"runtime clamps to 1..64",
|
|
291
|
+
"read live from the peer at each start and resume, and also the ceiling: a request above the pool is clamped to it, and the run log records the clamp",
|
|
292
|
+
"16 is only the fallback for a peer that publishes no pool size",
|
|
293
|
+
"pi-subagents maxConcurrent stays the authoritative throttle; one setting governs the whole fleet",
|
|
294
|
+
"that pool is typically small (4 slots by default), so a wide fan-out runs in waves rather than all at once",
|
|
295
|
+
],
|
|
275
296
|
},
|
|
276
297
|
{
|
|
277
298
|
name: "agentRetries",
|
|
@@ -283,9 +304,13 @@ export const WORKFLOW_CAPABILITIES: readonly CapabilityEntry[] = [
|
|
|
283
304
|
{
|
|
284
305
|
name: "agentTimeoutMs",
|
|
285
306
|
classification: "workflow-tool-input",
|
|
286
|
-
signature: "agentTimeoutMs?: number =
|
|
307
|
+
signature: "agentTimeoutMs?: number = 1800000",
|
|
287
308
|
options: [],
|
|
288
|
-
constraints: [
|
|
309
|
+
constraints: [
|
|
310
|
+
"null disables the hard timeout; default 1800000ms (30min)",
|
|
311
|
+
"it bounds a hang only; an agent's real budget is the host's turn, tool and token ceiling",
|
|
312
|
+
"the clock restarts when the host reports the agent left its queue, so waiting for a slot is not charged as work",
|
|
313
|
+
],
|
|
289
314
|
},
|
|
290
315
|
{
|
|
291
316
|
name: "tokenBudget",
|
package/src/engine.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
type WorkflowRunResult,
|
|
62
62
|
type WorkflowRuntimeEvent,
|
|
63
63
|
} from "./runtime.js";
|
|
64
|
+
import type { WorkflowStrengthTable } from "./strengths.js";
|
|
64
65
|
|
|
65
66
|
export const MAX_ATTEMPTS_PER_NODE = 3;
|
|
66
67
|
|
|
@@ -88,8 +89,19 @@ export interface ScriptStartOptions {
|
|
|
88
89
|
concurrency?: number;
|
|
89
90
|
agentRetries?: number;
|
|
90
91
|
tokenBudget?: number | null;
|
|
91
|
-
/**
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The user's strength → Agent tier table; see WorkflowRunOptions.strengths.
|
|
94
|
+
*
|
|
95
|
+
* Deliberately absent from `frozenOptions`. The frozen set is the run's
|
|
96
|
+
* inputs — budget, scale, toolset — which a resume must not silently
|
|
97
|
+
* renegotiate. Routing is not an input: a resume already re-reads the host's
|
|
98
|
+
* tier catalogue, and a table frozen against a catalogue that has since moved
|
|
99
|
+
* would be the one thing in the run still pointing at the old world.
|
|
100
|
+
*
|
|
101
|
+
* Being unfrozen makes supplying it every resume's job, including the resumes
|
|
102
|
+
* the engine starts itself — see {@link WorkflowEngine.strengths}.
|
|
103
|
+
*/
|
|
104
|
+
strengths?: WorkflowStrengthTable;
|
|
93
105
|
agentTimeoutMs?: number | null;
|
|
94
106
|
toolset?: string;
|
|
95
107
|
excludeTools?: string[];
|
|
@@ -105,13 +117,8 @@ export interface ScriptStartOptions {
|
|
|
105
117
|
timeoutMs?: number;
|
|
106
118
|
},
|
|
107
119
|
) => Promise<unknown>;
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
* script it returned ships with this package, because that decides how the
|
|
111
|
-
* child frame treats a tier name the host does not define — see
|
|
112
|
-
* `WorkflowRunOptions.shippedScript`.
|
|
113
|
-
*/
|
|
114
|
-
loadSavedWorkflow?: (name: string) => { script: string; shippedScript?: boolean } | undefined;
|
|
120
|
+
/** Resolve a nested `workflow(name)` reference to the script it names. */
|
|
121
|
+
loadSavedWorkflow?: (name: string) => string | undefined;
|
|
115
122
|
mainModel?: string;
|
|
116
123
|
}
|
|
117
124
|
|
|
@@ -129,12 +136,32 @@ interface AgentLifecycleWaiter {
|
|
|
129
136
|
generation: number;
|
|
130
137
|
onUsage?: (usage: AgentUsage) => void;
|
|
131
138
|
onHistory?: (history: unknown[]) => void;
|
|
139
|
+
/** Told once when the host reports this agent left the queue and started. */
|
|
140
|
+
onRunning?: () => void;
|
|
141
|
+
startedNotified: boolean;
|
|
132
142
|
executionGeneration: number;
|
|
133
143
|
resolve: (response: ManagedSpawnResponse) => void;
|
|
134
144
|
reject: (error: unknown) => void;
|
|
135
145
|
settled: boolean;
|
|
136
146
|
}
|
|
137
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Tell a waiter once that its agent started.
|
|
150
|
+
*
|
|
151
|
+
* Once, because the wall clock is restarted from this point: a repeated report
|
|
152
|
+
* — a duplicate event, or a reply that raced its own lifecycle event — would
|
|
153
|
+
* keep pushing the deadline out and quietly turn the timeout off.
|
|
154
|
+
*/
|
|
155
|
+
function notifyAgentRunning(waiter: AgentLifecycleWaiter): void {
|
|
156
|
+
if (waiter.startedNotified || waiter.settled) return;
|
|
157
|
+
waiter.startedNotified = true;
|
|
158
|
+
try {
|
|
159
|
+
waiter.onRunning?.();
|
|
160
|
+
} catch {
|
|
161
|
+
/* the clock is best effort; never fail a live agent over it */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
138
165
|
interface AgentTerminalDelivery {
|
|
139
166
|
response: ManagedSpawnResponse;
|
|
140
167
|
usage?: AgentUsage;
|
|
@@ -151,14 +178,7 @@ export interface ScriptRunState {
|
|
|
151
178
|
/** Frozen run parameters — resume reuses the original values, ignoring new budget/toolset overrides. */
|
|
152
179
|
frozenOptions: Pick<
|
|
153
180
|
ScriptStartOptions,
|
|
154
|
-
| "
|
|
155
|
-
| "maxAgents"
|
|
156
|
-
| "concurrency"
|
|
157
|
-
| "agentRetries"
|
|
158
|
-
| "agentTimeoutMs"
|
|
159
|
-
| "toolset"
|
|
160
|
-
| "shippedScript"
|
|
161
|
-
| "excludeTools"
|
|
181
|
+
"tokenBudget" | "maxAgents" | "concurrency" | "agentRetries" | "agentTimeoutMs" | "toolset" | "excludeTools"
|
|
162
182
|
>;
|
|
163
183
|
/** Abort controller for this run's script execution. */
|
|
164
184
|
controller: AbortController;
|
|
@@ -289,6 +309,18 @@ export class WorkflowEngine {
|
|
|
289
309
|
onRunSettled: (() => void) | undefined;
|
|
290
310
|
/** Optional in-process observer for phase/task/quality runtime events. */
|
|
291
311
|
onRuntimeEvent: ((runId: string, event: WorkflowRuntimeEvent) => void) | undefined;
|
|
312
|
+
/**
|
|
313
|
+
* The user's current strength → Agent tier table, read fresh per execution.
|
|
314
|
+
*
|
|
315
|
+
* A callback rather than a value because the table is deliberately unfrozen:
|
|
316
|
+
* "read fresh at each start and resume" has to hold for the resumes the
|
|
317
|
+
* engine starts on its own — `control("resume")` and the provider-limit
|
|
318
|
+
* retry — which have no caller to pass options for them. Without it those
|
|
319
|
+
* resume with no table at all, and since a call's identity keys on the tier
|
|
320
|
+
* it requested, every already-finished call would miss its journal entry and
|
|
321
|
+
* re-run untabled: the exact re-spend this table exists to prevent.
|
|
322
|
+
*/
|
|
323
|
+
strengths: (() => WorkflowStrengthTable | undefined) | undefined;
|
|
292
324
|
private readonly waiters = new Map<
|
|
293
325
|
string,
|
|
294
326
|
Set<{ resolve: (run: ScriptRun) => void; reject: (e: unknown) => void; settled: boolean; cleanup: () => void }>
|
|
@@ -420,6 +452,10 @@ export class WorkflowEngine {
|
|
|
420
452
|
if (updateCallTierIdentity(state, callNodeId, data.tier)) {
|
|
421
453
|
this.persistCallTierIdentity(state, callNodeId);
|
|
422
454
|
}
|
|
455
|
+
// A start is the moment the host admitted this agent from its queue. It is
|
|
456
|
+
// the only signal that separates waiting for a slot from doing the work, so
|
|
457
|
+
// the call's wall clock is restarted here rather than at dispatch.
|
|
458
|
+
if (eventName === "subagents:started" && waiterMatches && waiter) notifyAgentRunning(waiter);
|
|
423
459
|
// Created/started lifecycle facts carry managed identity before the RPC
|
|
424
460
|
// reply is necessarily available. They are observation-only here; terminal
|
|
425
461
|
// ownership is handled below once a completion/failure fact arrives.
|
|
@@ -628,7 +664,6 @@ export class WorkflowEngine {
|
|
|
628
664
|
const runId = randomUUID();
|
|
629
665
|
const scriptHash = hashScript(script);
|
|
630
666
|
const toolset = options.toolset;
|
|
631
|
-
const shippedScript = options.shippedScript === true ? true : undefined;
|
|
632
667
|
const durableArgs = cloneWorkflowArgs(options.args);
|
|
633
668
|
const runtimeArgs = cloneWorkflowArgs(durableArgs);
|
|
634
669
|
const journalArgs = cloneWorkflowArgs(durableArgs);
|
|
@@ -657,7 +692,6 @@ export class WorkflowEngine {
|
|
|
657
692
|
meta,
|
|
658
693
|
...(durableArgs === undefined ? {} : { args: durableArgs }),
|
|
659
694
|
frozenArgsPresent,
|
|
660
|
-
...(shippedScript === undefined ? {} : { shippedScript }),
|
|
661
695
|
...(toolset ? { toolset } : {}),
|
|
662
696
|
...(frozenMaxAgents !== undefined ? { frozenMaxAgents } : {}),
|
|
663
697
|
...(frozenConcurrency !== undefined ? { frozenConcurrency } : {}),
|
|
@@ -686,7 +720,6 @@ export class WorkflowEngine {
|
|
|
686
720
|
meta,
|
|
687
721
|
...(journalArgs === undefined ? {} : { args: journalArgs }),
|
|
688
722
|
frozenArgsPresent,
|
|
689
|
-
...(shippedScript === undefined ? {} : { shippedScript }),
|
|
690
723
|
...(toolset ? { toolset } : {}),
|
|
691
724
|
...(frozenMaxAgents !== undefined ? { frozenMaxAgents } : {}),
|
|
692
725
|
...(frozenConcurrency !== undefined ? { frozenConcurrency } : {}),
|
|
@@ -705,7 +738,6 @@ export class WorkflowEngine {
|
|
|
705
738
|
...(frozenAgentRetries !== undefined ? { agentRetries: frozenAgentRetries } : {}),
|
|
706
739
|
...(frozenAgentTimeoutMs !== undefined ? { agentTimeoutMs: frozenAgentTimeoutMs } : {}),
|
|
707
740
|
...(options.toolset ? { toolset: options.toolset } : {}),
|
|
708
|
-
...(shippedScript === undefined ? {} : { shippedScript }),
|
|
709
741
|
...(options.excludeTools ? { excludeTools: [...options.excludeTools] } : {}),
|
|
710
742
|
},
|
|
711
743
|
controller: new AbortController(),
|
|
@@ -802,11 +834,15 @@ export class WorkflowEngine {
|
|
|
802
834
|
this.setWorkflowStatus(run, "running");
|
|
803
835
|
state.controller = new AbortController();
|
|
804
836
|
// Freeze semantics: original run parameters and args win over new options.
|
|
805
|
-
// Only signal/confirm/background/loadSavedWorkflow/mainModel are
|
|
806
|
-
// input/budget/scale/toolset are frozen from the
|
|
837
|
+
// Only signal/confirm/background/loadSavedWorkflow/mainModel/strengths are
|
|
838
|
+
// per-resume context; input/budget/scale/toolset are frozen from the
|
|
839
|
+
// original start.
|
|
807
840
|
const frozen = state.frozenOptions;
|
|
808
841
|
const resumeOptions: ScriptStartOptions = {
|
|
809
842
|
...options,
|
|
843
|
+
// `??`, not an overwrite: a caller that passed a table meant it, and only
|
|
844
|
+
// a caller that passed none is asking the engine to read the current one.
|
|
845
|
+
strengths: options.strengths ?? this.strengths?.(),
|
|
810
846
|
args: resumeArgs,
|
|
811
847
|
tokenBudget: frozen.tokenBudget,
|
|
812
848
|
maxAgents: frozen.maxAgents,
|
|
@@ -814,7 +850,6 @@ export class WorkflowEngine {
|
|
|
814
850
|
agentRetries: frozen.agentRetries,
|
|
815
851
|
agentTimeoutMs: frozen.agentTimeoutMs,
|
|
816
852
|
toolset: frozen.toolset,
|
|
817
|
-
shippedScript: frozen.shippedScript,
|
|
818
853
|
excludeTools: frozen.excludeTools ? [...frozen.excludeTools] : undefined,
|
|
819
854
|
};
|
|
820
855
|
state.bufferedTerminals.clear();
|
|
@@ -999,6 +1034,10 @@ export class WorkflowEngine {
|
|
|
999
1034
|
: {
|
|
1000
1035
|
routingPolicy: protocol.routingPolicy,
|
|
1001
1036
|
routingPolicyFingerprint: protocol.routingPolicyFingerprint,
|
|
1037
|
+
// Read live at each start and resume, never frozen onto the run:
|
|
1038
|
+
// the pool is a host setting the user can change between them, and
|
|
1039
|
+
// a run that outlived a narrowing would dispatch into a queue.
|
|
1040
|
+
...(protocol.maxConcurrent === undefined ? {} : { hostMaxConcurrent: protocol.maxConcurrent }),
|
|
1002
1041
|
}),
|
|
1003
1042
|
concurrency: options.concurrency,
|
|
1004
1043
|
agentRetries: options.agentRetries,
|
|
@@ -1010,7 +1049,7 @@ export class WorkflowEngine {
|
|
|
1010
1049
|
executionNonce: `${generation}-${randomUUID()}`,
|
|
1011
1050
|
resumeJournal,
|
|
1012
1051
|
toolset: options.toolset,
|
|
1013
|
-
|
|
1052
|
+
strengths: options.strengths,
|
|
1014
1053
|
excludeTools: options.excludeTools,
|
|
1015
1054
|
confirm: options.confirm,
|
|
1016
1055
|
loadSavedWorkflow: options.loadSavedWorkflow,
|
|
@@ -1276,11 +1315,17 @@ export class WorkflowEngine {
|
|
|
1276
1315
|
executionGeneration,
|
|
1277
1316
|
onUsage: runOptions.onUsage,
|
|
1278
1317
|
onHistory: runOptions.onHistory,
|
|
1318
|
+
onRunning: runOptions.onRunning,
|
|
1319
|
+
startedNotified: false,
|
|
1279
1320
|
settled: false,
|
|
1280
1321
|
resolve,
|
|
1281
1322
|
reject,
|
|
1282
1323
|
};
|
|
1283
1324
|
state.agentWaiters.set(pendingId, waiter);
|
|
1325
|
+
// A spawn that was admitted straight away is already running, and its
|
|
1326
|
+
// `subagents:started` event fired before this waiter existed. Only a
|
|
1327
|
+
// reply that says "queued" still has a start to wait for.
|
|
1328
|
+
if (typeof response !== "string" && response.state !== "queued") notifyAgentRunning(waiter);
|
|
1284
1329
|
externalSignal.addEventListener("abort", waitAbort, { once: true });
|
|
1285
1330
|
if (externalSignal.aborted) {
|
|
1286
1331
|
waitAbort();
|
|
@@ -1750,7 +1795,6 @@ export class WorkflowEngine {
|
|
|
1750
1795
|
...(run.frozenAgentRetries !== undefined ? { agentRetries: run.frozenAgentRetries } : {}),
|
|
1751
1796
|
...(run.frozenAgentTimeoutMs !== undefined ? { agentTimeoutMs: run.frozenAgentTimeoutMs } : {}),
|
|
1752
1797
|
...(run.toolset ? { toolset: run.toolset } : {}),
|
|
1753
|
-
...(run.shippedScript === undefined ? {} : { shippedScript: run.shippedScript }),
|
|
1754
1798
|
...(run.frozenExcludeTools ? { excludeTools: [...run.frozenExcludeTools] } : {}),
|
|
1755
1799
|
},
|
|
1756
1800
|
controller: new AbortController(),
|