@codemation/core 0.0.18 → 0.2.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 +24 -0
- package/dist/EngineRuntimeRegistration.types-0sgV2XL2.d.ts +42 -0
- package/dist/EngineWorkflowRunnerService-Dx7bJsJR.d.cts +73 -0
- package/dist/InMemoryRunDataFactory-qIYQEar7.d.cts +94 -0
- package/dist/{InMemoryLiveWorkflowRepository-DxoualoC.d.ts → RunIntentService-BCvGdOSY.d.ts} +438 -9
- package/dist/{RunIntentService-BB4nqX3-.js → RunIntentService-BFA48UpH.js} +308 -71
- package/dist/RunIntentService-BFA48UpH.js.map +1 -0
- package/dist/{InMemoryLiveWorkflowRepository-orY1VsWG.d.cts → RunIntentService-CV8izV8t.d.cts} +214 -7
- package/dist/{RunIntentService-nRx-m0Xs.cjs → RunIntentService-DcxXf_AM.cjs} +318 -69
- package/dist/RunIntentService-DcxXf_AM.cjs.map +1 -0
- package/dist/bootstrap/index.cjs +14 -1135
- package/dist/bootstrap/index.d.cts +7 -60
- package/dist/bootstrap/index.d.ts +4 -40
- package/dist/bootstrap/index.js +3 -1122
- package/dist/bootstrap-D67Sf2BF.js +1136 -0
- package/dist/bootstrap-D67Sf2BF.js.map +1 -0
- package/dist/bootstrap-DoQHAEQJ.cjs +1203 -0
- package/dist/bootstrap-DoQHAEQJ.cjs.map +1 -0
- package/dist/{index-B4_ZRTyI.d.ts → index-BHmrZIHp.d.ts} +32 -251
- package/dist/index.cjs +98 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +92 -218
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +329 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +181 -4
- package/dist/testing.d.ts +181 -3
- package/dist/testing.js +319 -2
- package/dist/testing.js.map +1 -1
- package/dist/workflowActivationPolicy-B8HzTk3o.js +201 -0
- package/dist/workflowActivationPolicy-B8HzTk3o.js.map +1 -0
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs +231 -0
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +1 -0
- package/package.json +1 -1
- package/src/ai/AgentConnectionNodeCollector.ts +99 -0
- package/src/ai/AgentToolFactory.ts +38 -2
- package/src/ai/AiHost.ts +1 -1
- package/src/browser.ts +11 -0
- package/src/contracts/executionPersistenceContracts.ts +186 -0
- package/src/contracts/index.ts +1 -0
- package/src/contracts/runFinishedAtFactory.ts +5 -2
- package/src/contracts/runTypes.ts +10 -0
- package/src/contracts/runtimeTypes.ts +6 -2
- package/src/contracts/workflowTypes.ts +3 -2
- package/src/events/EventPublishingWorkflowExecutionRepository.ts +5 -0
- package/src/execution/ActivationEnqueueService.ts +8 -8
- package/src/execution/PersistedRunStateTerminalBuilder.ts +3 -0
- package/src/index.ts +6 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +11 -6
- package/src/orchestration/RunContinuationService.ts +94 -24
- package/src/planning/CurrentStateFrontierPlanner.ts +24 -1
- package/src/runStorage/InMemoryWorkflowExecutionRepository.ts +14 -1
- package/src/runtime/RunIntentService.ts +68 -14
- package/src/scheduler/DefaultDrivingScheduler.ts +21 -11
- package/src/scheduler/InlineDrivingScheduler.ts +17 -21
- package/src/testing/CapturingScheduler.ts +15 -0
- package/src/testing/EngineTestKitRunIdFactory.ts +24 -0
- package/src/testing/InMemoryTriggerSetupStateRepository.ts +21 -0
- package/src/testing/PrefixedSequentialIdGenerator.ts +17 -0
- package/src/testing/RegistrarEngineTestKit.types.ts +76 -0
- package/src/testing/RegistrarEngineTestKitFactory.ts +154 -0
- package/src/testing/SubWorkflowRunnerTestNode.ts +83 -0
- package/src/testing/WorkflowTestHarnessManualTrigger.ts +39 -0
- package/src/testing/WorkflowTestKit.types.ts +9 -0
- package/src/testing/WorkflowTestKitBuilder.ts +77 -0
- package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +17 -0
- package/src/testing/WorkflowTestKitRunNodeWorkflowFactory.ts +26 -0
- package/src/testing.ts +19 -0
- package/src/types/index.ts +1 -0
- package/src/workflow/definition/ConnectionNodeIdFactory.ts +28 -0
- package/dist/InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs +0 -151
- package/dist/InMemoryLiveWorkflowRepository-BTzHpQ6e.cjs.map +0 -1
- package/dist/InMemoryLiveWorkflowRepository-BoLNnVLg.js +0 -139
- package/dist/InMemoryLiveWorkflowRepository-BoLNnVLg.js.map +0 -1
- package/dist/RunIntentService-BB4nqX3-.js.map +0 -1
- package/dist/RunIntentService-ByuUYsAL.d.cts +0 -279
- package/dist/RunIntentService-nRx-m0Xs.cjs.map +0 -1
- package/dist/WorkflowSnapshotCodec-DSEzKyt3.d.cts +0 -22
- package/dist/bootstrap/index.cjs.map +0 -1
- package/dist/bootstrap/index.js.map +0 -1
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
NodeId,
|
|
6
6
|
NodeInputsByPort,
|
|
7
7
|
NodeOutputs,
|
|
8
|
+
PendingNodeExecution,
|
|
9
|
+
PersistedRunSchedulingState,
|
|
8
10
|
PersistedRunState,
|
|
9
11
|
RunDataFactory,
|
|
10
12
|
RunId,
|
|
@@ -31,7 +33,6 @@ import { NodeEventPublisher } from "../events/NodeEventPublisher";
|
|
|
31
33
|
|
|
32
34
|
import { ActivationEnqueueService } from "../execution/ActivationEnqueueService";
|
|
33
35
|
import { NodeExecutionSnapshotFactory } from "../execution/NodeExecutionSnapshotFactory";
|
|
34
|
-
import { NodeInputsByPortFactory } from "../execution/NodeInputsByPortFactory";
|
|
35
36
|
import { NodeRunStateWriterFactory } from "../execution/NodeRunStateWriterFactory";
|
|
36
37
|
import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
|
|
37
38
|
import { PersistedRunStateTerminalBuilder } from "../execution/PersistedRunStateTerminalBuilder";
|
|
@@ -65,9 +66,13 @@ export class RunContinuationService {
|
|
|
65
66
|
nodeId: NodeId;
|
|
66
67
|
inputsByPort: NodeInputsByPort;
|
|
67
68
|
}): Promise<void> {
|
|
68
|
-
const state = await
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const [state, schedulingState] = await Promise.all([
|
|
70
|
+
this.workflowExecutionRepository.load(args.runId),
|
|
71
|
+
this.workflowExecutionRepository.loadSchedulingState(args.runId),
|
|
72
|
+
]);
|
|
73
|
+
const pendingExecution = schedulingState?.pending;
|
|
74
|
+
if (!state || !pendingExecution) return;
|
|
75
|
+
if (pendingExecution.activationId !== args.activationId || pendingExecution.nodeId !== args.nodeId) return;
|
|
71
76
|
|
|
72
77
|
const startedAt = new Date().toISOString();
|
|
73
78
|
const previous = state.nodeSnapshotsByNodeId?.[args.nodeId];
|
|
@@ -98,12 +103,21 @@ export class RunContinuationService {
|
|
|
98
103
|
nodeId: NodeId;
|
|
99
104
|
outputs: NodeOutputs;
|
|
100
105
|
}): Promise<RunResult> {
|
|
101
|
-
const state = await
|
|
106
|
+
const [state, schedulingState] = await Promise.all([
|
|
107
|
+
this.workflowExecutionRepository.load(args.runId),
|
|
108
|
+
this.workflowExecutionRepository.loadSchedulingState(args.runId),
|
|
109
|
+
]);
|
|
102
110
|
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
const pendingExecution = this.requirePendingExecution(
|
|
112
|
+
args.runId,
|
|
113
|
+
args.activationId,
|
|
114
|
+
args.nodeId,
|
|
115
|
+
state,
|
|
116
|
+
schedulingState,
|
|
117
|
+
);
|
|
118
|
+
if (pendingExecution.activationId !== args.activationId)
|
|
105
119
|
throw new Error(`activationId mismatch for run ${args.runId}`);
|
|
106
|
-
if (
|
|
120
|
+
if (pendingExecution.nodeId !== args.nodeId) throw new Error(`nodeId mismatch for run ${args.runId}`);
|
|
107
121
|
|
|
108
122
|
const wf = this.resolvePersistedWorkflow(state);
|
|
109
123
|
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
@@ -135,7 +149,7 @@ export class RunContinuationService {
|
|
|
135
149
|
activationId: args.activationId,
|
|
136
150
|
parent: state.parent,
|
|
137
151
|
finishedAt: completedAt,
|
|
138
|
-
inputsByPort:
|
|
152
|
+
inputsByPort: pendingExecution.inputsByPort,
|
|
139
153
|
outputs: args.outputs,
|
|
140
154
|
});
|
|
141
155
|
|
|
@@ -154,6 +168,7 @@ export class RunContinuationService {
|
|
|
154
168
|
...(state.nodeSnapshotsByNodeId ?? {}),
|
|
155
169
|
[args.nodeId]: completedSnapshot,
|
|
156
170
|
},
|
|
171
|
+
finishedAtIso: completedAt,
|
|
157
172
|
});
|
|
158
173
|
await this.workflowExecutionRepository.save(completedState);
|
|
159
174
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -174,8 +189,8 @@ export class RunContinuationService {
|
|
|
174
189
|
return result;
|
|
175
190
|
}
|
|
176
191
|
|
|
177
|
-
const batchId =
|
|
178
|
-
const queue: RunQueueEntry[] = (
|
|
192
|
+
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
193
|
+
const queue: RunQueueEntry[] = (schedulingState?.queue ?? []).map((q) => ({ ...q, batchId: q.batchId ?? batchId }));
|
|
179
194
|
const nextNodeSnapshotsByNodeId = {
|
|
180
195
|
...(state.nodeSnapshotsByNodeId ?? {}),
|
|
181
196
|
[args.nodeId]: completedSnapshot,
|
|
@@ -220,6 +235,7 @@ export class RunContinuationService {
|
|
|
220
235
|
queue: [],
|
|
221
236
|
outputsByNode: data.dump(),
|
|
222
237
|
nodeSnapshotsByNodeId: nextNodeSnapshotsByNodeId,
|
|
238
|
+
finishedAtIso: completedAt,
|
|
223
239
|
});
|
|
224
240
|
await this.workflowExecutionRepository.save(completedState);
|
|
225
241
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -250,6 +266,7 @@ export class RunContinuationService {
|
|
|
250
266
|
queue: queue.map((q) => ({ ...q })),
|
|
251
267
|
outputsByNode: data.dump(),
|
|
252
268
|
nodeSnapshotsByNodeId: nextNodeSnapshotsByNodeId,
|
|
269
|
+
finishedAtIso: completedAt,
|
|
253
270
|
});
|
|
254
271
|
await this.workflowExecutionRepository.save(failedState);
|
|
255
272
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -312,12 +329,21 @@ export class RunContinuationService {
|
|
|
312
329
|
nodeId: NodeId;
|
|
313
330
|
error: Error;
|
|
314
331
|
}): Promise<RunResult> {
|
|
315
|
-
const state = await
|
|
332
|
+
const [state, schedulingState] = await Promise.all([
|
|
333
|
+
this.workflowExecutionRepository.load(args.runId),
|
|
334
|
+
this.workflowExecutionRepository.loadSchedulingState(args.runId),
|
|
335
|
+
]);
|
|
316
336
|
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
317
|
-
|
|
318
|
-
|
|
337
|
+
const pendingExecution = this.requirePendingExecution(
|
|
338
|
+
args.runId,
|
|
339
|
+
args.activationId,
|
|
340
|
+
args.nodeId,
|
|
341
|
+
state,
|
|
342
|
+
schedulingState,
|
|
343
|
+
);
|
|
344
|
+
if (pendingExecution.activationId !== args.activationId)
|
|
319
345
|
throw new Error(`activationId mismatch for run ${args.runId}`);
|
|
320
|
-
if (
|
|
346
|
+
if (pendingExecution.nodeId !== args.nodeId) throw new Error(`nodeId mismatch for run ${args.runId}`);
|
|
321
347
|
|
|
322
348
|
const wf = this.resolvePersistedWorkflow(state);
|
|
323
349
|
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
@@ -327,15 +353,28 @@ export class RunContinuationService {
|
|
|
327
353
|
? this.asWebhookControlSignal(args.error)
|
|
328
354
|
: undefined;
|
|
329
355
|
if (webhookControlSignal) {
|
|
330
|
-
return await this.resumeFromWebhookControl({
|
|
356
|
+
return await this.resumeFromWebhookControl({
|
|
357
|
+
state,
|
|
358
|
+
schedulingState,
|
|
359
|
+
pendingExecution,
|
|
360
|
+
workflow: wf,
|
|
361
|
+
args,
|
|
362
|
+
signal: webhookControlSignal,
|
|
363
|
+
});
|
|
331
364
|
}
|
|
332
365
|
|
|
333
366
|
if (failedDefinition && failedDefinition.kind === "node") {
|
|
334
367
|
const nodeHandler = this.policyErrorServices.resolveNodeErrorHandler(failedDefinition.config.nodeErrorHandler);
|
|
335
368
|
if (nodeHandler) {
|
|
336
369
|
try {
|
|
337
|
-
const ctx = this.buildNodeExecutionContextForPending(
|
|
338
|
-
|
|
370
|
+
const ctx = this.buildNodeExecutionContextForPending(
|
|
371
|
+
state,
|
|
372
|
+
pendingExecution,
|
|
373
|
+
wf,
|
|
374
|
+
failedDefinition,
|
|
375
|
+
args.nodeId,
|
|
376
|
+
);
|
|
377
|
+
const inputsByPort = pendingExecution.inputsByPort;
|
|
339
378
|
const portKeys = Object.keys(inputsByPort);
|
|
340
379
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? ("single" as const) : ("multi" as const);
|
|
341
380
|
const items = inputsByPort.in ?? [];
|
|
@@ -368,19 +407,20 @@ export class RunContinuationService {
|
|
|
368
407
|
activationId: args.activationId,
|
|
369
408
|
parent: state.parent,
|
|
370
409
|
finishedAt,
|
|
371
|
-
inputsByPort:
|
|
410
|
+
inputsByPort: pendingExecution.inputsByPort,
|
|
372
411
|
error: args.error,
|
|
373
412
|
});
|
|
374
413
|
const failedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
375
414
|
state,
|
|
376
415
|
engineCounters: state.engineCounters ?? { completedNodeActivations: 0 },
|
|
377
416
|
status: "failed",
|
|
378
|
-
queue: (
|
|
417
|
+
queue: (schedulingState?.queue ?? []).map((q) => ({ ...q })),
|
|
379
418
|
outputsByNode: state.outputsByNode,
|
|
380
419
|
nodeSnapshotsByNodeId: {
|
|
381
420
|
...(state.nodeSnapshotsByNodeId ?? {}),
|
|
382
421
|
[args.nodeId]: failedSnapshot,
|
|
383
422
|
},
|
|
423
|
+
finishedAtIso: finishedAt,
|
|
384
424
|
});
|
|
385
425
|
await this.workflowExecutionRepository.save(failedState);
|
|
386
426
|
await this.nodeEventPublisher.publish("nodeFailed", failedSnapshot);
|
|
@@ -474,6 +514,8 @@ export class RunContinuationService {
|
|
|
474
514
|
|
|
475
515
|
private async resumeFromWebhookControl(args: {
|
|
476
516
|
state: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>;
|
|
517
|
+
schedulingState: PersistedRunSchedulingState | undefined;
|
|
518
|
+
pendingExecution: PendingNodeExecution;
|
|
477
519
|
workflow: WorkflowDefinition;
|
|
478
520
|
args: { runId: RunId; activationId: NodeActivationId; nodeId: NodeId; error: Error };
|
|
479
521
|
signal: WebhookControlSignal;
|
|
@@ -495,7 +537,7 @@ export class RunContinuationService {
|
|
|
495
537
|
activationId: args.args.activationId,
|
|
496
538
|
parent: args.state.parent,
|
|
497
539
|
finishedAt: new Date().toISOString(),
|
|
498
|
-
inputsByPort: args.
|
|
540
|
+
inputsByPort: args.pendingExecution.inputsByPort,
|
|
499
541
|
outputs: triggerOutputs,
|
|
500
542
|
});
|
|
501
543
|
|
|
@@ -514,6 +556,7 @@ export class RunContinuationService {
|
|
|
514
556
|
...(args.state.nodeSnapshotsByNodeId ?? {}),
|
|
515
557
|
[args.args.nodeId]: completedSnapshot,
|
|
516
558
|
},
|
|
559
|
+
finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt,
|
|
517
560
|
});
|
|
518
561
|
await this.workflowExecutionRepository.save(completedState);
|
|
519
562
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -552,6 +595,7 @@ export class RunContinuationService {
|
|
|
552
595
|
...(args.state.nodeSnapshotsByNodeId ?? {}),
|
|
553
596
|
[args.args.nodeId]: completedSnapshot,
|
|
554
597
|
},
|
|
598
|
+
finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt,
|
|
555
599
|
});
|
|
556
600
|
await this.workflowExecutionRepository.save(completedState);
|
|
557
601
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -580,8 +624,8 @@ export class RunContinuationService {
|
|
|
580
624
|
return result;
|
|
581
625
|
}
|
|
582
626
|
|
|
583
|
-
const batchId = args.
|
|
584
|
-
const queue: RunQueueEntry[] = (args.
|
|
627
|
+
const batchId = args.pendingExecution.batchId ?? "batch_1";
|
|
628
|
+
const queue: RunQueueEntry[] = (args.schedulingState?.queue ?? []).map((entry) => ({
|
|
585
629
|
...entry,
|
|
586
630
|
batchId: entry.batchId ?? batchId,
|
|
587
631
|
}));
|
|
@@ -603,6 +647,7 @@ export class RunContinuationService {
|
|
|
603
647
|
...(args.state.nodeSnapshotsByNodeId ?? {}),
|
|
604
648
|
[args.args.nodeId]: completedSnapshot,
|
|
605
649
|
},
|
|
650
|
+
finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt,
|
|
606
651
|
});
|
|
607
652
|
await this.workflowExecutionRepository.save(completedState);
|
|
608
653
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -643,6 +688,7 @@ export class RunContinuationService {
|
|
|
643
688
|
...(args.state.nodeSnapshotsByNodeId ?? {}),
|
|
644
689
|
[args.args.nodeId]: completedSnapshot,
|
|
645
690
|
},
|
|
691
|
+
finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt,
|
|
646
692
|
});
|
|
647
693
|
await this.workflowExecutionRepository.save(failedState);
|
|
648
694
|
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
@@ -750,6 +796,7 @@ export class RunContinuationService {
|
|
|
750
796
|
|
|
751
797
|
private buildNodeExecutionContextForPending(
|
|
752
798
|
state: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>,
|
|
799
|
+
pendingExecution: PendingNodeExecution,
|
|
753
800
|
wf: WorkflowDefinition,
|
|
754
801
|
def: Readonly<{ id: NodeId; config: NodeExecutionContext["config"] }>,
|
|
755
802
|
nodeId: NodeId,
|
|
@@ -767,7 +814,7 @@ export class RunContinuationService {
|
|
|
767
814
|
data,
|
|
768
815
|
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
769
816
|
});
|
|
770
|
-
const activationId =
|
|
817
|
+
const activationId = pendingExecution.activationId;
|
|
771
818
|
return {
|
|
772
819
|
...base,
|
|
773
820
|
data,
|
|
@@ -779,6 +826,29 @@ export class RunContinuationService {
|
|
|
779
826
|
};
|
|
780
827
|
}
|
|
781
828
|
|
|
829
|
+
private requirePendingExecution(
|
|
830
|
+
runId: RunId,
|
|
831
|
+
activationId: NodeActivationId,
|
|
832
|
+
nodeId: NodeId,
|
|
833
|
+
state: PersistedRunState,
|
|
834
|
+
schedulingState?: PersistedRunSchedulingState,
|
|
835
|
+
): PendingNodeExecution {
|
|
836
|
+
if (state.status !== "pending") {
|
|
837
|
+
throw new Error(`Run ${runId} is not pending`);
|
|
838
|
+
}
|
|
839
|
+
const pendingExecution = schedulingState?.pending;
|
|
840
|
+
if (!pendingExecution) {
|
|
841
|
+
throw new Error(`Run ${runId} is not pending`);
|
|
842
|
+
}
|
|
843
|
+
if (pendingExecution.activationId !== activationId) {
|
|
844
|
+
throw new Error(`activationId mismatch for run ${runId}`);
|
|
845
|
+
}
|
|
846
|
+
if (pendingExecution.nodeId !== nodeId) {
|
|
847
|
+
throw new Error(`nodeId mismatch for run ${runId}`);
|
|
848
|
+
}
|
|
849
|
+
return pendingExecution;
|
|
850
|
+
}
|
|
851
|
+
|
|
782
852
|
private resolveEngineLimitsFromState(state: PersistedRunState): {
|
|
783
853
|
engineMaxNodeActivations: number;
|
|
784
854
|
engineMaxSubworkflowDepth: number;
|
|
@@ -303,7 +303,17 @@ export class CurrentStateFrontierPlanner {
|
|
|
303
303
|
if (!incomingEdge) {
|
|
304
304
|
return false;
|
|
305
305
|
}
|
|
306
|
-
|
|
306
|
+
if (!this.hasOutputPort(currentState, incomingEdge.from.nodeId, incomingEdge.from.output)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (this.usesCollect(nodeId)) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
const items = this.resolveOutputItems(currentState, incomingEdge.from.nodeId, incomingEdge.from.output);
|
|
313
|
+
if (items.length > 0) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
return this.shouldContinueAfterEmptyOutputFromSource(incomingEdge.from.nodeId);
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
private resolveInput(currentState: RunCurrentState, nodeId: NodeId, input: InputPortKey): Items {
|
|
@@ -335,6 +345,19 @@ export class CurrentStateFrontierPlanner {
|
|
|
335
345
|
return currentState.outputsByNode[nodeId]?.[output] ?? [];
|
|
336
346
|
}
|
|
337
347
|
|
|
348
|
+
private usesCollect(nodeId: NodeId): boolean {
|
|
349
|
+
const expectedInputs = this.topology.expectedInputsByNode.get(nodeId) ?? [];
|
|
350
|
+
return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private shouldContinueAfterEmptyOutputFromSource(nodeId: NodeId): boolean {
|
|
354
|
+
const definition = this.topology.defsById.get(nodeId);
|
|
355
|
+
if (!definition) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return definition.config.continueWhenEmptyOutput === true;
|
|
359
|
+
}
|
|
360
|
+
|
|
338
361
|
private getPinnedOutputs(currentState: RunCurrentState, nodeId: NodeId): NodeOutputs | undefined {
|
|
339
362
|
return currentState.mutableState?.nodesById?.[nodeId]?.pinnedOutputsByPort;
|
|
340
363
|
}
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
NodeId,
|
|
4
4
|
NodeOutputs,
|
|
5
5
|
ParentExecutionRef,
|
|
6
|
+
PersistedRunSchedulingState,
|
|
6
7
|
PersistedRunState,
|
|
7
8
|
RunId,
|
|
8
9
|
RunSummary,
|
|
@@ -36,6 +37,7 @@ export class InMemoryWorkflowExecutionRepository
|
|
|
36
37
|
runId: args.runId,
|
|
37
38
|
workflowId: args.workflowId,
|
|
38
39
|
startedAt: args.startedAt,
|
|
40
|
+
revision: 0,
|
|
39
41
|
parent: args.parent,
|
|
40
42
|
executionOptions: args.executionOptions,
|
|
41
43
|
control: args.control,
|
|
@@ -55,8 +57,19 @@ export class InMemoryWorkflowExecutionRepository
|
|
|
55
57
|
return this.runs.get(runId);
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
async loadSchedulingState(runId: RunId): Promise<PersistedRunSchedulingState | undefined> {
|
|
61
|
+
const state = this.runs.get(runId);
|
|
62
|
+
if (!state) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
pending: state.pending ? { ...state.pending } : undefined,
|
|
67
|
+
queue: state.queue.map((entry) => ({ ...entry })),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
58
71
|
async save(state: PersistedRunState): Promise<void> {
|
|
59
|
-
this.runs.set(state.runId, state);
|
|
72
|
+
this.runs.set(state.runId, { ...state, revision: (state.revision ?? 0) + 1 });
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
async deleteRun(runId: RunId): Promise<void> {
|
|
@@ -20,6 +20,7 @@ export type StartWorkflowIntent = {
|
|
|
20
20
|
workflow: WorkflowDefinition;
|
|
21
21
|
startAt?: string;
|
|
22
22
|
items: Items;
|
|
23
|
+
synthesizeTriggerItems?: boolean;
|
|
23
24
|
parent?: CurrentStateExecutionRequest["parent"];
|
|
24
25
|
executionOptions?: RunExecutionOptions;
|
|
25
26
|
workflowSnapshot?: CurrentStateExecutionRequest["workflowSnapshot"];
|
|
@@ -34,6 +35,7 @@ export type RerunFromNodeIntent = {
|
|
|
34
35
|
nodeId: NodeId;
|
|
35
36
|
currentState: RunCurrentState;
|
|
36
37
|
items?: Items;
|
|
38
|
+
synthesizeTriggerItems?: boolean;
|
|
37
39
|
parent?: CurrentStateExecutionRequest["parent"];
|
|
38
40
|
executionOptions?: RunExecutionOptions;
|
|
39
41
|
workflowSnapshot?: CurrentStateExecutionRequest["workflowSnapshot"];
|
|
@@ -58,22 +60,16 @@ export class RunIntentService {
|
|
|
58
60
|
) {}
|
|
59
61
|
|
|
60
62
|
async startWorkflow(args: StartWorkflowIntent): Promise<RunResult> {
|
|
63
|
+
const items = await this.resolveStartWorkflowItems(args);
|
|
61
64
|
if (args.startAt && !args.currentState && !args.stopCondition && !args.reset) {
|
|
62
|
-
return await this.engine.runWorkflow(
|
|
63
|
-
args.
|
|
64
|
-
args.
|
|
65
|
-
|
|
66
|
-
args.parent,
|
|
67
|
-
args.executionOptions,
|
|
68
|
-
{
|
|
69
|
-
workflowSnapshot: args.workflowSnapshot,
|
|
70
|
-
mutableState: args.mutableState,
|
|
71
|
-
},
|
|
72
|
-
);
|
|
65
|
+
return await this.engine.runWorkflow(args.workflow, args.startAt, items, args.parent, args.executionOptions, {
|
|
66
|
+
workflowSnapshot: args.workflowSnapshot,
|
|
67
|
+
mutableState: args.mutableState,
|
|
68
|
+
});
|
|
73
69
|
}
|
|
74
70
|
return await this.engine.runWorkflowFromState({
|
|
75
71
|
workflow: args.workflow,
|
|
76
|
-
items
|
|
72
|
+
items,
|
|
77
73
|
parent: args.parent,
|
|
78
74
|
executionOptions: args.executionOptions,
|
|
79
75
|
workflowSnapshot: args.workflowSnapshot,
|
|
@@ -85,8 +81,9 @@ export class RunIntentService {
|
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
async rerunFromNode(args: RerunFromNodeIntent): Promise<RunResult> {
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const items = await this.resolveRerunItems(args);
|
|
85
|
+
if (items) {
|
|
86
|
+
return await this.engine.runWorkflow(args.workflow, args.nodeId, items, args.parent, args.executionOptions, {
|
|
90
87
|
workflowSnapshot: args.workflowSnapshot,
|
|
91
88
|
mutableState: args.mutableState,
|
|
92
89
|
});
|
|
@@ -103,6 +100,63 @@ export class RunIntentService {
|
|
|
103
100
|
});
|
|
104
101
|
}
|
|
105
102
|
|
|
103
|
+
private async resolveStartWorkflowItems(args: StartWorkflowIntent): Promise<Items> {
|
|
104
|
+
if (this.hasNonEmptyItems(args.items)) {
|
|
105
|
+
return args.items;
|
|
106
|
+
}
|
|
107
|
+
const triggerNodeId = this.resolveStartWorkflowTriggerNodeId(args);
|
|
108
|
+
if (!triggerNodeId) {
|
|
109
|
+
return args.items;
|
|
110
|
+
}
|
|
111
|
+
return (await this.engine.createTriggerTestItems({ workflow: args.workflow, nodeId: triggerNodeId })) ?? args.items;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async resolveRerunItems(args: RerunFromNodeIntent): Promise<Items | undefined> {
|
|
115
|
+
if (this.hasNonEmptyItems(args.items)) {
|
|
116
|
+
return args.items;
|
|
117
|
+
}
|
|
118
|
+
const triggerNodeId = this.resolveRerunTriggerNodeId(args);
|
|
119
|
+
if (!triggerNodeId) {
|
|
120
|
+
return args.items;
|
|
121
|
+
}
|
|
122
|
+
return (await this.engine.createTriggerTestItems({ workflow: args.workflow, nodeId: triggerNodeId })) ?? args.items;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private resolveStartWorkflowTriggerNodeId(args: StartWorkflowIntent): NodeId | undefined {
|
|
126
|
+
if (args.stopCondition?.kind === "nodeCompleted" && this.isTriggerNode(args.workflow, args.stopCondition.nodeId)) {
|
|
127
|
+
return args.stopCondition.nodeId;
|
|
128
|
+
}
|
|
129
|
+
if (!args.synthesizeTriggerItems) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
if (args.startAt && this.isTriggerNode(args.workflow, args.startAt)) {
|
|
133
|
+
return args.startAt;
|
|
134
|
+
}
|
|
135
|
+
return this.firstTriggerNodeId(args.workflow);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private resolveRerunTriggerNodeId(args: RerunFromNodeIntent): NodeId | undefined {
|
|
139
|
+
if (this.isTriggerNode(args.workflow, args.nodeId)) {
|
|
140
|
+
return args.nodeId;
|
|
141
|
+
}
|
|
142
|
+
if (!args.synthesizeTriggerItems) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return this.firstTriggerNodeId(args.workflow);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private firstTriggerNodeId(workflow: WorkflowDefinition): NodeId | undefined {
|
|
149
|
+
return workflow.nodes.find((node) => node.kind === "trigger")?.id;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private isTriggerNode(workflow: WorkflowDefinition, nodeId: string): boolean {
|
|
153
|
+
return workflow.nodes.find((node) => node.id === nodeId)?.kind === "trigger";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private hasNonEmptyItems(items: Items | undefined): boolean {
|
|
157
|
+
return (items?.length ?? 0) > 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
106
160
|
resolveWebhookTrigger(args: { endpointPath: string; method: HttpMethod }): WebhookTriggerResolution {
|
|
107
161
|
return this.engine.resolveWebhookTrigger(args);
|
|
108
162
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
NodeActivationContinuation,
|
|
3
|
+
PreparedNodeActivationDispatch,
|
|
3
4
|
NodeActivationReceipt,
|
|
4
5
|
NodeActivationRequest,
|
|
5
6
|
NodeActivationScheduler,
|
|
@@ -26,7 +27,7 @@ export class DefaultDrivingScheduler implements NodeActivationScheduler {
|
|
|
26
27
|
this.inline.setContinuation(continuation);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
async
|
|
30
|
+
async prepareDispatch(request: NodeActivationRequest): Promise<PreparedNodeActivationDispatch> {
|
|
30
31
|
const selection = await this.selectScheduler(request);
|
|
31
32
|
if (selection.mode === "worker") {
|
|
32
33
|
if (request.kind === "multi") {
|
|
@@ -44,15 +45,19 @@ export class DefaultDrivingScheduler implements NodeActivationScheduler {
|
|
|
44
45
|
executionOptions: request.executionOptions,
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
return {
|
|
49
|
+
receipt: {
|
|
50
|
+
receiptId: request.activationId,
|
|
51
|
+
mode: "worker",
|
|
52
|
+
queue: selection.queue,
|
|
53
|
+
},
|
|
54
|
+
dispatch: async () => {
|
|
55
|
+
await this.workerScheduler.enqueue(workerRequest);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
return await this.
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
notifyPendingStatePersisted(runId: string): void {
|
|
55
|
-
this.inline.notifyPendingStatePersisted(runId);
|
|
60
|
+
return await this.prepareInlineDispatch(request);
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
@@ -93,8 +98,13 @@ export class DefaultDrivingScheduler implements NodeActivationScheduler {
|
|
|
93
98
|
return request.ctx.config.execution?.hint !== undefined || request.ctx.config.execution?.queue !== undefined;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
private async
|
|
97
|
-
const
|
|
98
|
-
return {
|
|
101
|
+
private async prepareInlineDispatch(request: NodeActivationRequest): Promise<PreparedNodeActivationDispatch> {
|
|
102
|
+
const prepared = await this.inline.prepareDispatch(request);
|
|
103
|
+
return {
|
|
104
|
+
receipt: { ...prepared.receipt, mode: "local" },
|
|
105
|
+
dispatch: async () => {
|
|
106
|
+
await prepared.dispatch();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
99
109
|
}
|
|
100
110
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
NodeActivationContinuation,
|
|
3
|
+
PreparedNodeActivationDispatch,
|
|
3
4
|
NodeActivationReceipt,
|
|
4
5
|
NodeActivationRequest,
|
|
5
6
|
NodeActivationScheduler,
|
|
6
|
-
RunId,
|
|
7
7
|
} from "../types";
|
|
8
8
|
import { NodeExecutor } from "../execution/NodeExecutor";
|
|
9
9
|
|
|
10
10
|
export class InlineDrivingScheduler implements NodeActivationScheduler {
|
|
11
11
|
private continuation: NodeActivationContinuation | undefined;
|
|
12
|
-
private readonly drainingRuns = new Set<
|
|
12
|
+
private readonly drainingRuns = new Set<string>();
|
|
13
13
|
private readonly queuesByRunId = new Map<
|
|
14
|
-
|
|
14
|
+
string,
|
|
15
15
|
Array<Readonly<{ request: NodeActivationRequest; receipt: NodeActivationReceipt }>>
|
|
16
16
|
>();
|
|
17
|
-
private readonly scheduledRuns = new Set<
|
|
18
|
-
private seq = 0;
|
|
17
|
+
private readonly scheduledRuns = new Set<string>();
|
|
19
18
|
|
|
20
19
|
constructor(private readonly nodeExecutor: NodeExecutor) {}
|
|
21
20
|
|
|
@@ -23,23 +22,20 @@ export class InlineDrivingScheduler implements NodeActivationScheduler {
|
|
|
23
22
|
this.continuation = continuation;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
async
|
|
27
|
-
const receipt: NodeActivationReceipt = { receiptId:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
this.scheduleDrain(runId);
|
|
25
|
+
async prepareDispatch(request: NodeActivationRequest): Promise<PreparedNodeActivationDispatch> {
|
|
26
|
+
const receipt: NodeActivationReceipt = { receiptId: request.activationId, mode: "local" };
|
|
27
|
+
return {
|
|
28
|
+
receipt,
|
|
29
|
+
dispatch: async () => {
|
|
30
|
+
const queue = this.queuesByRunId.get(request.runId) ?? [];
|
|
31
|
+
queue.push({ request, receipt });
|
|
32
|
+
this.queuesByRunId.set(request.runId, queue);
|
|
33
|
+
this.scheduleDrain(request.runId);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
40
36
|
}
|
|
41
37
|
|
|
42
|
-
private async drainRun(runId:
|
|
38
|
+
private async drainRun(runId: string): Promise<void> {
|
|
43
39
|
if (this.drainingRuns.has(runId)) return;
|
|
44
40
|
this.drainingRuns.add(runId);
|
|
45
41
|
this.scheduledRuns.delete(runId);
|
|
@@ -78,7 +74,7 @@ export class InlineDrivingScheduler implements NodeActivationScheduler {
|
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
private scheduleDrain(runId:
|
|
77
|
+
private scheduleDrain(runId: string): void {
|
|
82
78
|
if (this.drainingRuns.has(runId) || this.scheduledRuns.has(runId)) {
|
|
83
79
|
return;
|
|
84
80
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NodeExecutionRequest, NodeExecutionScheduler } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test scheduler that records enqueue requests without executing a real queue.
|
|
5
|
+
*/
|
|
6
|
+
export class CapturingScheduler implements NodeExecutionScheduler {
|
|
7
|
+
lastRequest: NodeExecutionRequest | undefined;
|
|
8
|
+
requests: NodeExecutionRequest[] = [];
|
|
9
|
+
|
|
10
|
+
async enqueue(request: NodeExecutionRequest): Promise<{ receiptId: string }> {
|
|
11
|
+
this.lastRequest = request;
|
|
12
|
+
this.requests.push(request);
|
|
13
|
+
return { receiptId: `receipt_${this.requests.length}` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RunIdFactory } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @internal Test harness id factory shared by registrar kit wiring.
|
|
5
|
+
*/
|
|
6
|
+
export class EngineTestKitRunIdFactory implements RunIdFactory {
|
|
7
|
+
private runCounter = 0;
|
|
8
|
+
private activationCounter = 0;
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly makeRunIdValue: () => string,
|
|
12
|
+
private readonly makeActivationIdValue: () => string,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
makeRunId(): string {
|
|
16
|
+
this.runCounter += 1;
|
|
17
|
+
return this.makeRunIdValue();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
makeActivationId(): string {
|
|
21
|
+
this.activationCounter += 1;
|
|
22
|
+
return this.makeActivationIdValue();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PersistedTriggerSetupState, TriggerSetupStateRepository } from "../types";
|
|
2
|
+
|
|
3
|
+
export class InMemoryTriggerSetupStateRepository implements TriggerSetupStateRepository {
|
|
4
|
+
private readonly statesByKey = new Map<string, PersistedTriggerSetupState>();
|
|
5
|
+
|
|
6
|
+
async load(trigger: { workflowId: string; nodeId: string }): Promise<PersistedTriggerSetupState | undefined> {
|
|
7
|
+
return this.statesByKey.get(this.toKey(trigger));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async save(state: PersistedTriggerSetupState): Promise<void> {
|
|
11
|
+
this.statesByKey.set(this.toKey(state.trigger), state);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async delete(trigger: { workflowId: string; nodeId: string }): Promise<void> {
|
|
15
|
+
this.statesByKey.delete(this.toKey(trigger));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private toKey(trigger: { workflowId: string; nodeId: string }): string {
|
|
19
|
+
return `${trigger.workflowId}:${trigger.nodeId}`;
|
|
20
|
+
}
|
|
21
|
+
}
|