@codemation/core 0.8.0 → 0.10.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
  4. package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
  5. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
  6. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
  7. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
  8. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
  9. package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
  10. package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
  11. package/dist/bootstrap/index.cjs +5 -2
  12. package/dist/bootstrap/index.d.cts +212 -135
  13. package/dist/bootstrap/index.d.ts +4 -4
  14. package/dist/bootstrap/index.js +3 -3
  15. package/dist/{bootstrap-BaN6hZ5I.cjs → bootstrap-Cm5ruQxx.cjs} +263 -12
  16. package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
  17. package/dist/bootstrap-D3r505ko.js +454 -0
  18. package/dist/bootstrap-D3r505ko.js.map +1 -0
  19. package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
  20. package/dist/index.cjs +323 -176
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +544 -91
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +299 -166
  25. package/dist/index.js.map +1 -1
  26. package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
  27. package/dist/runtime-BGNbRnqs.js.map +1 -0
  28. package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
  29. package/dist/runtime-DKXJwTNv.cjs.map +1 -0
  30. package/dist/testing.cjs +5 -5
  31. package/dist/testing.cjs.map +1 -1
  32. package/dist/testing.d.cts +2 -2
  33. package/dist/testing.d.ts +2 -2
  34. package/dist/testing.js +4 -4
  35. package/dist/testing.js.map +1 -1
  36. package/package.json +7 -2
  37. package/src/ai/AiHost.ts +42 -14
  38. package/src/authoring/DefinedCollectionRegistry.ts +17 -0
  39. package/src/authoring/defineCollection.types.ts +181 -0
  40. package/src/authoring/definePollingTrigger.types.ts +396 -0
  41. package/src/authoring/definePollingTriggerInternals.ts +74 -0
  42. package/src/authoring/index.ts +19 -0
  43. package/src/bootstrap/index.ts +9 -0
  44. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +21 -14
  45. package/src/browser.ts +1 -0
  46. package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
  47. package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
  48. package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
  49. package/src/contracts/assertionTypes.ts +63 -0
  50. package/src/contracts/baseTypes.ts +12 -0
  51. package/src/contracts/collectionTypes.ts +44 -0
  52. package/src/contracts/credentialTypes.ts +23 -1
  53. package/src/contracts/executionPersistenceContracts.ts +30 -0
  54. package/src/contracts/index.ts +4 -0
  55. package/src/contracts/runTypes.ts +37 -1
  56. package/src/contracts/runtimeTypes.ts +42 -0
  57. package/src/contracts/telemetryTypes.ts +8 -0
  58. package/src/contracts/testTriggerTypes.ts +66 -0
  59. package/src/contracts/workflowTypes.ts +36 -7
  60. package/src/contracts.ts +59 -0
  61. package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
  62. package/src/events/index.ts +1 -0
  63. package/src/events/runEvents.ts +74 -0
  64. package/src/execution/ChildExecutionScopeFactory.ts +55 -0
  65. package/src/execution/DefaultExecutionContextFactory.ts +6 -0
  66. package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
  67. package/src/execution/NodeExecutor.ts +10 -2
  68. package/src/execution/NodeInstanceFactory.ts +13 -1
  69. package/src/execution/NodeInstantiationError.ts +16 -0
  70. package/src/execution/NodeRunStateWriter.ts +7 -0
  71. package/src/execution/NodeRunStateWriterFactory.ts +7 -0
  72. package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
  73. package/src/execution/index.ts +2 -0
  74. package/src/index.ts +8 -0
  75. package/src/orchestration/AbortControllerFactory.ts +9 -0
  76. package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
  77. package/src/orchestration/RunContinuationService.ts +3 -0
  78. package/src/orchestration/RunStartService.ts +122 -3
  79. package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
  80. package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
  81. package/src/orchestration/TriggerRuntimeService.ts +34 -7
  82. package/src/orchestration/index.ts +9 -0
  83. package/src/runtime/EngineFactory.ts +12 -0
  84. package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +1 -3
  85. package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
  86. package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
  87. package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
  88. package/src/triggers/polling/index.ts +5 -0
  89. package/src/types/index.ts +12 -9
  90. package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
  91. package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
  92. package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
  93. package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
  94. package/src/workflow/index.ts +3 -0
  95. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
  96. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
  97. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
  98. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
  99. package/dist/bootstrap-BaN6hZ5I.cjs.map +0 -1
  100. package/dist/bootstrap-d_BMaDT4.js +0 -221
  101. package/dist/bootstrap-d_BMaDT4.js.map +0 -1
  102. package/dist/runtime-DUW6tIJ1.js.map +0 -1
  103. package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
@@ -3,13 +3,18 @@ import type { ZodType } from "zod";
3
3
  import type { TypeToken } from "../di";
4
4
  import type { CredentialRequirement } from "./credentialTypes";
