@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.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +167 -27
  3. package/dist/DispatcherConditionEvaluator.d.ts +10 -0
  4. package/dist/DispatcherConditionEvaluator.d.ts.map +1 -0
  5. package/dist/DispatcherConditionEvaluator.js +27 -0
  6. package/dist/DispatcherConditionEvaluator.js.map +1 -0
  7. package/dist/TaskClaimStore.d.ts +125 -0
  8. package/dist/TaskClaimStore.d.ts.map +1 -0
  9. package/dist/TaskClaimStore.js +223 -0
  10. package/dist/TaskClaimStore.js.map +1 -0
  11. package/dist/TaskGraphDispatcher.d.ts +548 -0
  12. package/dist/TaskGraphDispatcher.d.ts.map +1 -0
  13. package/dist/TaskGraphDispatcher.js +2232 -0
  14. package/dist/TaskGraphDispatcher.js.map +1 -0
  15. package/dist/TaskGraphService.d.ts +247 -0
  16. package/dist/TaskGraphService.d.ts.map +1 -0
  17. package/dist/TaskGraphService.js +753 -0
  18. package/dist/TaskGraphService.js.map +1 -0
  19. package/dist/TaskGraphSubmitterImpl.d.ts +7 -0
  20. package/dist/TaskGraphSubmitterImpl.d.ts.map +1 -0
  21. package/dist/TaskGraphSubmitterImpl.js +52 -0
  22. package/dist/TaskGraphSubmitterImpl.js.map +1 -0
  23. package/dist/TaskLoopExecutor.d.ts +62 -0
  24. package/dist/TaskLoopExecutor.d.ts.map +1 -0
  25. package/dist/TaskLoopExecutor.js +248 -0
  26. package/dist/TaskLoopExecutor.js.map +1 -0
  27. package/dist/WorkflowSpecSync.d.ts +197 -0
  28. package/dist/WorkflowSpecSync.d.ts.map +1 -0
  29. package/dist/WorkflowSpecSync.js +474 -0
  30. package/dist/WorkflowSpecSync.js.map +1 -0
  31. package/dist/index.d.ts +19 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +19 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/operations/TaskGraphOperations.d.ts +39 -0
  36. package/dist/operations/TaskGraphOperations.d.ts.map +1 -0
  37. package/dist/operations/TaskGraphOperations.js +168 -0
  38. package/dist/operations/TaskGraphOperations.js.map +1 -0
  39. package/dist/operations/WorkflowDraftOperation.d.ts +37 -0
  40. package/dist/operations/WorkflowDraftOperation.d.ts.map +1 -0
  41. package/dist/operations/WorkflowDraftOperation.js +141 -0
  42. package/dist/operations/WorkflowDraftOperation.js.map +1 -0
  43. package/dist/operations/WorkflowOperations.d.ts +22 -0
  44. package/dist/operations/WorkflowOperations.d.ts.map +1 -0
  45. package/dist/operations/WorkflowOperations.js +99 -0
  46. package/dist/operations/WorkflowOperations.js.map +1 -0
  47. package/dist/types.d.ts +328 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +9 -0
  50. package/dist/types.js.map +1 -0
  51. 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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"}