@memberjunction/task-graph 0.0.0 → 6.1.0-edge.2
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/LICENSE +7 -0
- package/README.md +167 -27
- package/dist/DispatcherConditionEvaluator.d.ts +10 -0
- package/dist/DispatcherConditionEvaluator.d.ts.map +1 -0
- package/dist/DispatcherConditionEvaluator.js +27 -0
- package/dist/DispatcherConditionEvaluator.js.map +1 -0
- package/dist/TaskClaimStore.d.ts +125 -0
- package/dist/TaskClaimStore.d.ts.map +1 -0
- package/dist/TaskClaimStore.js +223 -0
- package/dist/TaskClaimStore.js.map +1 -0
- package/dist/TaskGraphDispatcher.d.ts +548 -0
- package/dist/TaskGraphDispatcher.d.ts.map +1 -0
- package/dist/TaskGraphDispatcher.js +2232 -0
- package/dist/TaskGraphDispatcher.js.map +1 -0
- package/dist/TaskGraphService.d.ts +247 -0
- package/dist/TaskGraphService.d.ts.map +1 -0
- package/dist/TaskGraphService.js +753 -0
- package/dist/TaskGraphService.js.map +1 -0
- package/dist/TaskGraphSubmitterImpl.d.ts +7 -0
- package/dist/TaskGraphSubmitterImpl.d.ts.map +1 -0
- package/dist/TaskGraphSubmitterImpl.js +52 -0
- package/dist/TaskGraphSubmitterImpl.js.map +1 -0
- package/dist/TaskLoopExecutor.d.ts +62 -0
- package/dist/TaskLoopExecutor.d.ts.map +1 -0
- package/dist/TaskLoopExecutor.js +248 -0
- package/dist/TaskLoopExecutor.js.map +1 -0
- package/dist/WorkflowSpecSync.d.ts +197 -0
- package/dist/WorkflowSpecSync.d.ts.map +1 -0
- package/dist/WorkflowSpecSync.js +474 -0
- package/dist/WorkflowSpecSync.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/TaskGraphOperations.d.ts +39 -0
- package/dist/operations/TaskGraphOperations.d.ts.map +1 -0
- package/dist/operations/TaskGraphOperations.js +168 -0
- package/dist/operations/TaskGraphOperations.js.map +1 -0
- package/dist/operations/WorkflowDraftOperation.d.ts +37 -0
- package/dist/operations/WorkflowDraftOperation.d.ts.map +1 -0
- package/dist/operations/WorkflowDraftOperation.js +141 -0
- package/dist/operations/WorkflowDraftOperation.js.map +1 -0
- package/dist/operations/WorkflowOperations.d.ts +22 -0
- package/dist/operations/WorkflowOperations.d.ts.map +1 -0
- package/dist/operations/WorkflowOperations.js +99 -0
- package/dist/operations/WorkflowOperations.js.map +1 -0
- package/dist/types.d.ts +328 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -8
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { IShutdownable } from '@memberjunction/global';
|
|
3
|
+
import { ProviderFactory, TaskActionRunner, TaskAgentRunner, TaskPromptRunner, TaskGraphDispatcherConfig, type TaskContinuationDeliverer, type TaskGraphObserver } from './types.js';
|
|
4
|
+
export declare class TaskGraphDispatcher implements IShutdownable {
|
|
5
|
+
private readonly providerFactory;
|
|
6
|
+
private readonly agentRunner;
|
|
7
|
+
private readonly contextUser;
|
|
8
|
+
/**
|
|
9
|
+
* Optional. Absent means a host that cannot post messages or start agent turns — a worker,
|
|
10
|
+
* a test. The dispatcher still records and logs every completion, so a graph's outcome is
|
|
11
|
+
* never lost; it simply is not announced.
|
|
12
|
+
*/
|
|
13
|
+
private readonly continuationDeliverer?;
|
|
14
|
+
/**
|
|
15
|
+
* Optional. Absent means nobody is watching — the dispatcher behaves identically, it just
|
|
16
|
+
* announces nothing.
|
|
17
|
+
*/
|
|
18
|
+
private readonly observer?;
|
|
19
|
+
/**
|
|
20
|
+
* Optional. Absent means this host cannot run action nodes; they stay Pending and visible
|
|
21
|
+
* rather than being failed, because "nobody here can run this" is not "this ran and broke".
|
|
22
|
+
*/
|
|
23
|
+
private readonly actionRunner?;
|
|
24
|
+
/**
|
|
25
|
+
* Optional. Absent means this host cannot run prompt nodes; they stay Pending and visible
|
|
26
|
+
* rather than being failed, for the same reason action nodes do.
|
|
27
|
+
*/
|
|
28
|
+
private readonly promptRunner?;
|
|
29
|
+
private readonly config;
|
|
30
|
+
private readonly claims;
|
|
31
|
+
private readonly conditionEvaluator;
|
|
32
|
+
private running;
|
|
33
|
+
private pollTimer;
|
|
34
|
+
private reconcileTimer;
|
|
35
|
+
/** Tasks this instance is currently executing — bounds concurrency and drives heartbeats. */
|
|
36
|
+
private readonly inFlight;
|
|
37
|
+
/** Guards against a slow poll overlapping the next tick. */
|
|
38
|
+
private polling;
|
|
39
|
+
/**
|
|
40
|
+
* The poll pass currently running, so `Stop` can wait for it.
|
|
41
|
+
*
|
|
42
|
+
* `clearInterval` cannot cancel a tick that has already fired, and a pass is a long sequence of
|
|
43
|
+
* awaits (provider, rollup, claim query) — so without this, `Stop` returns while a pass is still
|
|
44
|
+
* mid-flight and about to claim. Its tasks then land in `inFlight` AFTER the drain loop already
|
|
45
|
+
* saw an empty set, which is precisely the state the drain exists to prevent.
|
|
46
|
+
*/
|
|
47
|
+
private pollPass;
|
|
48
|
+
/** Graph → owning user, from the parent's durable metadata. Ownership never changes, so this never goes stale. */
|
|
49
|
+
private readonly ownerByParentID;
|
|
50
|
+
constructor(providerFactory: ProviderFactory, agentRunner: TaskAgentRunner, contextUser: UserInfo, config: Partial<TaskGraphDispatcherConfig> & Pick<TaskGraphDispatcherConfig, 'InstanceID'>,
|
|
51
|
+
/**
|
|
52
|
+
* Optional. Absent means a host that cannot post messages or start agent turns — a worker,
|
|
53
|
+
* a test. The dispatcher still records and logs every completion, so a graph's outcome is
|
|
54
|
+
* never lost; it simply is not announced.
|
|
55
|
+
*/
|
|
56
|
+
continuationDeliverer?: TaskContinuationDeliverer,
|
|
57
|
+
/**
|
|
58
|
+
* Optional. Absent means nobody is watching — the dispatcher behaves identically, it just
|
|
59
|
+
* announces nothing.
|
|
60
|
+
*/
|
|
61
|
+
observer?: TaskGraphObserver,
|
|
62
|
+
/**
|
|
63
|
+
* Optional. Absent means this host cannot run action nodes; they stay Pending and visible
|
|
64
|
+
* rather than being failed, because "nobody here can run this" is not "this ran and broke".
|
|
65
|
+
*/
|
|
66
|
+
actionRunner?: TaskActionRunner,
|
|
67
|
+
/**
|
|
68
|
+
* Optional. Absent means this host cannot run prompt nodes; they stay Pending and visible
|
|
69
|
+
* rather than being failed, for the same reason action nodes do.
|
|
70
|
+
*/
|
|
71
|
+
promptRunner?: TaskPromptRunner);
|
|
72
|
+
/**
|
|
73
|
+
* Announce something that happened, and never let the announcement matter.
|
|
74
|
+
*
|
|
75
|
+
* A frame is commentary on work, never a step of it, so an observer that throws must not be able
|
|
76
|
+
* to fail a task or stall a graph. Swallowing here rather than asking every implementation to be
|
|
77
|
+
* careful means one place enforces it.
|
|
78
|
+
*/
|
|
79
|
+
private emit;
|
|
80
|
+
/**
|
|
81
|
+
* Who a graph belongs to, memoized for the process's lifetime.
|
|
82
|
+
*
|
|
83
|
+
* Read from the parent's durable metadata rather than a column, because `Task.UserID` means
|
|
84
|
+
* "the person this task is waiting on" — setting it on a parent would make every graph look
|
|
85
|
+
* like a human task. Memoized because frames are emitted per step: without the cache, watching
|
|
86
|
+
* a run would cost one query per event, and observability that scales with work is the thing a
|
|
87
|
+
* push mechanism exists to avoid. Ownership never changes for a given graph, so the cache can
|
|
88
|
+
* never go stale.
|
|
89
|
+
*
|
|
90
|
+
* Skipped entirely when nobody is observing — the lookup exists only to address frames.
|
|
91
|
+
*/
|
|
92
|
+
private resolveOwner;
|
|
93
|
+
/**
|
|
94
|
+
* Begins dispatching.
|
|
95
|
+
*
|
|
96
|
+
* Runs reconciliation FIRST, before accepting any new work. On a restart this instance may be
|
|
97
|
+
* looking at tasks its own previous incarnation claimed and never released — reclaiming those
|
|
98
|
+
* up front is what turns a crash from "work stranded forever" into "work resumes".
|
|
99
|
+
*/
|
|
100
|
+
Start(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Stops accepting new work and waits for in-flight tasks to finish.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately does NOT release claims on the way out: an abandoned claim expires on its own,
|
|
105
|
+
* and releasing eagerly would hand a still-running task to another instance mid-execution.
|
|
106
|
+
* Letting the TTL do it is the safer failure mode.
|
|
107
|
+
*/
|
|
108
|
+
Stop(): Promise<void>;
|
|
109
|
+
/** Name shown in the shutdown drain log. */
|
|
110
|
+
readonly ShutdownName = "TaskGraphDispatcher";
|
|
111
|
+
/** {@link IShutdownable} — idempotent by way of `Stop`'s `running` guard. */
|
|
112
|
+
Shutdown(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Reclaims expired claims and reports anomalies.
|
|
115
|
+
*
|
|
116
|
+
* Also enforces the two schema promises that previously had no enforcer anywhere: agent tasks
|
|
117
|
+
* left `In Progress` with no claim are surfaced loudly rather than silently corrected, since
|
|
118
|
+
* that shape indicates tampering or a bug and Record Changes already carries the audit trail.
|
|
119
|
+
*/
|
|
120
|
+
Reconcile(): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* One dispatch pass: find claimable work, claim what fits under the concurrency cap, execute.
|
|
123
|
+
*
|
|
124
|
+
* Overlap-guarded — a pass that runs long simply skips the next tick rather than stacking, which
|
|
125
|
+
* would otherwise let a slow database multiply in-flight work past the cap.
|
|
126
|
+
*/
|
|
127
|
+
private pollOnce;
|
|
128
|
+
/**
|
|
129
|
+
* Executes one claimed task on its own provider, heartbeating until it settles.
|
|
130
|
+
*
|
|
131
|
+
* A fresh provider per task is the point of `ProviderFactory`: parallel tasks must not share a
|
|
132
|
+
* transaction scope or entity instances, or one task's work becomes visible inside another's.
|
|
133
|
+
*/
|
|
134
|
+
private executeClaimed;
|
|
135
|
+
/**
|
|
136
|
+
* Applies failure propagation and parent rollup across every graph with active work.
|
|
137
|
+
*
|
|
138
|
+
* All four decisions — what is eligible, what must block, what the parent status is, whether the
|
|
139
|
+
* graph is wedged — are delegated to the pure algorithms, unchanged from Phase 1.
|
|
140
|
+
*/
|
|
141
|
+
private propagateAndRollup;
|
|
142
|
+
/**
|
|
143
|
+
* Credits a finished graph's spending back to the agent run that submitted it.
|
|
144
|
+
*
|
|
145
|
+
* **Why this cannot happen during the run.** `BaseAgent` totals a run by walking its steps in
|
|
146
|
+
* memory at finalization — but a submitting run *ends at submission*. Submit-and-detach is the
|
|
147
|
+
* point: the run returns as soon as the graph is durable, and the graph executes afterwards,
|
|
148
|
+
* possibly minutes later on a different instance. At the moment the run computes its totals the
|
|
149
|
+
* spending has not happened yet, so there is nothing to count. The only place the number can be
|
|
150
|
+
* known is here, when the graph settles.
|
|
151
|
+
*
|
|
152
|
+
* **Why the `…Rollup` columns and not the plain ones.** `AIAgentRun` has carried six `…Rollup`
|
|
153
|
+
* columns since v3 that nothing has ever written — they exist for exactly this distinction:
|
|
154
|
+
*
|
|
155
|
+
* - `TotalCost` — what the run itself spent. For a Flow agent that is genuinely near zero: it
|
|
156
|
+
* compiled a graph and handed it off. This value is already final and is never rewritten here,
|
|
157
|
+
* so nothing that reads it today changes meaning, and no guardrail that already evaluated
|
|
158
|
+
* against it is retroactively falsified.
|
|
159
|
+
* - `TotalCostRollup` — the run plus everything it caused. Provisional until the graph settles,
|
|
160
|
+
* which is now.
|
|
161
|
+
*
|
|
162
|
+
* **The tree is the authority; these columns are its settlement-time cache.** The total is a SUM
|
|
163
|
+
* over `GetAgentRunTree`, not arithmetic of its own. The previous version walked the graph's
|
|
164
|
+
* child tasks and added each one's agent run, which was wrong in two ways that no test could
|
|
165
|
+
* see: a `Prompt` task has no agent run at all, so every prompt step's spend was simply missing;
|
|
166
|
+
* and it read each nested run's `…Rollup ?? …Total`, mixing a descendant-inclusive number with an
|
|
167
|
+
* own-spend one and depending on whether that nested graph happened to have settled yet. The
|
|
168
|
+
* tree already models every one of those cases — it reaches prompt runs through
|
|
169
|
+
* `Configuration.runtime.promptRunID`, and it descends into nested runs and their graphs
|
|
170
|
+
* structurally — so summing it cannot disagree with what the run viewer shows, because it IS
|
|
171
|
+
* what the run viewer shows.
|
|
172
|
+
*
|
|
173
|
+
* **This refuses rather than guesses.** A tree that failed to load, hit the depth cap, or does
|
|
174
|
+
* not contain the settling graph would still produce a number — a lower bound. Writing one would
|
|
175
|
+
* put an authoritative-looking total in a column every cost surface reads. Each of those cases
|
|
176
|
+
* logs and leaves the column alone, so `?? TotalCost` keeps its honest meaning: not settled.
|
|
177
|
+
*
|
|
178
|
+
* A graph with no submitting run (a scheduled job, a remote-operation caller) simply has nobody
|
|
179
|
+
* to credit — its own Task rows still carry the truth, and this returns quietly.
|
|
180
|
+
*/
|
|
181
|
+
private rollUpCostToSubmittingRun;
|
|
182
|
+
/**
|
|
183
|
+
* Clears a rollup that can no longer be trusted, and says why.
|
|
184
|
+
*
|
|
185
|
+
* **Why clear rather than leave.** The four `…Rollup` columns are a cache of the run tree, and
|
|
186
|
+
* every reader treats a value there as the total. When the tree cannot be summed, any value
|
|
187
|
+
* already in the column was computed from an EARLIER settlement — it excludes the graph that
|
|
188
|
+
* just finished, so it is not merely incomplete, it is a wrong total presented as a right one.
|
|
189
|
+
* `?? TotalCost` protects a reader from null, not from stale.
|
|
190
|
+
*
|
|
191
|
+
* Nulling restores the invariant this whole design rests on: **when the column is present, it
|
|
192
|
+
* equals the tree.** Absent means not settled, which is exactly what a reader should conclude.
|
|
193
|
+
* A run with no rollup yet is untouched — there is nothing stale to clear, and writing nulls
|
|
194
|
+
* over nulls would churn Record Changes for nothing.
|
|
195
|
+
*/
|
|
196
|
+
private clearStaleRollup;
|
|
197
|
+
/**
|
|
198
|
+
* Whether the settling graph is actually reachable from the submitting run's tree.
|
|
199
|
+
*
|
|
200
|
+
* Matched on the graph's parent Task id, which is the node the `TaskGraph` member of the query
|
|
201
|
+
* emits. A run that submitted a graph but recorded no `parentTaskID` produces a tree that stops
|
|
202
|
+
* at the run — structurally indistinguishable, at the SUM, from a run that never dispatched
|
|
203
|
+
* anything. This is the check that tells those two apart.
|
|
204
|
+
*/
|
|
205
|
+
private treeContainsGraph;
|
|
206
|
+
/**
|
|
207
|
+
* Ends a graph early because a prompt said the work is finished.
|
|
208
|
+
*
|
|
209
|
+
* **Why `Skipped` and not `Cancelled`.** Nothing went wrong and nobody intervened — the workflow
|
|
210
|
+
* reached its own conclusion before running every drawn step, which is exactly what a reasoning
|
|
211
|
+
* step is for. `Cancelled` would tell a reader someone stopped it; `Skipped` says these routes
|
|
212
|
+
* were not taken, which is true and already the vocabulary the fork machinery uses.
|
|
213
|
+
*
|
|
214
|
+
* The message is written to the parent so the graph carries its own answer, rather than the
|
|
215
|
+
* answer living only on the step that produced it.
|
|
216
|
+
*/
|
|
217
|
+
private endGraphEarly;
|
|
218
|
+
/**
|
|
219
|
+
* How deep the continuation chain already is, read from the graph's parent metadata.
|
|
220
|
+
*
|
|
221
|
+
* A run started by a graph inherits that graph's depth **plus one**. Without this every spawned
|
|
222
|
+
* run begins at zero, so a self-referencing flow — one that dispatches a graph containing itself
|
|
223
|
+
* — recurses without bound while the cap it should be hitting compares against a permanent zero.
|
|
224
|
+
*/
|
|
225
|
+
private graphContext;
|
|
226
|
+
/**
|
|
227
|
+
* Which failures the workflow drew a way out of.
|
|
228
|
+
*
|
|
229
|
+
* A Failed task with a **satisfied outgoing edge** is a handled failure: its author drew a
|
|
230
|
+
* recovery route and that route is now live. Downstream work should be released along it, and the
|
|
231
|
+
* parent should not roll up Failed because of a step the workflow explicitly planned around.
|
|
232
|
+
*
|
|
233
|
+
* Scoped to `failureSemantics: 'edges'` on purpose. Under `'block'` — every agent-emitted graph —
|
|
234
|
+
* a failure is terminal for its dependents whatever edges exist, because nobody drew those edges
|
|
235
|
+
* as a recovery path; they are ordinary sequencing, and treating them as recovery would let a
|
|
236
|
+
* graph sail past a failure it never anticipated.
|
|
237
|
+
*/
|
|
238
|
+
private computeHandledFailures;
|
|
239
|
+
/** The graph's child tasks, with the fields the rollup needs. */
|
|
240
|
+
private loadChildTasks;
|
|
241
|
+
/**
|
|
242
|
+
* Runs the graph's continuation exactly once, now that it has settled.
|
|
243
|
+
*
|
|
244
|
+
* **Why the delivery marker is written before the side effect.** Delivery is at-least-once by
|
|
245
|
+
* nature: the process can die between "the graph is done" and "the user has been told". Marking
|
|
246
|
+
* first and acting second means the worst case is a *missed* notification that shows up in the
|
|
247
|
+
* task record as delivered — recoverable, visible, and inspectable. Marking after would make the
|
|
248
|
+
* worst case a *repeated* notification on every reconciliation sweep, forever, which is both
|
|
249
|
+
* user-visible noise and, for `reinvoke`, an unbounded agent-run loop. Given one of the two has
|
|
250
|
+
* to be chosen, the quiet failure is the safe one.
|
|
251
|
+
*
|
|
252
|
+
* The marker is written with a compare-and-swap read-back, so two instances reconciling the same
|
|
253
|
+
* completed graph produce one winner rather than two.
|
|
254
|
+
*/
|
|
255
|
+
private deliverContinuation;
|
|
256
|
+
/** Reads the parent's durable continuation metadata through the shared parser. */
|
|
257
|
+
private readParentMetadata;
|
|
258
|
+
/**
|
|
259
|
+
* Stamps the delivery marker and confirms this instance won the race.
|
|
260
|
+
*
|
|
261
|
+
* `MJ: Tasks` stays user-writable (D20), so a plain "read, decide, write" is not enough — the
|
|
262
|
+
* read-back is what makes a lost race observable instead of producing a duplicate delivery.
|
|
263
|
+
*/
|
|
264
|
+
private claimContinuation;
|
|
265
|
+
/** One line describing how the graph ended, for the completion log and message delivery. */
|
|
266
|
+
private buildContinuationSummary;
|
|
267
|
+
/**
|
|
268
|
+
* Tells the assignee that a human task is ready, exactly once.
|
|
269
|
+
*
|
|
270
|
+
* **Once** matters more than it looks: eligibility is recomputed on every poll, so a task parked
|
|
271
|
+
* on a person for three days would otherwise re-notify every five seconds until they acted. The
|
|
272
|
+
* marker is the task's own `ClaimedBy` — a human task has no executor to claim it, so the column
|
|
273
|
+
* is free, and reusing it means the "already notified" fact is as durable and as crash-safe as
|
|
274
|
+
* every other piece of graph state. A restart cannot resend.
|
|
275
|
+
*
|
|
276
|
+
* Best-effort by design. A notification that fails to send must not stop the graph or the poll
|
|
277
|
+
* loop; the task is still visible in the Tasks UI, so the work is discoverable even when the
|
|
278
|
+
* nudge does not arrive.
|
|
279
|
+
*/
|
|
280
|
+
private notifyHumanTaskReady;
|
|
281
|
+
/**
|
|
282
|
+
* Parent tasks that still have work to do.
|
|
283
|
+
*
|
|
284
|
+
* `BypassCache` for the reason the caching guide names explicitly: **the claim protocol mutates
|
|
285
|
+
* these rows through direct SQL**, because the CAS guarantee IS the database's atomicity and a
|
|
286
|
+
* `BaseEntity.Save()` cannot express a guarded UPDATE. Direct DML fires no invalidation event,
|
|
287
|
+
* so a cached read of this query is stale the instant any task is claimed or completed — and
|
|
288
|
+
* the dispatcher would then be reading its own work queue through a cache its own writes never
|
|
289
|
+
* invalidate. Left cached, a completed task keeps reading as `In Progress` and the graph never
|
|
290
|
+
* rolls up: submitted work simply never settles.
|
|
291
|
+
*/
|
|
292
|
+
private findActiveGraphIDs;
|
|
293
|
+
/**
|
|
294
|
+
* Tasks eligible to claim right now, across all active graphs.
|
|
295
|
+
*
|
|
296
|
+
* Eligibility is decided by the pure algorithm rather than by SQL: expressing "all prerequisites
|
|
297
|
+
* complete" as a query is possible but would be a second, independently-maintained definition of
|
|
298
|
+
* the same rule, free to drift from the one the in-run executor uses.
|
|
299
|
+
*/
|
|
300
|
+
private findClaimableTasks;
|
|
301
|
+
/**
|
|
302
|
+
* Marks a human task as notified, so the request is raised exactly once.
|
|
303
|
+
*
|
|
304
|
+
* Written even when delivery threw. Retrying on every poll is a worse failure than one missed
|
|
305
|
+
* notification: the task stays visible in the inbox either way, whereas a notification storm is
|
|
306
|
+
* not self-correcting.
|
|
307
|
+
*/
|
|
308
|
+
private markHumanTaskNotified;
|
|
309
|
+
/**
|
|
310
|
+
* Raises the `MJ: AI Agent Requests` row a person answers to release this step.
|
|
311
|
+
*
|
|
312
|
+
* **Why that entity rather than something new.** It already models everything a workflow's human
|
|
313
|
+
* step needs — who is being asked, what for, a typed response schema, priority, expiry, and an
|
|
314
|
+
* inbox surface people already use. A second HITL substrate beside it would split the inbox in
|
|
315
|
+
* two and leave one of them without expiry or permissions.
|
|
316
|
+
*
|
|
317
|
+
* **What it deliberately does NOT set is `ResumingAgentRunID`.** A request normally suspends an
|
|
318
|
+
* agent run and resumes it. A workflow needs none of that: the graph OUTLIVES the run that
|
|
319
|
+
* submitted it, so nothing is suspended — the task sits Pending, every other branch keeps
|
|
320
|
+
* running, and answering settles the task. That column staying null is meaningful, not missing.
|
|
321
|
+
*/
|
|
322
|
+
private raiseHumanRequest;
|
|
323
|
+
/**
|
|
324
|
+
* The agent that owns this task's workflow — who the request is asked on behalf of.
|
|
325
|
+
*
|
|
326
|
+
* Reads the graph's parent row, falling back to the run that submitted it. A human step has no
|
|
327
|
+
* agent of its own by design: `AgentID` names what EXECUTES a step, and a person is not an agent.
|
|
328
|
+
*/
|
|
329
|
+
private owningAgentOf;
|
|
330
|
+
/** The still-open request for a task, if one exists. */
|
|
331
|
+
private findOpenRequest;
|
|
332
|
+
/**
|
|
333
|
+
* Settles a human task from the request a person answered.
|
|
334
|
+
*
|
|
335
|
+
* Runs on the poll rather than on a save hook, because the answer can arrive through any surface
|
|
336
|
+
* — the inbox, the API, a conversation — and only the dispatcher knows how to release the rest
|
|
337
|
+
* of the graph afterwards.
|
|
338
|
+
*
|
|
339
|
+
* **`ResponseData` becomes the task's output.** That is what makes a human step useful rather
|
|
340
|
+
* than a gate: a downstream edge can branch on what the person actually said, typed by the
|
|
341
|
+
* request's own ResponseSchema. A step that only recorded "approved" would force every decision
|
|
342
|
+
* back into a separate action.
|
|
343
|
+
*/
|
|
344
|
+
private settleAnsweredHumanTasks;
|
|
345
|
+
/**
|
|
346
|
+
* Re-opens a human step whose request was CANCELLED.
|
|
347
|
+
*
|
|
348
|
+
* `answeredRequestFor` deliberately excludes `Canceled`, because cancelling withdraws the ASK
|
|
349
|
+
* rather than deciding the step — the task is supposed to keep waiting "for whatever replaces
|
|
350
|
+
* it". Nothing replaced it. `raiseHumanRequest` refuses to raise twice (the notified marker on
|
|
351
|
+
* `ClaimedBy` is what stops the notification storm), so a cancelled request left the task Pending
|
|
352
|
+
* with no open request and no path to acquiring one: a workflow waiting forever on a question
|
|
353
|
+
* nobody is being asked.
|
|
354
|
+
*
|
|
355
|
+
* Clearing the marker is the whole fix — the next poll sees an un-notified Pending human task
|
|
356
|
+
* and raises a fresh request, which is exactly the replacement the design assumed. Bounded by
|
|
357
|
+
* human action: it takes another person cancelling again to come back here.
|
|
358
|
+
*/
|
|
359
|
+
private reopenCancelledHumanTasks;
|
|
360
|
+
/** The answered (or expired) request for a task, if any. */
|
|
361
|
+
private answeredRequestFor;
|
|
362
|
+
/**
|
|
363
|
+
* Expires requests whose deadline has passed.
|
|
364
|
+
*
|
|
365
|
+
* A deadline that nothing enforces is a comment. Without this an `ExpiresAt` in the past leaves
|
|
366
|
+
* the request `Requested` forever and the workflow waiting on it just as long.
|
|
367
|
+
*/
|
|
368
|
+
private expireOverdueRequests;
|
|
369
|
+
/** The agent run that submitted this task's graph, for provenance on the request. */
|
|
370
|
+
private submittingRunOf;
|
|
371
|
+
/** Loads a graph's children and edges in the shapes both the algorithms and mutation need. */
|
|
372
|
+
private loadGraphState;
|
|
373
|
+
/**
|
|
374
|
+
* Decides whether a conditional dependency edge is live.
|
|
375
|
+
*
|
|
376
|
+
* The condition sees the upstream task's outcome — its status and parsed output — which is the
|
|
377
|
+
* only information a runtime graph has to branch on. Returns `'drop'` only on a definite false;
|
|
378
|
+
* an unevaluable condition keeps the edge for the reason stated at the call site.
|
|
379
|
+
*/
|
|
380
|
+
private evaluateEdgeCondition;
|
|
381
|
+
/**
|
|
382
|
+
* An exclusive edge's condition as a three-way outcome.
|
|
383
|
+
*
|
|
384
|
+
* `ResolveExclusiveGroups` needs to tell "false" from "could not be evaluated": the first loses
|
|
385
|
+
* the branch, the second holds the whole group. The generic keep/drop path cannot express that
|
|
386
|
+
* difference, which is why exclusive edges take this route instead.
|
|
387
|
+
*/
|
|
388
|
+
private evaluateExclusiveCondition;
|
|
389
|
+
/**
|
|
390
|
+
* Everything an edge condition can see — the SUPERSET of both dialects.
|
|
391
|
+
*
|
|
392
|
+
* A flow condition is written against `payload` / `stepResult` / `flowContext` / `data` /
|
|
393
|
+
* `context`; the dispatcher's own conditions are written against `status` / `succeeded` /
|
|
394
|
+
* `failed` / `output` / `errorMessage`. Compiling flows onto this engine without the flow
|
|
395
|
+
* dialect would make every `payload.x` condition evaluate against nothing — silently, since an
|
|
396
|
+
* undefined property is simply falsy. Both dialects are readable here so a condition means the
|
|
397
|
+
* same thing on either engine.
|
|
398
|
+
*
|
|
399
|
+
* `payload` is the ORIGIN task's post-step snapshot. There is deliberately no "graph-wide
|
|
400
|
+
* payload": each task's output is its own, and inventing a merged one would give conditions a
|
|
401
|
+
* value the flow engine never had.
|
|
402
|
+
*/
|
|
403
|
+
private buildConditionContext;
|
|
404
|
+
/** Parsed `OutputPayload` of each completed dependency, keyed by that task's ID. */
|
|
405
|
+
private loadDependencyOutputs;
|
|
406
|
+
/**
|
|
407
|
+
* Runs one task's body, whatever kind of step it is.
|
|
408
|
+
*
|
|
409
|
+
* **Routing is on `StepType`, not on which key happens to be set.** A loop step carries the same
|
|
410
|
+
* `ActionID` or `AgentID` as an ordinary step — that key is what the loop *repeats* — so the old
|
|
411
|
+
* `task.ActionID ? action : agent` test would have run a loop exactly once and called it done.
|
|
412
|
+
* `StepType` is the only field that distinguishes them.
|
|
413
|
+
*
|
|
414
|
+
* Every branch is normalized to one shape so the recording path above stays single: an action has
|
|
415
|
+
* no agent run to point at, because its forensics live in `ActionExecutionLog` instead.
|
|
416
|
+
*/
|
|
417
|
+
private runTaskBody;
|
|
418
|
+
/**
|
|
419
|
+
* Runs a loop step: its body once per iteration, with the item and index in scope.
|
|
420
|
+
*
|
|
421
|
+
* The loop's own `Configuration` supplies the definition; the row's `ActionID` / `AgentID`
|
|
422
|
+
* supplies what to repeat. Per-iteration inputs are resolved fresh each pass — the bindings are
|
|
423
|
+
* merged into the payload before the mapping is applied, which is how a body can reference the
|
|
424
|
+
* current item at all.
|
|
425
|
+
*/
|
|
426
|
+
private runLoopTask;
|
|
427
|
+
/**
|
|
428
|
+
* Folds one pass's result into the loop's running payload.
|
|
429
|
+
*
|
|
430
|
+
* **With a body mapping**, the pass's declared outputs are filed where the author said to put
|
|
431
|
+
* them — including `name[]`, which appends, so a ForEach can collect one entry per item. That is
|
|
432
|
+
* the whole point of a loop over a collection, and it is only expressible per pass.
|
|
433
|
+
*
|
|
434
|
+
* **Without one**, the raw result is deep-merged, which is the pre-existing behaviour and the
|
|
435
|
+
* right default for a `While` that converges on a value: each pass refines what the condition
|
|
436
|
+
* reads. It is the wrong default for a ForEach that collects — hence the mapping.
|
|
437
|
+
*
|
|
438
|
+
* An unmapped output is reported per pass rather than swallowed, for the same reason
|
|
439
|
+
* {@link applyStepOutputMapping} reports it: a mapping that names something the body never
|
|
440
|
+
* returned means the pass did work that went nowhere, while everything reports success.
|
|
441
|
+
*/
|
|
442
|
+
private foldIterationOutput;
|
|
443
|
+
/**
|
|
444
|
+
* Files a step's result into the payload it hands downstream.
|
|
445
|
+
*
|
|
446
|
+
* **This is what makes a branch condition possible.** A workflow that branches on
|
|
447
|
+
* `payload.stockPrice` has that value only because this step mapped `CurrentPrice -> stockPrice`.
|
|
448
|
+
* Without it the condition reads `undefined` — merely falsy — so the workflow takes the other
|
|
449
|
+
* branch, finishes, and reports success with nothing to indicate anything went wrong.
|
|
450
|
+
*
|
|
451
|
+
* The incoming payload is carried through as well as the update, so a value written three steps
|
|
452
|
+
* back is still readable here. Returning only this step's own output is what used to limit a
|
|
453
|
+
* condition's view to its immediate predecessor.
|
|
454
|
+
*/
|
|
455
|
+
private applyStepOutputMapping;
|
|
456
|
+
/**
|
|
457
|
+
* Runs an Agent step, telling the runner where in the graph it sits.
|
|
458
|
+
*
|
|
459
|
+
* Depth and provenance are read together because they come from the same row: the graph's parent
|
|
460
|
+
* task knows both how many continuation hops led here and which run submitted it.
|
|
461
|
+
*/
|
|
462
|
+
private runAgentNode;
|
|
463
|
+
/**
|
|
464
|
+
* Completes the agent run that parked on this graph.
|
|
465
|
+
*
|
|
466
|
+
* **This is the other half of submit-and-detach.** A run that dispatches a graph does not
|
|
467
|
+
* complete at submission — it ends `Paused`, because reporting `Completed` above a workflow
|
|
468
|
+
* where nothing has happened yet is a claim the row cannot support. The run's lifecycle is
|
|
469
|
+
* finished HERE, when the graph it was waiting on actually settles, which is the first moment
|
|
470
|
+
* the answer exists.
|
|
471
|
+
*
|
|
472
|
+
* Doing it from the dispatcher rather than by awaiting in the agent is what keeps the properties
|
|
473
|
+
* that made detach right in the first place: a graph containing a human approval can park for
|
|
474
|
+
* days without holding a conversation turn open, and a graph reclaimed by another instance after
|
|
475
|
+
* a crash still settles its submitting run, because the settling happens wherever the graph
|
|
476
|
+
* finishes rather than wherever it started.
|
|
477
|
+
*
|
|
478
|
+
* **Only a parked run is touched.** A run that is already `Completed`, `Failed` or `Cancelled`
|
|
479
|
+
* reached that state for its own reasons — a second graph settling later, a run the user
|
|
480
|
+
* cancelled, a run that failed after submitting — and overwriting it would rewrite history from
|
|
481
|
+
* the outside. The `Paused` predicate is the whole guard.
|
|
482
|
+
*
|
|
483
|
+
* @param graphStatus the parent rollup's status: what the workflow as a whole did
|
|
484
|
+
*/
|
|
485
|
+
private settleSubmittingRun;
|
|
486
|
+
/**
|
|
487
|
+
* Gives every step that lacks one a position, once the graph has finished.
|
|
488
|
+
*
|
|
489
|
+
* **Why the run stores geometry at all.** A `TaskGraphSpec` is a logical structure with no
|
|
490
|
+
* layout field, so a graph an agent emitted has no opinion about where its boxes go. Every
|
|
491
|
+
* viewer was therefore laying it out for itself at render time — and a viewer that failed to
|
|
492
|
+
* (because the canvas measures nodes it has not drawn yet) fell back to every node at the
|
|
493
|
+
* origin, piled on one another, with the zoom-to-fit that follows fitting a one-node bounding
|
|
494
|
+
* box. Settling it once, server-side, means the agent-run canvas, the Workflows runs tab and
|
|
495
|
+
* anything built later all draw the same picture, and none of them has to compute it.
|
|
496
|
+
*
|
|
497
|
+
* **An authored position is never overwritten.** A workflow compiled from a Flow agent carries
|
|
498
|
+
* the arrangement someone dragged into place; replacing it with an algorithm's guess would
|
|
499
|
+
* discard a deliberate act. Only steps with no geometry get one, so a partially-arranged graph
|
|
500
|
+
* keeps what it has.
|
|
501
|
+
*
|
|
502
|
+
* Failure here is logged and swallowed: this is presentation. A graph whose work completed must
|
|
503
|
+
* not be reported as failed because its picture could not be saved.
|
|
504
|
+
*/
|
|
505
|
+
private persistComputedLayout;
|
|
506
|
+
/**
|
|
507
|
+
* The earliest moment any step in the graph began, or null when none has.
|
|
508
|
+
*
|
|
509
|
+
* Null is a real answer — a graph whose tasks are all still Pending has not started — and is
|
|
510
|
+
* deliberately not collapsed to "now", which would date the graph from whenever this pass
|
|
511
|
+
* happened to run.
|
|
512
|
+
*/
|
|
513
|
+
private earliestStart;
|
|
514
|
+
/**
|
|
515
|
+
* The step's Configuration with this run's artefacts folded in, or `undefined` to leave it be.
|
|
516
|
+
*
|
|
517
|
+
* **Merged into the authored bag, never written over it.** The Configuration column holds the
|
|
518
|
+
* step's definition — its loop body, its mappings, its policy, the position someone dragged it
|
|
519
|
+
* to. Writing a fresh object containing only `runtime` would erase all of that the first time a
|
|
520
|
+
* prompt step completed, which is the kind of loss that surfaces much later as a workflow that
|
|
521
|
+
* mysteriously stopped mapping its output.
|
|
522
|
+
*
|
|
523
|
+
* Returns `undefined` when there is nothing to record, so the guarded write omits the column
|
|
524
|
+
* rather than rewriting it with what it already held.
|
|
525
|
+
*/
|
|
526
|
+
private configurationWithRuntime;
|
|
527
|
+
/**
|
|
528
|
+
* Reads a step's Configuration bag, tolerating a row whose JSON cannot be parsed.
|
|
529
|
+
*
|
|
530
|
+
* Unparseable configuration is logged rather than thrown: the step has already RUN by the time
|
|
531
|
+
* this is called, and refusing to record its outcome because its definition is malformed would
|
|
532
|
+
* discard the result of real work and leave the task claimed until the claim lapsed.
|
|
533
|
+
*/
|
|
534
|
+
private parseConfiguration;
|
|
535
|
+
/**
|
|
536
|
+
* The payload a step sees: everything its prerequisites produced, plus its own declared input.
|
|
537
|
+
*
|
|
538
|
+
* **Why the outputs are merged rather than kept per-task.** A flow carried ONE payload that
|
|
539
|
+
* accumulated as it went, so a condition on the edge into step C could read a value step A wrote.
|
|
540
|
+
* Handing each task only its immediate predecessor's output would silently narrow that: the
|
|
541
|
+
* condition reads `undefined`, which is falsy, and the workflow quietly takes a different route
|
|
542
|
+
* than the flow it was compiled from. Merging in dependency order restores the accumulation.
|
|
543
|
+
*
|
|
544
|
+
* Later prerequisites win on a key collision, matching a flow's own last-write-wins behaviour.
|
|
545
|
+
*/
|
|
546
|
+
private mergedPayload;
|
|
547
|
+
}
|
|
548
|
+
//# sourceMappingURL=TaskGraphDispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskGraphDispatcher.d.ts","sourceRoot":"","sources":["../src/TaskGraphDispatcher.ts"],"names":[],"mappings":"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"}
|