5
5
  import type { RetryPolicySpec } from "./retryPolicySpec.types";
6
+ import type { InputPortKey, NodeConnectionName, NodeId, OutputPortKey, WorkflowId } from "./baseTypes";
7
+
8
+ export type {
9
+ InputPortKey,
10
+ NodeConnectionName,
11
+ NodeId,
12
+ OutputPortKey,
13
+ PersistedTokenId,
14
+ WorkflowId,
15
+ } from "./baseTypes";
6
16
 
7
- export type WorkflowId = string;
8
- export type NodeId = string;
9
17
  export type NodeIdRef<TJson = unknown> = NodeId & Readonly<{ __codemationNodeJson?: TJson }>;
10
- export type OutputPortKey = string;
11
- export type InputPortKey = string;
12
- export type PersistedTokenId = string;
13
18
 
14
19
  export type NodeKind = "trigger" | "node";
15
20
  export type JsonPrimitive = string | number | boolean | null;
@@ -26,8 +31,6 @@ export interface Edge {
26
31
  to: { nodeId: NodeId; input: InputPortKey };
27
32
  }
28
33
 
29
- export type NodeConnectionName = string;
30
-
31
34
  /**
32
35
  * Named connection from a parent node to child nodes that exist in {@link WorkflowDefinition.nodes}
33
36
  * but are not traversed by the main execution graph. Parents are commonly executable nodes, but may
@@ -91,6 +94,14 @@ export interface NodeConfigBase {
91
94
  readonly declaredOutputPorts?: ReadonlyArray<OutputPortKey>;
92
95
  readonly declaredInputPorts?: ReadonlyArray<InputPortKey>;
93
96
  getCredentialRequirements?(): ReadonlyArray<CredentialRequirement>;
97
+ /**
98
+ * Marker: this node emits {@link import("./assertionTypes").AssertionResult}-shaped items on its
99
+ * `main` port. The TestSuiteOrchestrator (and host-side TestAssertionPersister) listen for
100
+ * `nodeCompleted` events from nodes with this flag set, and persist their output items as
101
+ * TestAssertion records (only when the run carries a `testContext`). Set on assertion node
102
+ * configs (e.g. `AssertionNodeConfig`, `StringEqualsAssertionNodeConfig`).
103
+ */
104
+ readonly emitsAssertions?: true;
94
105
  }
95
106
 
96
107
  export declare const runnableNodeInputType: unique symbol;
@@ -127,6 +138,12 @@ export interface TriggerNodeConfig<
127
138
  readonly kind: "trigger";
128
139
  readonly [triggerNodeOutputType]?: TOutputJson;
129
140
  readonly [triggerNodeSetupStateType]?: TSetupState;
141
+ /**
142
+ * Distinguishes triggers driven by the live activation policy (webhooks, cron, polling) from
143
+ * triggers driven only by the {@link TestSuiteOrchestrator}. `WorkflowActivation` skips
144
+ * `"test"` triggers; the orchestrator skips `"live"` triggers. Defaults to `"live"` when omitted.
145
+ */
146
+ readonly triggerKind?: "live" | "test";
130
147
  }
131
148
 
132
149
  export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any>> =
@@ -194,6 +211,12 @@ export type NodeOutputs = Partial<Record<OutputPortKey, Items>>;
194
211
 
195
212
  export type RunId = string;
196
213
  export type NodeActivationId = string;
214
+ /**
215
+ * One per-item iteration of a runnable node's execute loop. Refines `NodeActivationId` for
216
+ * per-item connection invocations and telemetry. Undefined when the executing node is a batch
217
+ * node or trigger that does not iterate items.
218
+ */
219
+ export type NodeIterationId = string;
197
220
 
