@codemation/core 0.2.0 → 0.2.3
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 +20 -0
- package/dist/{EngineRuntimeRegistration.types-0sgV2XL2.d.ts → EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-Dx7bJsJR.d.cts → EngineWorkflowRunnerService-Dd4yD31l.d.cts} +2 -2
- package/dist/{InMemoryRunDataFactory-qIYQEar7.d.cts → InMemoryRunDataFactory-OUzDmAHt.d.cts} +2 -2
- package/dist/{RunIntentService-BCvGdOSY.d.ts → RunIntentService-BAKikN8h.d.ts} +80 -12
- package/dist/{RunIntentService-CV8izV8t.d.cts → RunIntentService-Bkg4oYrM.d.cts} +74 -5
- package/dist/bootstrap/index.cjs +31 -31
- package/dist/bootstrap/index.d.cts +5 -3
- package/dist/bootstrap/index.d.ts +3 -3
- package/dist/bootstrap/index.js +2 -2
- package/dist/bootstrap-BD6CobHl.js +215 -0
- package/dist/bootstrap-BD6CobHl.js.map +1 -0
- package/dist/bootstrap-DwS5S7s9.cjs +240 -0
- package/dist/bootstrap-DwS5S7s9.cjs.map +1 -0
- package/dist/{index-BHmrZIHp.d.ts → index-BDHCiN22.d.ts} +23 -10
- package/dist/index.cjs +60 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -17
- package/dist/index.d.ts +3 -3
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/{RunIntentService-BFA48UpH.js → runtime-Cy-3FTI_.js} +1224 -94
- package/dist/runtime-Cy-3FTI_.js.map +1 -0
- package/dist/{RunIntentService-DcxXf_AM.cjs → runtime-ZJUpWmPH.cjs} +1251 -132
- package/dist/runtime-ZJUpWmPH.cjs.map +1 -0
- package/dist/testing.cjs +74 -29
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +55 -3
- package/dist/testing.d.ts +55 -3
- package/dist/testing.js +46 -3
- package/dist/testing.js.map +1 -1
- package/dist/workflowActivationPolicy-B8HzTk3o.js.map +1 -1
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +1 -1
- package/package.json +1 -1
- package/src/ai/AgentMessageConfigNormalizerFactory.ts +43 -0
- package/src/ai/AgentToolFactory.ts +2 -2
- package/src/ai/AiHost.ts +10 -9
- package/src/ai/NodeBackedToolConfig.ts +1 -1
- package/src/authoring/defineNode.types.ts +7 -1
- package/src/contracts/runtimeTypes.ts +26 -0
- package/src/contracts/workflowTypes.ts +67 -5
- package/src/execution/ActivationEnqueueService.ts +8 -5
- package/src/execution/NodeActivationRequestInputPreparer.ts +89 -0
- package/src/execution/NodeExecutor.ts +38 -1
- package/src/execution/NodeInputContractError.ts +13 -0
- package/src/execution/index.ts +2 -0
- package/src/orchestration/RunContinuationService.ts +181 -50
- package/src/planning/RunQueuePlanner.ts +12 -1
- package/src/runtime/EngineFactory.ts +3 -0
- package/src/testing/ItemHarnessNode.ts +27 -0
- package/src/testing/ItemHarnessNodeConfig.ts +43 -0
- package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/workflow/dsl/ChainCursorResolver.ts +1 -1
- package/src/workflow/dsl/workflowBuilderTypes.ts +8 -5
- package/dist/RunIntentService-BFA48UpH.js.map +0 -1
- package/dist/RunIntentService-DcxXf_AM.cjs.map +0 -1
- package/dist/bootstrap-D67Sf2BF.js +0 -1136
- package/dist/bootstrap-D67Sf2BF.js.map +0 -1
- package/dist/bootstrap-DoQHAEQJ.cjs +0 -1203
- package/dist/bootstrap-DoQHAEQJ.cjs.map +0 -1
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { RunQueuePlanner } from "../planning/RunQueuePlanner";
|
|
22
22
|
|
|
23
23
|
import { NodeEventPublisher } from "../events/NodeEventPublisher";
|
|
24
|
+
import type { NodeActivationRequestInputPreparer } from "./NodeActivationRequestInputPreparer";
|
|
24
25
|
import { NodeExecutionSnapshotFactory } from "./NodeExecutionSnapshotFactory";
|
|
25
26
|
import { NodeInputsByPortFactory } from "./NodeInputsByPortFactory";
|
|
26
27
|
|
|
@@ -51,6 +52,7 @@ export class ActivationEnqueueService {
|
|
|
51
52
|
private readonly activationScheduler: ActivationSchedulerPort,
|
|
52
53
|
private readonly workflowExecutionRepository: WorkflowExecutionRepository,
|
|
53
54
|
private readonly nodeEventPublisher: NodeEventPublisher,
|
|
55
|
+
private readonly nodeActivationRequestInputPreparer: NodeActivationRequestInputPreparer,
|
|
54
56
|
) {}
|
|
55
57
|
|
|
56
58
|
async enqueueActivation(args: ActivationEnqueueRequest): Promise<RunResult> {
|
|
@@ -62,12 +64,13 @@ export class ActivationEnqueueService {
|
|
|
62
64
|
async enqueueActivationWithSnapshot(
|
|
63
65
|
args: ActivationEnqueueRequest,
|
|
64
66
|
): Promise<{ result: RunResult; queuedSnapshot: NodeExecutionSnapshot }> {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
+
const preparedRequest = await this.nodeActivationRequestInputPreparer.prepare(args.request);
|
|
68
|
+
const preparedDispatch = await this.activationScheduler.prepareDispatch(preparedRequest);
|
|
69
|
+
const inputsByPort = NodeInputsByPortFactory.fromRequest(preparedRequest);
|
|
67
70
|
const itemsIn =
|
|
68
|
-
|
|
69
|
-
? args.planner.sumItemsByPort(
|
|
70
|
-
:
|
|
71
|
+
preparedRequest.kind === "multi"
|
|
72
|
+
? args.planner.sumItemsByPort(preparedRequest.inputsByPort)
|
|
73
|
+
: preparedRequest.input.length;
|
|
71
74
|
const enqueuedAt = new Date().toISOString();
|
|
72
75
|
const pending: PendingNodeExecution = {
|
|
73
76
|
runId: args.runId,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z, ZodError } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { Item, NodeActivationRequest, RunnableNodeConfig, WorkflowNodeInstanceFactory } from "../types";
|
|
4
|
+
|
|
5
|
+
import { NodeInputContractError } from "./NodeInputContractError";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maps and validates per-item inputs for {@link ItemNode} before enqueue persistence.
|
|
9
|
+
*/
|
|
10
|
+
export class NodeActivationRequestInputPreparer {
|
|
11
|
+
constructor(private readonly workflowNodeInstanceFactory: WorkflowNodeInstanceFactory) {}
|
|
12
|
+
|
|
13
|
+
async prepare(request: NodeActivationRequest): Promise<NodeActivationRequest> {
|
|
14
|
+
if (request.kind !== "single") {
|
|
15
|
+
return request;
|
|
16
|
+
}
|
|
17
|
+
const nodeInstance: unknown = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
|
|
18
|
+
if (!this.hasExecuteOne(nodeInstance)) {
|
|
19
|
+
return request;
|
|
20
|
+
}
|
|
21
|
+
const inputSchema = this.resolveInputSchema(nodeInstance, request.ctx.config as RunnableNodeConfig);
|
|
22
|
+
const config = request.ctx.config as RunnableNodeConfig;
|
|
23
|
+
const mappedItems: Item[] = [];
|
|
24
|
+
for (let i = 0; i < request.input.length; i++) {
|
|
25
|
+
const item = request.input[i] as Item;
|
|
26
|
+
try {
|
|
27
|
+
const mappedRaw = config.mapInput
|
|
28
|
+
? await Promise.resolve(
|
|
29
|
+
config.mapInput({
|
|
30
|
+
item,
|
|
31
|
+
itemIndex: i,
|
|
32
|
+
items: request.input,
|
|
33
|
+
ctx: request.ctx,
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
: item.json;
|
|
37
|
+
const parsed = inputSchema.parse(mappedRaw);
|
|
38
|
+
mappedItems.push({ ...item, json: parsed });
|
|
39
|
+
} catch (cause) {
|
|
40
|
+
const message = this.formatContractFailure(cause);
|
|
41
|
+
throw new NodeInputContractError(
|
|
42
|
+
`Node ${request.nodeId} activation ${request.activationId}: input contract failed: ${message}`,
|
|
43
|
+
request.nodeId,
|
|
44
|
+
request.activationId,
|
|
45
|
+
cause,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...request,
|
|
51
|
+
input: mappedItems,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private hasExecuteOne(nodeInstance: unknown): boolean {
|
|
56
|
+
return (
|
|
57
|
+
typeof nodeInstance === "object" &&
|
|
58
|
+
nodeInstance !== null &&
|
|
59
|
+
typeof (nodeInstance as { executeOne?: unknown }).executeOne === "function"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private resolveInputSchema(
|
|
64
|
+
nodeInstance: unknown,
|
|
65
|
+
config: RunnableNodeConfig,
|
|
66
|
+
): {
|
|
67
|
+
parse: (data: unknown) => unknown;
|
|
68
|
+
} {
|
|
69
|
+
const fromInstance = (nodeInstance as { inputSchema?: unknown }).inputSchema;
|
|
70
|
+
if (fromInstance && typeof (fromInstance as { parse?: unknown }).parse === "function") {
|
|
71
|
+
return fromInstance as { parse: (data: unknown) => unknown };
|
|
72
|
+
}
|
|
73
|
+
const fromConfig = config.inputSchema;
|
|
74
|
+
if (fromConfig && typeof fromConfig.parse === "function") {
|
|
75
|
+
return fromConfig as { parse: (data: unknown) => unknown };
|
|
76
|
+
}
|
|
77
|
+
return z.unknown();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private formatContractFailure(cause: unknown): string {
|
|
81
|
+
if (cause instanceof ZodError) {
|
|
82
|
+
return cause.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
|
|
83
|
+
}
|
|
84
|
+
if (cause instanceof Error) {
|
|
85
|
+
return cause.message;
|
|
86
|
+
}
|
|
87
|
+
return String(cause);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Item,
|
|
3
|
+
MultiInputNode,
|
|
4
|
+
Node,
|
|
5
|
+
NodeActivationRequest,
|
|
6
|
+
NodeOutputs,
|
|
7
|
+
WorkflowNodeInstanceFactory,
|
|
8
|
+
} from "../types";
|
|
2
9
|
|
|
3
10
|
import { InProcessRetryRunner } from "./InProcessRetryRunner";
|
|
4
11
|
|
|
@@ -34,10 +41,40 @@ export class NodeExecutor {
|
|
|
34
41
|
request: Extract<NodeActivationRequest, { kind: "single" }>,
|
|
35
42
|
node: unknown,
|
|
36
43
|
): Promise<NodeOutputs> {
|
|
44
|
+
if (this.hasExecuteOne(node)) {
|
|
45
|
+
return await this.executeItemNode(request, node);
|
|
46
|
+
}
|
|
37
47
|
const singleInputNode = node as Node;
|
|
38
48
|
if (typeof (singleInputNode as { execute?: unknown }).execute !== "function") {
|
|
39
49
|
throw new Error(`Node ${request.nodeId} does not support execute but received single-input activation`);
|
|
40
50
|
}
|
|
41
51
|
return await singleInputNode.execute(request.input, request.ctx as any);
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
private hasExecuteOne(node: unknown): node is { executeOne: (args: unknown) => unknown | Promise<unknown> } {
|
|
55
|
+
return (
|
|
56
|
+
typeof node === "object" && node !== null && typeof (node as { executeOne?: unknown }).executeOne === "function"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async executeItemNode(
|
|
61
|
+
request: Extract<NodeActivationRequest, { kind: "single" }>,
|
|
62
|
+
node: { executeOne: (args: unknown) => unknown | Promise<unknown> },
|
|
63
|
+
): Promise<NodeOutputs> {
|
|
64
|
+
const out: Item[] = [];
|
|
65
|
+
for (let i = 0; i < request.input.length; i++) {
|
|
66
|
+
const item = request.input[i] as Item;
|
|
67
|
+
const outputJson = await Promise.resolve(
|
|
68
|
+
node.executeOne({
|
|
69
|
+
input: item.json,
|
|
70
|
+
item,
|
|
71
|
+
itemIndex: i,
|
|
72
|
+
items: request.input,
|
|
73
|
+
ctx: request.ctx,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
out.push({ ...item, json: outputJson });
|
|
77
|
+
}
|
|
78
|
+
return { main: out };
|
|
79
|
+
}
|
|
43
80
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NodeActivationId, NodeId } from "../types";
|
|
2
|
+
|
|
3
|
+
export class NodeInputContractError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
message: string,
|
|
6
|
+
public readonly nodeId: NodeId,
|
|
7
|
+
public readonly activationId: NodeActivationId,
|
|
8
|
+
public readonly cause?: unknown,
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "NodeInputContractError";
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/execution/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ActivationEnqueueService } from "./ActivationEnqueueService";
|
|
2
|
+
export { NodeActivationRequestInputPreparer } from "./NodeActivationRequestInputPreparer";
|
|
3
|
+
export { NodeInputContractError } from "./NodeInputContractError";
|
|
2
4
|
export { CredentialResolverFactory } from "./CredentialResolverFactory";
|
|
3
5
|
export { DefaultAsyncSleeper } from "./DefaultAsyncSleeper";
|
|
4
6
|
export { DefaultExecutionContextFactory } from "./DefaultExecutionContextFactory";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ActivationIdFactory,
|
|
3
|
+
EngineRunCounters,
|
|
3
4
|
NodeActivationId,
|
|
5
|
+
NodeActivationRequest,
|
|
4
6
|
NodeExecutionContext,
|
|
7
|
+
NodeExecutionSnapshot,
|
|
5
8
|
NodeId,
|
|
6
9
|
NodeInputsByPort,
|
|
7
10
|
NodeOutputs,
|
|
@@ -32,6 +35,7 @@ import type { EngineWaiters } from "../orchestration/EngineWaiters";
|
|
|
32
35
|
import { NodeEventPublisher } from "../events/NodeEventPublisher";
|
|
33
36
|
|
|
34
37
|
import { ActivationEnqueueService } from "../execution/ActivationEnqueueService";
|
|
38
|
+
import { NodeInputsByPortFactory } from "../execution/NodeInputsByPortFactory";
|
|
35
39
|
import { NodeExecutionSnapshotFactory } from "../execution/NodeExecutionSnapshotFactory";
|
|
36
40
|
import { NodeRunStateWriterFactory } from "../execution/NodeRunStateWriterFactory";
|
|
37
41
|
import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
|
|
@@ -301,26 +305,43 @@ export class RunContinuationService {
|
|
|
301
305
|
nodeDefinition: def,
|
|
302
306
|
});
|
|
303
307
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
308
|
+
try {
|
|
309
|
+
const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
310
|
+
runId: state.runId,
|
|
311
|
+
workflowId: state.workflowId,
|
|
312
|
+
startedAt: state.startedAt,
|
|
313
|
+
parent: state.parent,
|
|
314
|
+
executionOptions: state.executionOptions,
|
|
315
|
+
control: state.control,
|
|
316
|
+
workflowSnapshot: state.workflowSnapshot,
|
|
317
|
+
mutableState: state.mutableState,
|
|
318
|
+
policySnapshot: state.policySnapshot,
|
|
319
|
+
pendingQueue: queue,
|
|
320
|
+
request,
|
|
321
|
+
previousNodeSnapshotsByNodeId: nextNodeSnapshotsByNodeId,
|
|
322
|
+
planner,
|
|
323
|
+
engineCounters,
|
|
324
|
+
connectionInvocations: state.connectionInvocations ?? [],
|
|
325
|
+
});
|
|
326
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
327
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
328
|
+
return result;
|
|
329
|
+
} catch (cause) {
|
|
330
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
331
|
+
return await this.terminateRunAfterActivationEnqueueRejected({
|
|
332
|
+
wf,
|
|
333
|
+
state,
|
|
334
|
+
queue,
|
|
335
|
+
nextNodeId: next.nodeId,
|
|
336
|
+
request,
|
|
337
|
+
completedSnapshot,
|
|
338
|
+
nextNodeSnapshotsByNodeId,
|
|
339
|
+
outputsByNode: data.dump(),
|
|
340
|
+
engineCounters,
|
|
341
|
+
error,
|
|
342
|
+
completedAt,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
324
345
|
}
|
|
325
346
|
|
|
326
347
|
async resumeFromNodeError(args: {
|
|
@@ -744,36 +765,64 @@ export class RunContinuationService {
|
|
|
744
765
|
nodeDefinition: nextDefinition,
|
|
745
766
|
});
|
|
746
767
|
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
768
|
+
const mergedSnapshots = {
|
|
769
|
+
...(args.state.nodeSnapshotsByNodeId ?? {}),
|
|
770
|
+
[args.args.nodeId]: completedSnapshot,
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
try {
|
|
774
|
+
const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
775
|
+
runId: args.state.runId,
|
|
776
|
+
workflowId: args.state.workflowId,
|
|
777
|
+
startedAt: args.state.startedAt,
|
|
778
|
+
parent: args.state.parent,
|
|
779
|
+
executionOptions: args.state.executionOptions,
|
|
780
|
+
control: args.state.control,
|
|
781
|
+
workflowSnapshot: args.state.workflowSnapshot,
|
|
782
|
+
mutableState: args.state.mutableState,
|
|
783
|
+
policySnapshot: args.state.policySnapshot,
|
|
784
|
+
pendingQueue: queue,
|
|
785
|
+
request,
|
|
786
|
+
previousNodeSnapshotsByNodeId: mergedSnapshots,
|
|
787
|
+
planner,
|
|
788
|
+
engineCounters,
|
|
789
|
+
connectionInvocations: args.state.connectionInvocations ?? [],
|
|
790
|
+
});
|
|
791
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
792
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
793
|
+
this.waiters.resolveWebhookResponse({
|
|
794
|
+
runId: args.state.runId,
|
|
795
|
+
workflowId: args.state.workflowId,
|
|
796
|
+
startedAt: args.state.startedAt,
|
|
797
|
+
runStatus: "pending",
|
|
798
|
+
response: args.signal.responseItems,
|
|
799
|
+
});
|
|
800
|
+
return result;
|
|
801
|
+
} catch (cause) {
|
|
802
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
803
|
+
const finishedAt = completedSnapshot.finishedAt ?? completedSnapshot.updatedAt;
|
|
804
|
+
const result = await this.terminateRunAfterActivationEnqueueRejected({
|
|
805
|
+
wf: args.workflow,
|
|
806
|
+
state: args.state,
|
|
807
|
+
queue,
|
|
808
|
+
nextNodeId: next.nodeId,
|
|
809
|
+
request,
|
|
810
|
+
completedSnapshot,
|
|
811
|
+
nextNodeSnapshotsByNodeId: mergedSnapshots,
|
|
812
|
+
outputsByNode: data.dump(),
|
|
813
|
+
engineCounters,
|
|
814
|
+
error,
|
|
815
|
+
completedAt: finishedAt,
|
|
816
|
+
});
|
|
817
|
+
this.waiters.resolveWebhookResponse({
|
|
818
|
+
runId: args.state.runId,
|
|
819
|
+
workflowId: args.state.workflowId,
|
|
820
|
+
startedAt: args.state.startedAt,
|
|
821
|
+
runStatus: "pending",
|
|
822
|
+
response: args.signal.responseItems,
|
|
823
|
+
});
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
777
826
|
}
|
|
778
827
|
|
|
779
828
|
private asWebhookControlSignal(error: Error): WebhookControlSignal | undefined {
|
|
@@ -860,6 +909,88 @@ export class RunContinuationService {
|
|
|
860
909
|
};
|
|
861
910
|
}
|
|
862
911
|
|
|
912
|
+
/**
|
|
913
|
+
* Next activation could not be enqueued (e.g. input contract / mapping failed in the preparer).
|
|
914
|
+
* Marks the target node failed and terminates the run.
|
|
915
|
+
*/
|
|
916
|
+
private async terminateRunAfterActivationEnqueueRejected(
|
|
917
|
+
args: Readonly<{
|
|
918
|
+
wf: WorkflowDefinition;
|
|
919
|
+
state: PersistedRunState;
|
|
920
|
+
queue: RunQueueEntry[];
|
|
921
|
+
nextNodeId: NodeId;
|
|
922
|
+
request: NodeActivationRequest;
|
|
923
|
+
completedSnapshot: NodeExecutionSnapshot;
|
|
924
|
+
nextNodeSnapshotsByNodeId: NonNullable<PersistedRunState["nodeSnapshotsByNodeId"]>;
|
|
925
|
+
outputsByNode: PersistedRunState["outputsByNode"];
|
|
926
|
+
engineCounters: EngineRunCounters;
|
|
927
|
+
error: Error;
|
|
928
|
+
completedAt: string;
|
|
929
|
+
}>,
|
|
930
|
+
): Promise<RunResult> {
|
|
931
|
+
const finishedAt = args.completedAt;
|
|
932
|
+
const inputsByPort = NodeInputsByPortFactory.fromRequest(args.request);
|
|
933
|
+
const failedSnapshot = NodeExecutionSnapshotFactory.failed({
|
|
934
|
+
previous: undefined,
|
|
935
|
+
runId: args.state.runId,
|
|
936
|
+
workflowId: args.state.workflowId,
|
|
937
|
+
nodeId: args.nextNodeId,
|
|
938
|
+
activationId: args.request.activationId,
|
|
939
|
+
parent: args.state.parent,
|
|
940
|
+
finishedAt,
|
|
941
|
+
inputsByPort,
|
|
942
|
+
error: args.error,
|
|
943
|
+
});
|
|
944
|
+
const failedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
945
|
+
state: args.state,
|
|
946
|
+
engineCounters: args.engineCounters,
|
|
947
|
+
status: "failed",
|
|
948
|
+
queue: args.queue.map((q) => ({ ...q })),
|
|
949
|
+
outputsByNode: args.outputsByNode,
|
|
950
|
+
nodeSnapshotsByNodeId: {
|
|
951
|
+
...args.nextNodeSnapshotsByNodeId,
|
|
952
|
+
[args.nextNodeId]: failedSnapshot,
|
|
953
|
+
},
|
|
954
|
+
finishedAtIso: finishedAt,
|
|
955
|
+
});
|
|
956
|
+
await this.workflowExecutionRepository.save(failedState);
|
|
957
|
+
await this.nodeEventPublisher.publish("nodeCompleted", args.completedSnapshot);
|
|
958
|
+
await this.nodeEventPublisher.publish("nodeFailed", failedSnapshot);
|
|
959
|
+
|
|
960
|
+
const wfErr = this.policyErrorServices.resolveWorkflowErrorHandler(args.wf.workflowErrorHandler);
|
|
961
|
+
if (wfErr) {
|
|
962
|
+
await Promise.resolve(
|
|
963
|
+
wfErr.onError({
|
|
964
|
+
runId: args.state.runId,
|
|
965
|
+
workflowId: args.state.workflowId,
|
|
966
|
+
workflow: args.wf,
|
|
967
|
+
failedNodeId: args.nextNodeId,
|
|
968
|
+
error: args.error,
|
|
969
|
+
startedAt: args.state.startedAt,
|
|
970
|
+
finishedAt,
|
|
971
|
+
}),
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
await this.terminalPersistence.maybeDeleteAfterTerminalState({
|
|
976
|
+
workflow: args.wf,
|
|
977
|
+
state: failedState,
|
|
978
|
+
finalStatus: "failed",
|
|
979
|
+
finishedAt,
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const message = args.error.message ?? String(args.error);
|
|
983
|
+
const result: RunResult = {
|
|
984
|
+
runId: args.state.runId,
|
|
985
|
+
workflowId: args.state.workflowId,
|
|
986
|
+
startedAt: args.state.startedAt,
|
|
987
|
+
status: "failed",
|
|
988
|
+
error: { message },
|
|
989
|
+
};
|
|
990
|
+
this.waiters.resolveRunCompletion(result);
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
|
|
863
994
|
private formatNodeLabel(args: {
|
|
864
995
|
definition?: Readonly<{ id: NodeId; name?: string; type: unknown }>;
|
|
865
996
|
nodeId: NodeId;
|
|
@@ -149,6 +149,17 @@ export class RunQueuePlanner {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Matches `CurrentStateFrontierPlanner.buildFrontierQueue`: anything that is not exactly one input
|
|
154
|
+
* port named `in` participates in multi-port collect (Merge after `If` branches, etc.). Routing must
|
|
155
|
+
* not depend solely on `nodeInstances.get(toNodeId)?.executeMulti`, or a Merge can be enqueued as a
|
|
156
|
+
* single-input job and `NodeExecutor` will call `execute` on a multi-input-only implementation.
|
|
157
|
+
*/
|
|
158
|
+
private usesTopologyCollectMerge(toNodeId: NodeId): boolean {
|
|
159
|
+
const expectedInputs = this.topology.expectedInputsByNode.get(toNodeId) ?? [];
|
|
160
|
+
return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
private enqueueEdge(
|
|
153
164
|
queue: RunQueueEntry[],
|
|
154
165
|
args: Readonly<{
|
|
@@ -159,7 +170,7 @@ export class RunQueuePlanner {
|
|
|
159
170
|
}>,
|
|
160
171
|
): void {
|
|
161
172
|
const target = this.nodeInstances.get(args.to.nodeId);
|
|
162
|
-
const isMulti = this.isMultiInputNode(target);
|
|
173
|
+
const isMulti = this.usesTopologyCollectMerge(args.to.nodeId) || this.isMultiInputNode(target);
|
|
163
174
|
|
|
164
175
|
if (!isMulti) {
|
|
165
176
|
if (args.items.length === 0) {
|
|
@@ -5,6 +5,7 @@ import { MissingRuntimeExecutionMarker } from "../workflowSnapshots/MissingRunti
|
|
|
5
5
|
import { WorkflowSnapshotCodec } from "../workflowSnapshots/WorkflowSnapshotCodec";
|
|
6
6
|
import { WorkflowSnapshotResolver } from "../workflowSnapshots/WorkflowSnapshotResolver";
|
|
7
7
|
import { ActivationEnqueueService } from "../execution/ActivationEnqueueService";
|
|
8
|
+
import { NodeActivationRequestInputPreparer } from "../execution/NodeActivationRequestInputPreparer";
|
|
8
9
|
import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
|
|
9
10
|
import { PersistedRunStateTerminalBuilder } from "../execution/PersistedRunStateTerminalBuilder";
|
|
10
11
|
import { NodeExecutionRequestHandlerService } from "../orchestration/NodeExecutionRequestHandlerService";
|
|
@@ -61,10 +62,12 @@ export class EngineFactory {
|
|
|
61
62
|
);
|
|
62
63
|
|
|
63
64
|
const semantics = new RunStateSemantics(new MissingRuntimeExecutionMarker());
|
|
65
|
+
const nodeActivationRequestInputPreparer = new NodeActivationRequestInputPreparer(deps.workflowNodeInstanceFactory);
|
|
64
66
|
const activationEnqueueService = new ActivationEnqueueService(
|
|
65
67
|
deps.activationScheduler,
|
|
66
68
|
deps.workflowExecutionRepository,
|
|
67
69
|
nodeEventPublisher,
|
|
70
|
+
nodeActivationRequestInputPreparer,
|
|
68
71
|
);
|
|
69
72
|
const runExecutionContextFactory = new WorkflowRunExecutionContextFactory(
|
|
70
73
|
deps.executionContextFactory,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Item, ItemNode, Items, NodeExecutionContext } from "../types";
|
|
2
|
+
|
|
3
|
+
import type { ItemHarnessNodeConfig } from "./ItemHarnessNodeConfig";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Item-mode harness node for engine tests (see {@link ItemHarnessNodeConfig}).
|
|
7
|
+
*/
|
|
8
|
+
export class ItemHarnessNode implements ItemNode<ItemHarnessNodeConfig<any, any, any>, unknown, unknown> {
|
|
9
|
+
readonly kind = "node" as const;
|
|
10
|
+
readonly outputPorts = ["main"] as const;
|
|
11
|
+
|
|
12
|
+
async executeOne(args: {
|
|
13
|
+
input: unknown;
|
|
14
|
+
item: Item;
|
|
15
|
+
itemIndex: number;
|
|
16
|
+
items: Items;
|
|
17
|
+
ctx: NodeExecutionContext<ItemHarnessNodeConfig<any, any, any>>;
|
|
18
|
+
}): Promise<unknown> {
|
|
19
|
+
return await args.ctx.config.runOne({
|
|
20
|
+
input: args.input as never,
|
|
21
|
+
item: args.item,
|
|
22
|
+
itemIndex: args.itemIndex,
|
|
23
|
+
items: args.items,
|
|
24
|
+
ctx: args.ctx,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ZodType } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { TypeToken } from "../di";
|
|
4
|
+
import type { Item, ItemInputMapper, Items, NodeExecutionContext, RunnableNodeConfig } from "../types";
|
|
5
|
+
|
|
6
|
+
import { ItemHarnessNode } from "./ItemHarnessNode";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Item-mode harness node config for engine tests: engine applies {@link RunnableNodeConfig.inputSchema} +
|
|
10
|
+
* optional {@link RunnableNodeConfig.mapInput}, then {@link ItemHarnessNode.executeOne} per item.
|
|
11
|
+
*/
|
|
12
|
+
export class ItemHarnessNodeConfig<TIn = unknown, TOut = unknown, TWire = TIn> implements RunnableNodeConfig<
|
|
13
|
+
TIn,
|
|
14
|
+
TOut,
|
|
15
|
+
TWire
|
|
16
|
+
> {
|
|
17
|
+
readonly kind = "node" as const;
|
|
18
|
+
readonly type: TypeToken<unknown> = ItemHarnessNode;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
public readonly name: string,
|
|
22
|
+
public readonly inputSchema: ZodType<TIn>,
|
|
23
|
+
public readonly runOne: (args: {
|
|
24
|
+
input: TIn;
|
|
25
|
+
item: Item<TWire>;
|
|
26
|
+
itemIndex: number;
|
|
27
|
+
items: Items<TWire>;
|
|
28
|
+
ctx: NodeExecutionContext<ItemHarnessNodeConfig<TIn, TOut, TWire>>;
|
|
29
|
+
}) => TOut | Promise<TOut>,
|
|
30
|
+
public readonly opts: Readonly<{
|
|
31
|
+
id?: string;
|
|
32
|
+
mapInput?: ItemInputMapper<TWire, TIn>;
|
|
33
|
+
}> = {},
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
get mapInput(): ItemInputMapper<TWire, TIn> | undefined {
|
|
37
|
+
return this.opts.mapInput;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get id(): string | undefined {
|
|
41
|
+
return this.opts.id;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -29,6 +29,7 @@ import { RejectingCredentialSessionService } from "./RejectingCredentialSessionS
|
|
|
29
29
|
import { CapturingScheduler } from "./CapturingScheduler";
|
|
30
30
|
import { PrefixedSequentialIdGenerator } from "./PrefixedSequentialIdGenerator";
|
|
31
31
|
import type { RegistrarEngineTestKitHandle, RegistrarEngineTestKitOptions } from "./RegistrarEngineTestKit.types";
|
|
32
|
+
import { ItemHarnessNode } from "./ItemHarnessNode";
|
|
32
33
|
import { SubWorkflowRunnerNode } from "./SubWorkflowRunnerTestNode";
|
|
33
34
|
import { WorkflowTestHarnessManualTriggerNode } from "./WorkflowTestHarnessManualTrigger";
|
|
34
35
|
|
|
@@ -108,6 +109,7 @@ export class RegistrarEngineTestKitFactory {
|
|
|
108
109
|
SubWorkflowRunnerNode,
|
|
109
110
|
new SubWorkflowRunnerNode(workflowRunner as WorkflowRunnerService),
|
|
110
111
|
);
|
|
112
|
+
dependencyContainer.registerInstance(ItemHarnessNode, new ItemHarnessNode());
|
|
111
113
|
dependencyContainer.registerInstance(
|
|
112
114
|
WorkflowTestHarnessManualTriggerNode,
|
|
113
115
|
new WorkflowTestHarnessManualTriggerNode(),
|
package/src/testing.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
export { InMemoryLiveWorkflowRepository } from "./runtime/InMemoryLiveWorkflowRepository";
|
|
5
5
|
export { WorkflowSnapshotCodec as PersistedWorkflowSnapshotFactory } from "./workflowSnapshots/WorkflowSnapshotCodec";
|
|
6
6
|
export { RejectingCredentialSessionService } from "./testing/RejectingCredentialSessionService";
|
|
7
|
+
export { ItemHarnessNode } from "./testing/ItemHarnessNode";
|
|
8
|
+
export { ItemHarnessNodeConfig } from "./testing/ItemHarnessNodeConfig";
|
|
7
9
|
export { CapturingScheduler } from "./testing/CapturingScheduler";
|
|
8
10
|
export { PrefixedSequentialIdGenerator } from "./testing/PrefixedSequentialIdGenerator";
|
|
9
11
|
export {
|
|
@@ -38,7 +38,7 @@ export class ChainCursor<TCurrentJson> {
|
|
|
38
38
|
private readonly cursorOutput: OutputPortKey,
|
|
39
39
|
) {}
|
|
40
40
|
|
|
41
|
-
then<TConfig extends RunnableNodeConfig<
|
|
41
|
+
then<TInputJson, TOutputJson, TConfig extends RunnableNodeConfig<TInputJson, TOutputJson, TCurrentJson>>(
|
|
42
42
|
config: TConfig,
|
|
43
43
|
): ChainCursor<RunnableNodeOutputJson<TConfig>> {
|
|
44
44
|
const next = (this.wf as any).add(config) as NodeRef;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RunnableNodeConfig, RunnableNodeOutputJson, TriggerNodeConfig } from "../../types";
|
|
2
2
|
|
|
3
|
-
export type AnyRunnableNodeConfig = RunnableNodeConfig<any, any>;
|
|
3
|
+
export type AnyRunnableNodeConfig = RunnableNodeConfig<any, any, any>;
|
|
4
4
|
|
|
5
5
|
export type AnyTriggerNodeConfig = TriggerNodeConfig<any>;
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ export type ValidStepSequence<
|
|
|
10
10
|
> = TSteps extends readonly []
|
|
11
11
|
? readonly []
|
|
12
12
|
: TSteps extends readonly [infer TFirst, ...infer TRest]
|
|
13
|
-
? TFirst extends RunnableNodeConfig<
|
|
13
|
+
? TFirst extends RunnableNodeConfig<infer _TIn, infer TNextJson, TCurrentJson>
|
|
14
14
|
? TRest extends ReadonlyArray<AnyRunnableNodeConfig>
|
|
15
15
|
? readonly [TFirst, ...ValidStepSequence<TNextJson, TRest>]
|
|
16
16
|
: never
|
|
@@ -22,7 +22,7 @@ export type StepSequenceOutput<TCurrentJson, TSteps extends ReadonlyArray<AnyRun
|
|
|
22
22
|
? TSteps extends readonly []
|
|
23
23
|
? TCurrentJson
|
|
24
24
|
: TSteps extends readonly [infer TFirst, ...infer TRest]
|
|
25
|
-
? TFirst extends RunnableNodeConfig<
|
|
25
|
+
? TFirst extends RunnableNodeConfig<infer _TIn, infer TNextJson, TCurrentJson>
|
|
26
26
|
? TRest extends ReadonlyArray<AnyRunnableNodeConfig>
|
|
27
27
|
? StepSequenceOutput<TNextJson, TRest>
|
|
28
28
|
: never
|
|
@@ -46,7 +46,7 @@ export type BranchStepsArg<TCurrentJson, TSteps extends ReadonlyArray<AnyRunnabl
|
|
|
46
46
|
|
|
47
47
|
export type BranchMoreArgs<
|
|
48
48
|
TCurrentJson,
|
|
49
|
-
TFirstStep extends RunnableNodeConfig<
|
|
49
|
+
TFirstStep extends RunnableNodeConfig<any, any, TCurrentJson>,
|
|
50
50
|
TRestSteps extends ReadonlyArray<AnyRunnableNodeConfig>,
|
|
51
51
|
> = TRestSteps & ValidStepSequence<RunnableNodeOutputJson<TFirstStep>, TRestSteps>;
|
|
52
52
|
|
|
@@ -55,7 +55,10 @@ export type BooleanWhenOverloads<TCurrentJson, TReturn> = {
|
|
|
55
55
|
branch: boolean,
|
|
56
56
|
steps: BranchStepsArg<TCurrentJson, TSteps>,
|
|
57
57
|
): TReturn;
|
|
58
|
-
<
|
|
58
|
+
<
|
|
59
|
+
TFirstStep extends RunnableNodeConfig<any, any, TCurrentJson>,
|
|
60
|
+
TRestSteps extends ReadonlyArray<AnyRunnableNodeConfig>,
|
|
61
|
+
>(
|
|
59
62
|
branch: boolean,
|
|
60
63
|
step: TFirstStep,
|
|
61
64
|
...more: BranchMoreArgs<TCurrentJson, TFirstStep, TRestSteps>
|