@codemation/core 0.8.1 → 0.10.1
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 +386 -0
- package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
- package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
- package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
- package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
- package/dist/bootstrap/index.cjs +5 -2
- package/dist/bootstrap/index.d.cts +212 -135
- package/dist/bootstrap/index.d.ts +4 -4
- package/dist/bootstrap/index.js +3 -3
- package/dist/{bootstrap-Bx1u4cbS.cjs → bootstrap-Cm5ruQxx.cjs} +253 -2
- package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
- package/dist/{bootstrap-BoknFKnw.js → bootstrap-D3r505ko.js} +236 -3
- package/dist/bootstrap-D3r505ko.js.map +1 -0
- package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
- package/dist/index.cjs +323 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +544 -91
- package/dist/index.d.ts +3 -3
- package/dist/index.js +299 -166
- package/dist/index.js.map +1 -1
- package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
- package/dist/runtime-BGNbRnqs.js.map +1 -0
- package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
- package/dist/runtime-DKXJwTNv.cjs.map +1 -0
- package/dist/testing.cjs +4 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/package.json +7 -2
- package/src/ai/AiHost.ts +42 -14
- package/src/authoring/DefinedCollectionRegistry.ts +17 -0
- package/src/authoring/defineCollection.types.ts +181 -0
- package/src/authoring/definePollingTrigger.types.ts +396 -0
- package/src/authoring/definePollingTriggerInternals.ts +74 -0
- package/src/authoring/index.ts +19 -0
- package/src/bootstrap/index.ts +9 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
- package/src/browser.ts +1 -0
- package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
- package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
- package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
- package/src/contracts/assertionTypes.ts +63 -0
- package/src/contracts/baseTypes.ts +12 -0
- package/src/contracts/collectionTypes.ts +44 -0
- package/src/contracts/credentialTypes.ts +23 -1
- package/src/contracts/executionPersistenceContracts.ts +30 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/runTypes.ts +37 -1
- package/src/contracts/runtimeTypes.ts +42 -0
- package/src/contracts/telemetryTypes.ts +8 -0
- package/src/contracts/testTriggerTypes.ts +66 -0
- package/src/contracts/workflowTypes.ts +36 -7
- package/src/contracts.ts +59 -0
- package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
- package/src/events/index.ts +1 -0
- package/src/events/runEvents.ts +74 -0
- package/src/execution/ChildExecutionScopeFactory.ts +55 -0
- package/src/execution/DefaultExecutionContextFactory.ts +6 -0
- package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
- package/src/execution/NodeExecutor.ts +10 -2
- package/src/execution/NodeInstanceFactory.ts +13 -1
- package/src/execution/NodeInstantiationError.ts +16 -0
- package/src/execution/NodeRunStateWriter.ts +7 -0
- package/src/execution/NodeRunStateWriterFactory.ts +7 -0
- package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
- package/src/execution/index.ts +2 -0
- package/src/index.ts +8 -0
- package/src/orchestration/AbortControllerFactory.ts +9 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
- package/src/orchestration/RunContinuationService.ts +3 -0
- package/src/orchestration/RunStartService.ts +122 -3
- package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
- package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
- package/src/orchestration/TriggerRuntimeService.ts +34 -7
- package/src/orchestration/index.ts +9 -0
- package/src/runtime/EngineFactory.ts +12 -0
- package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
- package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
- package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
- package/src/triggers/polling/index.ts +5 -0
- package/src/types/index.ts +12 -9
- package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
- package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
- package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
- package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
- package/src/workflow/index.ts +3 -0
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
- package/dist/bootstrap-BoknFKnw.js.map +0 -1
- package/dist/bootstrap-Bx1u4cbS.cjs.map +0 -1
- package/dist/runtime-DUW6tIJ1.js.map +0 -1
- package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
package/src/events/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { ConnectionInvocationEventPublisher } from "./ConnectionInvocationEventPublisher";
|
|
1
2
|
export { NodeEventPublisher } from "./NodeEventPublisher";
|
|
2
3
|
export { InMemoryRunEventBus } from "./InMemoryRunEventBusRegistry";
|
|
3
4
|
export { EventPublishingWorkflowExecutionRepository } from "./EventPublishingWorkflowExecutionRepository";
|
package/src/events/runEvents.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
import type { TestSuiteRunId } from "../contracts/testTriggerTypes";
|
|
2
|
+
import type { ConnectionInvocationRecord } from "../contracts/runTypes";
|
|
1
3
|
import type { NodeExecutionSnapshot, ParentExecutionRef, PersistedRunState, RunId, WorkflowId } from "../types";
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Outcome of a single test case (one workflow run dispatched by the test-suite orchestrator).
|
|
7
|
+
* - `running`: workflow still in flight
|
|
8
|
+
* - `succeeded`: workflow completed AND all assertions passed (or no assertions)
|
|
9
|
+
* - `failed`: workflow failed OR (workflow completed but ≥1 assertion failed)
|
|
10
|
+
* - `errored` / `cancelled`: workflow itself errored or was cancelled
|
|
11
|
+
*/
|
|
12
|
+
export type TestCaseRunStatus = "running" | "succeeded" | "failed" | "errored" | "cancelled";
|
|
13
|
+
/** Aggregate outcome of a TestSuiteRun. */
|
|
14
|
+
export type TestSuiteRunStatus = "succeeded" | "failed" | "partial" | "errored" | "cancelled";
|
|
15
|
+
|
|
3
16
|
export type RunEvent =
|
|
4
17
|
| Readonly<{ kind: "runCreated"; runId: RunId; workflowId: WorkflowId; parent?: ParentExecutionRef; at: string }>
|
|
5
18
|
| Readonly<{
|
|
@@ -41,6 +54,67 @@ export type RunEvent =
|
|
|
41
54
|
parent?: ParentExecutionRef;
|
|
42
55
|
at: string;
|
|
43
56
|
snapshot: NodeExecutionSnapshot;
|
|
57
|
+
}>
|
|
58
|
+
| Readonly<{
|
|
59
|
+
kind: "connectionInvocationStarted";
|
|
60
|
+
runId: RunId;
|
|
61
|
+
workflowId: WorkflowId;
|
|
62
|
+
parent?: ParentExecutionRef;
|
|
63
|
+
at: string;
|
|
64
|
+
record: ConnectionInvocationRecord;
|
|
65
|
+
}>
|
|
66
|
+
| Readonly<{
|
|
67
|
+
kind: "connectionInvocationCompleted";
|
|
68
|
+
runId: RunId;
|
|
69
|
+
workflowId: WorkflowId;
|
|
70
|
+
parent?: ParentExecutionRef;
|
|
71
|
+
at: string;
|
|
72
|
+
record: ConnectionInvocationRecord;
|
|
73
|
+
}>
|
|
74
|
+
| Readonly<{
|
|
75
|
+
kind: "connectionInvocationFailed";
|
|
76
|
+
runId: RunId;
|
|
77
|
+
workflowId: WorkflowId;
|
|
78
|
+
parent?: ParentExecutionRef;
|
|
79
|
+
at: string;
|
|
80
|
+
record: ConnectionInvocationRecord;
|
|
81
|
+
}>
|
|
82
|
+
| Readonly<{
|
|
83
|
+
kind: "testSuiteStarted";
|
|
84
|
+
testSuiteRunId: TestSuiteRunId;
|
|
85
|
+
workflowId: WorkflowId;
|
|
86
|
+
triggerNodeId: string;
|
|
87
|
+
triggerNodeName?: string;
|
|
88
|
+
concurrency: number;
|
|
89
|
+
at: string;
|
|
90
|
+
}>
|
|
91
|
+
| Readonly<{
|
|
92
|
+
kind: "testSuiteFinished";
|
|
93
|
+
testSuiteRunId: TestSuiteRunId;
|
|
94
|
+
workflowId: WorkflowId;
|
|
95
|
+
status: TestSuiteRunStatus;
|
|
96
|
+
totalCases: number;
|
|
97
|
+
passedCases: number;
|
|
98
|
+
failedCases: number;
|
|
99
|
+
at: string;
|
|
100
|
+
}>
|
|
101
|
+
| Readonly<{
|
|
102
|
+
kind: "testCaseStarted";
|
|
103
|
+
testSuiteRunId: TestSuiteRunId;
|
|
104
|
+
testCaseIndex: number;
|
|
105
|
+
runId: RunId;
|
|
106
|
+
workflowId: WorkflowId;
|
|
107
|
+
testCaseLabel?: string;
|
|
108
|
+
at: string;
|
|
109
|
+
}>
|
|
110
|
+
| Readonly<{
|
|
111
|
+
kind: "testCaseCompleted";
|
|
112
|
+
testSuiteRunId: TestSuiteRunId;
|
|
113
|
+
testCaseIndex: number;
|
|
114
|
+
runId: RunId;
|
|
115
|
+
workflowId: WorkflowId;
|
|
116
|
+
status: TestCaseRunStatus;
|
|
117
|
+
at: string;
|
|
44
118
|
}>;
|
|
45
119
|
|
|
46
120
|
export interface RunEventSubscription {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivationIdFactory,
|
|
3
|
+
ConnectionInvocationId,
|
|
4
|
+
NodeExecutionContext,
|
|
5
|
+
NodeId,
|
|
6
|
+
RunnableNodeConfig,
|
|
7
|
+
TelemetrySpanScope,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Builds a re-rooted child execution context for sub-agent (and other deeply-nested) invocations.
|
|
12
|
+
*
|
|
13
|
+
* At the orchestrator's `agent.tool.call` boundary the inner runtime needs a ctx whose:
|
|
14
|
+
* - `nodeId` is the tool's connection node id (so inner LLM/tool connection ids derive correctly),
|
|
15
|
+
* - `activationId` is fresh (so its connection-invocation rows are uniquely identifiable),
|
|
16
|
+
* - `telemetry` parents children under the tool-call span (not the orchestrator's node span),
|
|
17
|
+
* - `binary` is scoped to the new (nodeId, activationId),
|
|
18
|
+
* - `parentInvocationId` points back to the tool-call invocation for downstream lineage.
|
|
19
|
+
*
|
|
20
|
+
* Registered via factory in {@link EngineRuntimeRegistrar} so constructors stay free of parameter
|
|
21
|
+
* decorators (Next/SWC and coverage tooling cannot parse them on in-repo sources).
|
|
22
|
+
*/
|
|
23
|
+
export class ChildExecutionScopeFactory {
|
|
24
|
+
constructor(private readonly activationIdFactory: ActivationIdFactory) {}
|
|
25
|
+
|
|
26
|
+
forSubAgent<TConfig extends RunnableNodeConfig<any, any>>(
|
|
27
|
+
args: Readonly<{
|
|
28
|
+
parentCtx: NodeExecutionContext<TConfig>;
|
|
29
|
+
childNodeId: NodeId;
|
|
30
|
+
childConfig: TConfig;
|
|
31
|
+
parentInvocationId: ConnectionInvocationId;
|
|
32
|
+
parentSpan: TelemetrySpanScope;
|
|
33
|
+
}>,
|
|
34
|
+
): NodeExecutionContext<TConfig> {
|
|
35
|
+
const childActivationId = this.activationIdFactory.makeActivationId();
|
|
36
|
+
const childTelemetry = args.parentSpan.asNodeTelemetry({
|
|
37
|
+
nodeId: args.childNodeId,
|
|
38
|
+
activationId: childActivationId,
|
|
39
|
+
});
|
|
40
|
+
const childBinary = args.parentCtx.binary.forNode({
|
|
41
|
+
nodeId: args.childNodeId,
|
|
42
|
+
activationId: childActivationId,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
...args.parentCtx,
|
|
46
|
+
nodeId: args.childNodeId,
|
|
47
|
+
activationId: childActivationId,
|
|
48
|
+
config: args.childConfig,
|
|
49
|
+
telemetry: childTelemetry,
|
|
50
|
+
binary: childBinary,
|
|
51
|
+
parentInvocationId: args.parentInvocationId,
|
|
52
|
+
iterationId: undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BinaryStorage,
|
|
3
|
+
CollectionsContext,
|
|
3
4
|
CostTrackingTelemetryFactory,
|
|
4
5
|
ExecutionContext,
|
|
5
6
|
ExecutionContextFactory,
|
|
@@ -8,6 +9,7 @@ import type {
|
|
|
8
9
|
ParentExecutionRef,
|
|
9
10
|
RunDataSnapshot,
|
|
10
11
|
RunId,
|
|
12
|
+
RunTestContext,
|
|
11
13
|
WorkflowId,
|
|
12
14
|
} from "../types";
|
|
13
15
|
import { NoOpCostTrackingTelemetryFactory, NoOpExecutionTelemetryFactory } from "../types";
|
|
@@ -26,6 +28,7 @@ export class DefaultExecutionContextFactory implements ExecutionContextFactory {
|
|
|
26
28
|
private readonly telemetryFactory: ExecutionTelemetryFactory = new NoOpExecutionTelemetryFactory(),
|
|
27
29
|
private readonly costTrackingFactory: CostTrackingTelemetryFactory = new NoOpCostTrackingTelemetryFactory(),
|
|
28
30
|
private readonly currentDate: () => Date = () => new Date(),
|
|
31
|
+
private readonly collections?: CollectionsContext,
|
|
29
32
|
) {}
|
|
30
33
|
|
|
31
34
|
create(args: {
|
|
@@ -40,6 +43,7 @@ export class DefaultExecutionContextFactory implements ExecutionContextFactory {
|
|
|
40
43
|
nodeState?: NodeExecutionStatePublisher;
|
|
41
44
|
telemetry?: ExecutionContext["telemetry"];
|
|
42
45
|
getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
|
|
46
|
+
testContext?: RunTestContext;
|
|
43
47
|
}): ExecutionContext {
|
|
44
48
|
const baseTelemetry =
|
|
45
49
|
args.telemetry ??
|
|
@@ -66,6 +70,8 @@ export class DefaultExecutionContextFactory implements ExecutionContextFactory {
|
|
|
66
70
|
telemetry,
|
|
67
71
|
binary: new DefaultExecutionBinaryService(this.binaryStorage, args.workflowId, args.runId, this.currentDate),
|
|
68
72
|
getCredential: args.getCredential,
|
|
73
|
+
testContext: args.testContext,
|
|
74
|
+
collections: this.collections,
|
|
69
75
|
};
|
|
70
76
|
}
|
|
71
77
|
}
|
|
@@ -62,6 +62,15 @@ export class ExecutionTelemetryCostTrackingDecoratorFactory {
|
|
|
62
62
|
costTracking: args.costTracking.forScope(nodeTelemetry),
|
|
63
63
|
});
|
|
64
64
|
},
|
|
65
|
+
asNodeTelemetry: (
|
|
66
|
+
rescope: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>,
|
|
67
|
+
): NodeExecutionTelemetry => {
|
|
68
|
+
const nodeTelemetry = args.telemetry.asNodeTelemetry(rescope);
|
|
69
|
+
return this.decorateNodeExecutionTelemetry({
|
|
70
|
+
telemetry: nodeTelemetry,
|
|
71
|
+
costTracking: args.costTracking.forScope(nodeTelemetry),
|
|
72
|
+
});
|
|
73
|
+
},
|
|
65
74
|
};
|
|
66
75
|
}
|
|
67
76
|
|
|
@@ -79,6 +88,15 @@ export class ExecutionTelemetryCostTrackingDecoratorFactory {
|
|
|
79
88
|
artifact: TelemetryArtifactAttachment,
|
|
80
89
|
): Promise<TelemetryArtifactReference> | TelemetryArtifactReference => args.scope.attachArtifact(artifact),
|
|
81
90
|
end: (endArgs?: TelemetrySpanEnd) => args.scope.end(endArgs),
|
|
91
|
+
asNodeTelemetry: (
|
|
92
|
+
rescope: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>,
|
|
93
|
+
): NodeExecutionTelemetry => {
|
|
94
|
+
const nodeTelemetry = args.scope.asNodeTelemetry(rescope);
|
|
95
|
+
return this.decorateNodeExecutionTelemetry({
|
|
96
|
+
telemetry: nodeTelemetry,
|
|
97
|
+
costTracking: args.costTracking.forScope(nodeTelemetry),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
82
100
|
};
|
|
83
101
|
}
|
|
84
102
|
}
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
TriggerNode,
|
|
14
14
|
WorkflowNodeInstanceFactory,
|
|
15
15
|
} from "../types";
|
|
16
|
+
import { NodeIterationIdFactory } from "../workflow/definition/NodeIterationIdFactory";
|
|
16
17
|
|
|
17
18
|
import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
|
|
18
19
|
import { ItemExprResolver } from "./ItemExprResolver";
|
|
@@ -158,13 +159,20 @@ export class NodeExecutor {
|
|
|
158
159
|
const parsed = inputSchema.parse(item.json);
|
|
159
160
|
const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
|
|
160
161
|
const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
|
|
161
|
-
const
|
|
162
|
+
const baseCtx = this.pickExecutionContext(runnableCtx, resolvedCtx);
|
|
163
|
+
// Mint a per-item iteration id and stamp it (with the item index) onto the ctx so connection
|
|
164
|
+
// invocations and telemetry written from inside `node.execute` carry the per-item identity.
|
|
165
|
+
const iterationCtx = {
|
|
166
|
+
...baseCtx,
|
|
167
|
+
iterationId: NodeIterationIdFactory.create(),
|
|
168
|
+
itemIndex: i,
|
|
169
|
+
} as NodeExecutionContext<RunnableNodeConfig>;
|
|
162
170
|
const args: RunnableNodeExecuteArgs = {
|
|
163
171
|
input: parsed,
|
|
164
172
|
item,
|
|
165
173
|
itemIndex: i,
|
|
166
174
|
items: inputBatch,
|
|
167
|
-
ctx,
|
|
175
|
+
ctx: iterationCtx,
|
|
168
176
|
};
|
|
169
177
|
const raw = await Promise.resolve(node.execute(args));
|
|
170
178
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
@@ -4,6 +4,7 @@ import type { NodeId, NodeResolver, WorkflowDefinition, WorkflowNodeInstanceFact
|
|
|
4
4
|
import { MissingRuntimeNode, MissingRuntimeTrigger } from "../workflowSnapshots";
|
|
5
5
|
import { MissingRuntimeNodeToken } from "../workflowSnapshots/MissingRuntimeNodeToken";
|
|
6
6
|
import { MissingRuntimeTriggerToken } from "../workflowSnapshots/MissingRuntimeTriggerToken";
|
|
7
|
+
import { NodeInstantiationError } from "./NodeInstantiationError";
|
|
7
8
|
|
|
8
9
|
export class NodeInstanceFactory implements WorkflowNodeInstanceFactory {
|
|
9
10
|
constructor(private readonly nodeResolver: NodeResolver) {}
|
|
@@ -11,7 +12,18 @@ export class NodeInstanceFactory implements WorkflowNodeInstanceFactory {
|
|
|
11
12
|
createNodes(workflow: WorkflowDefinition): Map<NodeId, unknown> {
|
|
12
13
|
const nodeInstances = new Map<NodeId, unknown>();
|
|
13
14
|
for (const definition of workflow.nodes) {
|
|
14
|
-
|
|
15
|
+
try {
|
|
16
|
+
nodeInstances.set(definition.id, this.createNode(definition));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
if (err instanceof NodeInstantiationError) {
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
throw new NodeInstantiationError(
|
|
22
|
+
definition.id,
|
|
23
|
+
typeof definition.type === "function" ? definition.type.name : String(definition.type),
|
|
24
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
15
27
|
}
|
|
16
28
|
return nodeInstances;
|
|
17
29
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NodeId } from "../types";
|
|
2
|
+
|
|
3
|
+
export class NodeInstantiationError extends Error {
|
|
4
|
+
readonly name = "NodeInstantiationError";
|
|
5
|
+
readonly originalError: Error;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
readonly nodeId: NodeId,
|
|
9
|
+
readonly nodeType: string,
|
|
10
|
+
originalError: Error,
|
|
11
|
+
) {
|
|
12
|
+
super(`Failed to instantiate node "${nodeId}" (type ${nodeType}): ${originalError.message}`);
|
|
13
|
+
this.originalError = originalError;
|
|
14
|
+
this.stack = originalError.stack;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -28,6 +28,7 @@ export class NodeRunStateWriter implements NodeExecutionStatePublisher {
|
|
|
28
28
|
kind: "nodeQueued" | "nodeStarted" | "nodeCompleted" | "nodeFailed",
|
|
29
29
|
snapshot: NodeExecutionSnapshot,
|
|
30
30
|
) => Promise<void>,
|
|
31
|
+
private readonly publishConnectionInvocationEvent?: (record: ConnectionInvocationRecord) => Promise<void>,
|
|
31
32
|
) {}
|
|
32
33
|
|
|
33
34
|
markQueued(args: {
|
|
@@ -148,11 +149,17 @@ export class NodeRunStateWriter implements NodeExecutionStatePublisher {
|
|
|
148
149
|
startedAt: args.startedAt,
|
|
149
150
|
finishedAt: args.finishedAt,
|
|
150
151
|
updatedAt,
|
|
152
|
+
iterationId: args.iterationId,
|
|
153
|
+
itemIndex: args.itemIndex,
|
|
154
|
+
parentInvocationId: args.parentInvocationId,
|
|
151
155
|
};
|
|
152
156
|
await this.workflowExecutionRepository.save({
|
|
153
157
|
...state,
|
|
154
158
|
connectionInvocations: [...(state.connectionInvocations ?? []), record],
|
|
155
159
|
});
|
|
160
|
+
if (this.publishConnectionInvocationEvent) {
|
|
161
|
+
await this.publishConnectionInvocationEvent(record);
|
|
162
|
+
}
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
165
|
|
|
@@ -6,16 +6,20 @@ import type {
|
|
|
6
6
|
WorkflowId,
|
|
7
7
|
} from "../types";
|
|
8
8
|
|
|
9
|
+
import { ConnectionInvocationEventPublisher } from "../events/ConnectionInvocationEventPublisher";
|
|
9
10
|
import { NodeEventPublisher } from "../events/NodeEventPublisher";
|
|
11
|
+
import type { RunEventBus } from "../events/runEvents";
|
|
10
12
|
import { NodeRunStateWriter } from "./NodeRunStateWriter";
|
|
11
13
|
|
|
12
14
|
export class NodeRunStateWriterFactory {
|
|
13
15
|
constructor(
|
|
14
16
|
private readonly workflowExecutionRepository: WorkflowExecutionRepository,
|
|
15
17
|
private readonly nodeEventPublisher: NodeEventPublisher,
|
|
18
|
+
private readonly eventBus?: RunEventBus,
|
|
16
19
|
) {}
|
|
17
20
|
|
|
18
21
|
create(runId: RunId, workflowId: WorkflowId, parent: ParentExecutionRef | undefined): NodeExecutionStatePublisher {
|
|
22
|
+
const connectionInvocationEventPublisher = new ConnectionInvocationEventPublisher(this.eventBus, parent);
|
|
19
23
|
return new NodeRunStateWriter(
|
|
20
24
|
this.workflowExecutionRepository,
|
|
21
25
|
runId,
|
|
@@ -24,6 +28,9 @@ export class NodeRunStateWriterFactory {
|
|
|
24
28
|
async (kind, snapshot) => {
|
|
25
29
|
await this.nodeEventPublisher.publish(kind, snapshot);
|
|
26
30
|
},
|
|
31
|
+
async (record) => {
|
|
32
|
+
await connectionInvocationEventPublisher.publish(record);
|
|
33
|
+
},
|
|
27
34
|
);
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ParentExecutionRef,
|
|
6
6
|
RunDataFactory,
|
|
7
7
|
RunId,
|
|
8
|
+
RunTestContext,
|
|
8
9
|
WorkflowId,
|
|
9
10
|
} from "../types";
|
|
10
11
|
|
|
@@ -30,6 +31,7 @@ export class WorkflowRunExecutionContextFactory {
|
|
|
30
31
|
engineMaxSubworkflowDepth: number;
|
|
31
32
|
data: ReturnType<RunDataFactory["create"]>;
|
|
32
33
|
nodeState?: NodeExecutionStatePublisher;
|
|
34
|
+
testContext?: RunTestContext;
|
|
33
35
|
}) {
|
|
34
36
|
return this.executionContextFactory.create({
|
|
35
37
|
runId: args.runId,
|
|
@@ -42,6 +44,7 @@ export class WorkflowRunExecutionContextFactory {
|
|
|
42
44
|
data: args.data,
|
|
43
45
|
nodeState: args.nodeState,
|
|
44
46
|
getCredential: this.credentialResolverFactory.create(args.workflowId, args.nodeId),
|
|
47
|
+
testContext: args.testContext,
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
}
|
package/src/execution/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { ActivationEnqueueService } from "./ActivationEnqueueService";
|
|
2
|
+
export { ChildExecutionScopeFactory } from "./ChildExecutionScopeFactory";
|
|
2
3
|
export { NodeActivationRequestInputPreparer } from "./NodeActivationRequestInputPreparer";
|
|
3
4
|
export { NodeInputContractError } from "./NodeInputContractError";
|
|
5
|
+
export { NodeInstantiationError } from "./NodeInstantiationError";
|
|
4
6
|
export { CredentialResolverFactory } from "./CredentialResolverFactory";
|
|
5
7
|
export { DefaultAsyncSleeper } from "./DefaultAsyncSleeper";
|
|
6
8
|
export { DefaultExecutionContextFactory } from "./DefaultExecutionContextFactory";
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./runtime-types/runtimeTypeDecorators.types";
|
|
|
14
14
|
export * from "./serialization/ItemsInputNormalizer";
|
|
15
15
|
export { DefaultExecutionBinaryService, UnavailableBinaryStorage } from "./binaries";
|
|
16
16
|
export {
|
|
17
|
+
ChildExecutionScopeFactory,
|
|
17
18
|
CredentialResolverFactory,
|
|
18
19
|
DefaultAsyncSleeper,
|
|
19
20
|
DefaultExecutionContextFactory,
|
|
@@ -26,3 +27,10 @@ export { EngineExecutionLimitsPolicy, type EngineExecutionLimitsPolicyConfig } f
|
|
|
26
27
|
export { InMemoryBinaryStorage, InMemoryRunDataFactory } from "./runStorage";
|
|
27
28
|
export { InMemoryLiveWorkflowRepository, RunIntentService } from "./runtime";
|
|
28
29
|
export * from "./types";
|
|
30
|
+
export { PollingTriggerRuntime, PollingTriggerDedupWindow, NoOpPollingTriggerLogger } from "./triggers/polling";
|
|
31
|
+
export type {
|
|
32
|
+
PollingTriggerLogger,
|
|
33
|
+
PollingRunCycleArgs,
|
|
34
|
+
PollingRunCycleResult,
|
|
35
|
+
PollingTriggerStartArgs,
|
|
36
|
+
} from "./triggers/polling";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mints fresh {@link AbortController}s. Injected (rather than direct `new`) to honor the
|
|
3
|
+
* codebase's no-direct-construction rule and to give tests a seam for substituting a fake.
|
|
4
|
+
*/
|
|
5
|
+
export class AbortControllerFactory {
|
|
6
|
+
create(): AbortController {
|
|
7
|
+
return new AbortController();
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -77,6 +77,7 @@ export class NodeExecutionRequestHandlerService implements NodeExecutionRequestH
|
|
|
77
77
|
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
78
78
|
data,
|
|
79
79
|
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, resolvedParent),
|
|
80
|
+
testContext: state.executionOptions?.testContext,
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
const inputsByPort = pendingExecution.inputsByPort;
|
|
@@ -141,6 +141,7 @@ export class RunContinuationService {
|
|
|
141
141
|
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
142
142
|
data,
|
|
143
143
|
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
144
|
+
testContext: state.executionOptions?.testContext,
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
data.setOutputs(args.nodeId, args.outputs);
|
|
@@ -755,6 +756,7 @@ export class RunContinuationService {
|
|
|
755
756
|
engineMaxSubworkflowDepth: webhookLimits.engineMaxSubworkflowDepth,
|
|
756
757
|
data,
|
|
757
758
|
nodeState: this.nodeStatePublisherFactory.create(args.state.runId, args.state.workflowId, args.state.parent),
|
|
759
|
+
testContext: args.state.executionOptions?.testContext,
|
|
758
760
|
});
|
|
759
761
|
const request = this.nodeActivationRequestComposer.createFromPlannedActivation({
|
|
760
762
|
next,
|
|
@@ -859,6 +861,7 @@ export class RunContinuationService {
|
|
|
859
861
|
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
860
862
|
data,
|
|
861
863
|
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
864
|
+
testContext: state.executionOptions?.testContext,
|
|
862
865
|
});
|
|
863
866
|
const activationId = pendingExecution.activationId;
|
|
864
867
|
return {
|
|
@@ -34,8 +34,12 @@ import { RunPolicySnapshotFactory } from "../policies/storage/RunPolicySnapshotF
|
|
|
34
34
|
import { ActivationEnqueueService } from "../execution/ActivationEnqueueService";
|
|
35
35
|
import { NodeRunStateWriterFactory } from "../execution/NodeRunStateWriterFactory";
|
|
36
36
|
import { NodeActivationRequestComposer } from "../execution/NodeActivationRequestComposer";
|
|
37
|
+
import { NodeExecutionSnapshotFactory } from "../execution/NodeExecutionSnapshotFactory";
|
|
38
|
+
import { NodeInstantiationError } from "../execution/NodeInstantiationError";
|
|
39
|
+
import { PersistedRunStateTerminalBuilder } from "../execution/PersistedRunStateTerminalBuilder";
|
|
37
40
|
import { RunStateSemantics } from "../execution/RunStateSemantics";
|
|
38
41
|
import { WorkflowRunExecutionContextFactory } from "../execution/WorkflowRunExecutionContextFactory";
|
|
42
|
+
import { NodeEventPublisher } from "../events/NodeEventPublisher";
|
|
39
43
|
|
|
40
44
|
export class RunStartService {
|
|
41
45
|
constructor(
|
|
@@ -52,6 +56,8 @@ export class RunStartService {
|
|
|
52
56
|
private readonly waiters: EngineWaiters,
|
|
53
57
|
private readonly workflowPolicyRuntimeDefaults: WorkflowPolicyRuntimeDefaults | undefined,
|
|
54
58
|
private readonly executionLimitsPolicy: EngineExecutionLimitsPolicy,
|
|
59
|
+
private readonly nodeEventPublisher: NodeEventPublisher,
|
|
60
|
+
private readonly persistedRunStateTerminalBuilder: PersistedRunStateTerminalBuilder,
|
|
55
61
|
) {}
|
|
56
62
|
|
|
57
63
|
async runWorkflow(
|
|
@@ -96,8 +102,29 @@ export class RunStartService {
|
|
|
96
102
|
engineMaxSubworkflowDepth: mergedExecutionOptions.maxSubworkflowDepth!,
|
|
97
103
|
data,
|
|
98
104
|
nodeState: this.nodeStatePublisherFactory.create(runId, workflow.id, parent),
|
|
105
|
+
testContext: mergedExecutionOptions.testContext,
|
|
99
106
|
});
|
|
100
|
-
|
|
107
|
+
let planning: Readonly<ReturnType<EngineWorkflowPlanningFactory["create"]>>;
|
|
108
|
+
try {
|
|
109
|
+
planning = this.planningFactory.create(workflow);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err instanceof NodeInstantiationError) {
|
|
112
|
+
return await this.failRunDuringPlanning({
|
|
113
|
+
runId,
|
|
114
|
+
workflowId: workflow.id,
|
|
115
|
+
startedAt,
|
|
116
|
+
parent,
|
|
117
|
+
executionOptions: mergedExecutionOptions,
|
|
118
|
+
control: undefined,
|
|
119
|
+
workflowSnapshot,
|
|
120
|
+
mutableState,
|
|
121
|
+
policySnapshot,
|
|
122
|
+
err,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
const { topology, planner } = planning;
|
|
101
128
|
const startDefinition = topology.defsById.get(startAt);
|
|
102
129
|
if (!startDefinition) {
|
|
103
130
|
throw new Error(`Unknown start nodeId: ${startAt}`);
|
|
@@ -183,7 +210,27 @@ export class RunStartService {
|
|
|
183
210
|
engineCounters: { completedNodeActivations: 0 },
|
|
184
211
|
});
|
|
185
212
|
|
|
186
|
-
|
|
213
|
+
let planningFromState: Readonly<ReturnType<EngineWorkflowPlanningFactory["create"]>>;
|
|
214
|
+
try {
|
|
215
|
+
planningFromState = this.planningFactory.create(request.workflow);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (err instanceof NodeInstantiationError) {
|
|
218
|
+
return await this.failRunDuringPlanning({
|
|
219
|
+
runId,
|
|
220
|
+
workflowId: request.workflow.id,
|
|
221
|
+
startedAt,
|
|
222
|
+
parent: request.parent,
|
|
223
|
+
executionOptions: mergedExecutionOptions,
|
|
224
|
+
control,
|
|
225
|
+
workflowSnapshot,
|
|
226
|
+
mutableState,
|
|
227
|
+
policySnapshot,
|
|
228
|
+
err,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
const { topology, planner } = planningFromState;
|
|
187
234
|
const plan = CurrentStateFrontierPlanner.createFromTopology(topology).plan({
|
|
188
235
|
currentState: this.createRunCurrentState(request.currentState, mutableState),
|
|
189
236
|
stopCondition: control.stopCondition,
|
|
@@ -206,6 +253,7 @@ export class RunStartService {
|
|
|
206
253
|
engineMaxSubworkflowDepth: mergedExecutionOptions.maxSubworkflowDepth!,
|
|
207
254
|
data,
|
|
208
255
|
nodeState: this.nodeStatePublisherFactory.create(runId, request.workflow.id, request.parent),
|
|
256
|
+
testContext: mergedExecutionOptions.testContext,
|
|
209
257
|
});
|
|
210
258
|
|
|
211
259
|
return await this.scheduleInitialPlan({
|
|
@@ -229,10 +277,17 @@ export class RunStartService {
|
|
|
229
277
|
currentState: RunCurrentState | undefined,
|
|
230
278
|
mutableState: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["mutableState"],
|
|
231
279
|
): RunCurrentState {
|
|
280
|
+
// Each `ConnectionInvocationRecord` represents a single, auditable LLM/tool call
|
|
281
|
+
// that must belong to exactly one run. Reruns start from a fresh invocation
|
|
282
|
+
// ledger so the new run only records what it actually invokes. The prior run's
|
|
283
|
+
// invocations remain queryable on that run's persisted state (their true owner).
|
|
284
|
+
// Carrying them over here would write duplicate `ExecutionInstance` rows whose
|
|
285
|
+
// primary key (`invocationId`) already exists under the previous run, causing a
|
|
286
|
+
// `Unique constraint failed on the fields: (instance_id)` violation on first save.
|
|
232
287
|
return {
|
|
233
288
|
outputsByNode: { ...(currentState?.outputsByNode ?? {}) },
|
|
234
289
|
nodeSnapshotsByNodeId: { ...(currentState?.nodeSnapshotsByNodeId ?? {}) },
|
|
235
|
-
connectionInvocations:
|
|
290
|
+
connectionInvocations: [],
|
|
236
291
|
mutableState: mutableState ?? currentState?.mutableState,
|
|
237
292
|
};
|
|
238
293
|
}
|
|
@@ -448,4 +503,68 @@ export class RunStartService {
|
|
|
448
503
|
this.waiters.resolveRunCompletion(result);
|
|
449
504
|
return result;
|
|
450
505
|
}
|
|
506
|
+
|
|
507
|
+
private async failRunDuringPlanning(args: {
|
|
508
|
+
runId: RunId;
|
|
509
|
+
workflowId: WorkflowId;
|
|
510
|
+
startedAt: string;
|
|
511
|
+
parent?: ParentExecutionRef;
|
|
512
|
+
executionOptions?: RunExecutionOptions;
|
|
513
|
+
control?: PersistedRunControlState;
|
|
514
|
+
workflowSnapshot: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["workflowSnapshot"];
|
|
515
|
+
mutableState: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["mutableState"];
|
|
516
|
+
policySnapshot: NonNullable<Awaited<ReturnType<WorkflowExecutionRepository["load"]>>>["policySnapshot"];
|
|
517
|
+
err: NodeInstantiationError;
|
|
518
|
+
}): Promise<RunResult> {
|
|
519
|
+
const finishedAt = new Date().toISOString();
|
|
520
|
+
const failedSnapshot = NodeExecutionSnapshotFactory.failed({
|
|
521
|
+
previous: undefined,
|
|
522
|
+
runId: args.runId,
|
|
523
|
+
workflowId: args.workflowId,
|
|
524
|
+
nodeId: args.err.nodeId,
|
|
525
|
+
activationId: "planning_failure",
|
|
526
|
+
parent: args.parent,
|
|
527
|
+
finishedAt,
|
|
528
|
+
inputsByPort: {},
|
|
529
|
+
error: args.err,
|
|
530
|
+
});
|
|
531
|
+
const failedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
532
|
+
state: {
|
|
533
|
+
runId: args.runId,
|
|
534
|
+
workflowId: args.workflowId,
|
|
535
|
+
startedAt: args.startedAt,
|
|
536
|
+
parent: args.parent,
|
|
537
|
+
executionOptions: args.executionOptions,
|
|
538
|
+
control: args.control,
|
|
539
|
+
workflowSnapshot: args.workflowSnapshot,
|
|
540
|
+
mutableState: args.mutableState,
|
|
541
|
+
policySnapshot: args.policySnapshot,
|
|
542
|
+
engineCounters: { completedNodeActivations: 0 },
|
|
543
|
+
status: "pending",
|
|
544
|
+
pending: undefined,
|
|
545
|
+
queue: [],
|
|
546
|
+
outputsByNode: {},
|
|
547
|
+
nodeSnapshotsByNodeId: {},
|
|
548
|
+
connectionInvocations: [],
|
|
549
|
+
},
|
|
550
|
+
engineCounters: { completedNodeActivations: 0 },
|
|
551
|
+
status: "failed",
|
|
552
|
+
queue: [],
|
|
553
|
+
outputsByNode: {},
|
|
554
|
+
nodeSnapshotsByNodeId: { [args.err.nodeId]: failedSnapshot },
|
|
555
|
+
finishedAtIso: finishedAt,
|
|
556
|
+
});
|
|
557
|
+
await this.workflowExecutionRepository.save(failedState);
|
|
558
|
+
await this.nodeEventPublisher.publish("nodeFailed", failedSnapshot);
|
|
559
|
+
|
|
560
|
+
const result: RunResult = {
|
|
561
|
+
runId: args.runId,
|
|
562
|
+
workflowId: args.workflowId,
|
|
563
|
+
startedAt: args.startedAt,
|
|
564
|
+
status: "failed",
|
|
565
|
+
error: { message: args.err.message },
|
|
566
|
+
};
|
|
567
|
+
this.waiters.resolveRunCompletion(result);
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
451
570
|
}
|