198
221
  export interface ParentExecutionRef {
199
222
  runId: RunId;
@@ -205,6 +228,12 @@ export interface ParentExecutionRef {
205
228
  engineMaxNodeActivations?: number;
206
229
  /** Effective max subworkflow depth from the parent run (propagated to child policy merge). */
207
230
  engineMaxSubworkflowDepth?: number;
231
+ /**
232
+ * Test-suite linkage inherited by the child subworkflow run. Set by whichever node
233
+ * spawns the subworkflow when its own `ctx.testContext` is present, so assertions
234
+ * emitted inside a subworkflow land under the correct parent test case.
235
+ */
236
+ testContext?: import("./runTypes").RunTestContext;
208
237
  }
209
238
 
210
239
  export interface RunDataSnapshot {
@@ -0,0 +1,59 @@
1
+ // Pure-type-only re-exports. Use this for type-only consumers that should not drag in runtime DSL or factory code.
2
+ // This subpath prevents unnecessary compile-graph bloat for packages that only need types like NodeId, Items, etc.
3
+
4
+ export type * from "./contracts/baseTypes";
5
+ export type * from "./contracts/assertionTypes";
6
+ // assertionTypes also exports a runtime helper for deriving pass/fail from a score+threshold.
7
+ // We keep the type-only re-export above and surface the helper explicitly here so UI consumers
8
+ // (next-host) don't need to re-implement the comparison.
9
+ export { deriveAssertionPassed, DEFAULT_ASSERTION_PASS_THRESHOLD } from "./contracts/assertionTypes";
10
+ export type * from "./contracts/params";
11
+ export type * from "./contracts/retryPolicySpec.types";
12
+ export type * from "./contracts/CostCatalogContract";
13
+ export type * from "./contracts/executionPersistenceContracts";
14
+ export type * from "./contracts/runtimeTypes";
15
+ export type * from "./contracts/telemetryTypes";
16
+ export type * from "./contracts/testTriggerTypes";
17
+ export type * from "./contracts/runTypes";
18
+ export type * from "./contracts/webhookTypes";
19
+ export type * from "./contracts/workflowTypes";
20
+
21
+ // credentialTypes mixes types (Credential* interfaces) with runtime (CredentialUnboundError class).
22
+ // Export type-only subset for pure type consumers.
23
+ export type {
24
+ CredentialTypeId,
25
+ CredentialInstanceId,
26
+ CredentialMaterialSourceKind,
27
+ CredentialSetupStatus,
28
+ CredentialHealthStatus,
29
+ CredentialFieldSchema,
30
+ CredentialRequirement,
31
+ CredentialBindingKey,
32
+ CredentialBinding,
33
+ CredentialHealth,
34
+ OAuth2ProviderFromPublicConfig,
35
+ CredentialOAuth2ScopesFromPublicConfig,
36
+ CredentialOAuth2AuthDefinition,
37
+ CredentialAuthDefinition,
38
+ CredentialAdvancedSectionPresentation,
39
+ CredentialTypeDefinition,
40
+ CredentialJsonRecord,
41
+ CredentialInstanceRecord,
42
+ CredentialSessionFactoryArgs,
43
+ CredentialSessionFactory,
44
+ CredentialHealthTester,
45
+ CredentialType,
46
+ AnyCredentialType,
47
+ CredentialSessionService,
48
+ CredentialTypeRegistry,
49
+ } from "./contracts/credentialTypes";
50
+
51
+ // CostTrackingTelemetryContract mixes types with const runtime values (metric/attribute names).
52
+ // Export type-only subset for pure type consumers.
53
+ export type {
54
+ CostTrackingComponent,
55
+ CostTrackingUsageRecord,
56
+ CostTrackingPriceQuote,
57
+ CostTrackingTelemetry,
58
+ CostTrackingTelemetryFactory,
59
+ } from "./contracts/CostTrackingTelemetryContract";
@@ -0,0 +1,46 @@
1
+ import type { ConnectionInvocationRecord } from "../contracts/runTypes";
2
+ import type { ParentExecutionRef } from "../types";
3
+ import type { RunEventBus } from "./runEvents";
4
+
5
+ /**
6
+ * Publishes per-invocation lifecycle records onto the run {@link RunEventBus}.
7
+ *
8
+ * Surgical, per-invocation events let the UI update the right-side inspector
9
+ * timeline as each LLM round / tool call transitions through `running` → `completed`
10
+ * (or `failed`) without depending on a coarse `runSaved` poll.
11
+ */
12
+ export class ConnectionInvocationEventPublisher {
13
+ constructor(
14
+ private readonly eventBus: RunEventBus | undefined,
15
+ private readonly parent: ParentExecutionRef | undefined,
16
+ ) {}
17
+
18
+ async publish(record: ConnectionInvocationRecord): Promise<void> {
19
+ if (!this.eventBus) return;
20
+ const kind = this.kindFor(record);
21
+ if (!kind) return;
22
+ await this.eventBus.publish({
23
+ kind,
24
+ runId: record.runId,
25
+ workflowId: record.workflowId,
26
+ parent: this.parent,
27
+ at: record.updatedAt,
28
+ record,
29
+ });
30
+ }
31
+
32
+ private kindFor(
33
+ record: ConnectionInvocationRecord,
34
+ ): "connectionInvocationStarted" | "connectionInvocationCompleted" | "connectionInvocationFailed" | undefined {
35
+ if (record.status === "running" || record.status === "queued") {
36
+ return "connectionInvocationStarted";
37
+ }
38
+ if (record.status === "completed") {
39
+ return "connectionInvocationCompleted";
40
+ }
41
+ if (record.status === "failed") {
42
+ return "connectionInvocationFailed";
43
+ }
44
+ return undefined;
45
+ }
46
+ }
@@ -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";
@@ -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 ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
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
- nodeInstances.set(definition.id, this.createNode(definition));
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
  }
@@ -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 {