@codemation/core 0.2.3 → 0.4.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 (82) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +2 -0
  3. package/dist/{EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts → EngineRuntimeRegistration.types-DU6MsjU9.d.ts} +2 -2
  4. package/dist/{EngineWorkflowRunnerService-Dd4yD31l.d.cts → EngineWorkflowRunnerService-BBkL4VQF.d.cts} +2 -2
  5. package/dist/{InMemoryRunDataFactory-OUzDmAHt.d.cts → InMemoryRunDataFactory-CsYEMJK2.d.cts} +11 -3
  6. package/dist/{RunIntentService-Bkg4oYrM.d.cts → RunIntentService-BvlTpmEb.d.cts} +224 -237
  7. package/dist/{RunIntentService-BAKikN8h.d.ts → RunIntentService-zbTchO9T.d.ts} +305 -259
  8. package/dist/bootstrap/index.cjs +2 -2
  9. package/dist/bootstrap/index.d.cts +19 -7
  10. package/dist/bootstrap/index.d.ts +3 -3
  11. package/dist/bootstrap/index.js +2 -2
  12. package/dist/{bootstrap-DwS5S7s9.cjs → bootstrap-DHH2uo-W.cjs} +4 -2
  13. package/dist/bootstrap-DHH2uo-W.cjs.map +1 -0
  14. package/dist/{bootstrap-BD6CobHl.js → bootstrap-DbUlOl11.js} +4 -2
  15. package/dist/bootstrap-DbUlOl11.js.map +1 -0
  16. package/dist/{index-BDHCiN22.d.ts → index-CUt13qs1.d.ts} +85 -16
  17. package/dist/index.cjs +74 -12
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +131 -24
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +64 -13
  22. package/dist/index.js.map +1 -1
  23. package/dist/{runtime-Cy-3FTI_.js → runtime-BdH94eBR.js} +502 -123
  24. package/dist/runtime-BdH94eBR.js.map +1 -0
  25. package/dist/{runtime-ZJUpWmPH.cjs → runtime-feFn8OmG.cjs} +561 -122
  26. package/dist/runtime-feFn8OmG.cjs.map +1 -0
  27. package/dist/testing.cjs +40 -36
  28. package/dist/testing.cjs.map +1 -1
  29. package/dist/testing.d.cts +17 -26
  30. package/dist/testing.d.ts +17 -26
  31. package/dist/testing.js +40 -36
  32. package/dist/testing.js.map +1 -1
  33. package/dist/{workflowActivationPolicy-BzyzXLa_.cjs → workflowActivationPolicy-6V3OJD3N.cjs} +65 -19
  34. package/dist/workflowActivationPolicy-6V3OJD3N.cjs.map +1 -0
  35. package/dist/{workflowActivationPolicy-B8HzTk3o.js → workflowActivationPolicy-Td9HTOuD.js} +65 -19
  36. package/dist/workflowActivationPolicy-Td9HTOuD.js.map +1 -0
  37. package/package.json +2 -1
  38. package/src/ai/AgentConfigInspectorFactory.ts +4 -0
  39. package/src/ai/AgentMessageConfigNormalizerFactory.ts +7 -0
  40. package/src/ai/AgentToolFactory.ts +2 -2
  41. package/src/ai/AiHost.ts +11 -10
  42. package/src/ai/NodeBackedToolConfig.ts +1 -1
  43. package/src/authoring/defineNode.types.ts +144 -25
  44. package/src/authoring/index.ts +3 -1
  45. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
  46. package/src/contracts/emitPorts.ts +27 -0
  47. package/src/contracts/index.ts +3 -0
  48. package/src/contracts/itemMeta.ts +11 -0
  49. package/src/contracts/itemValue.ts +147 -0
  50. package/src/contracts/runtimeTypes.ts +39 -22
  51. package/src/contracts/workflowTypes.ts +26 -56
  52. package/src/execution/FanInMergeByOriginMerger.ts +67 -0
  53. package/src/execution/ItemValueResolver.ts +27 -0
  54. package/src/execution/NodeActivationRequestComposer.ts +25 -0
  55. package/src/execution/NodeActivationRequestInputPreparer.ts +57 -25
  56. package/src/execution/NodeExecutor.ts +199 -30
  57. package/src/execution/NodeOutputNormalizer.ts +90 -0
  58. package/src/execution/index.ts +2 -0
  59. package/src/index.ts +2 -0
  60. package/src/orchestration/NodeExecutionRequestHandlerService.ts +39 -18
  61. package/src/orchestration/RunContinuationService.ts +11 -17
  62. package/src/planning/CurrentStateFrontierPlanner.ts +20 -20
  63. package/src/planning/RunQueuePlanner.ts +56 -19
  64. package/src/planning/WorkflowTopologyPlanner.ts +57 -33
  65. package/src/testing/ItemHarnessNode.ts +4 -10
  66. package/src/testing/ItemHarnessNodeConfig.ts +7 -16
  67. package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
  68. package/src/testing/SubWorkflowRunnerTestNode.ts +28 -43
  69. package/src/testing/SwitchHarnessNode.ts +54 -0
  70. package/src/types/index.ts +3 -0
  71. package/src/workflow/dsl/ChainCursorResolver.ts +68 -23
  72. package/src/workflow/dsl/WorkflowBuilder.ts +3 -5
  73. package/src/workflow/dsl/workflowBuilderTypes.ts +5 -8
  74. package/src/workflowSnapshots/MissingRuntimeNode.ts +4 -4
  75. package/src/workflowSnapshots/MissingRuntimeNodeConfig.ts +2 -2
  76. package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +16 -7
  77. package/dist/bootstrap-BD6CobHl.js.map +0 -1
  78. package/dist/bootstrap-DwS5S7s9.cjs.map +0 -1
  79. package/dist/runtime-Cy-3FTI_.js.map +0 -1
  80. package/dist/runtime-ZJUpWmPH.cjs.map +0 -1
  81. package/dist/workflowActivationPolicy-B8HzTk3o.js.map +0 -1
  82. package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +0 -1
@@ -137,6 +137,74 @@ function chatModel(options = {}) {
137
137
  return InjectableRuntimeDecoratorComposer.compose("chatModel", options, import.meta.url);
138
138
  }
139
139
 
140
+ //#endregion
141
+ //#region src/contracts/itemValue.ts
142
+ const ITEM_VALUE_BRAND = Symbol.for("codemation.itemValue");
143
+ function itemValue(fn) {
144
+ return {
145
+ [ITEM_VALUE_BRAND]: true,
146
+ fn
147
+ };
148
+ }
149
+ function isItemValue(value) {
150
+ if (typeof value !== "object" || value === null) return false;
151
+ const v = value;
152
+ if (v[ITEM_VALUE_BRAND] === true) return true;
153
+ const keys = Object.keys(v);
154
+ if (keys.length === 1 && keys[0] === "fn" && typeof v.fn === "function") return true;
155
+ for (const sym of Object.getOwnPropertySymbols(v)) if (sym.description === "codemation.itemValue" && v[sym] === true) return true;
156
+ return false;
157
+ }
158
+ function containsItemValueInUnknown(value, seen = /* @__PURE__ */ new WeakSet()) {
159
+ if (isItemValue(value)) return true;
160
+ if (value === null || typeof value !== "object") return false;
161
+ if (seen.has(value)) return false;
162
+ seen.add(value);
163
+ if (Array.isArray(value)) return value.some((entry) => containsItemValueInUnknown(entry, seen));
164
+ for (const entry of Object.values(value)) if (containsItemValueInUnknown(entry, seen)) return true;
165
+ return false;
166
+ }
167
+ /**
168
+ * Deep-resolves {@link itemValue} leaves. Returns a new graph (does not mutate the original config object).
169
+ */
170
+ async function resolveItemValuesInUnknown(value, args, seen = /* @__PURE__ */ new WeakSet()) {
171
+ if (isItemValue(value)) return await Promise.resolve(value.fn(args));
172
+ if (value === null || typeof value !== "object") return value;
173
+ if (seen.has(value)) return value;
174
+ seen.add(value);
175
+ if (Array.isArray(value)) {
176
+ const out$1 = [];
177
+ for (let i = 0; i < value.length; i++) out$1.push(await resolveItemValuesInUnknown(value[i], args, seen));
178
+ return out$1;
179
+ }
180
+ const rec = value;
181
+ const entries = Object.entries(rec);
182
+ const proto = Object.getPrototypeOf(value);
183
+ if (proto !== Object.prototype && proto !== null && entries.length === 0) return value;
184
+ const out = Object.create(proto);
185
+ for (const [k, v] of entries) out[k] = await resolveItemValuesInUnknown(v, args, seen);
186
+ return out;
187
+ }
188
+ /**
189
+ * Clones runnable config (best-effort) so per-item {@link itemValue} resolution never mutates shared instances.
190
+ */
191
+ async function resolveItemValuesForExecution(config, nodeCtx, item, itemIndex, items) {
192
+ const ivArgs = {
193
+ item,
194
+ itemIndex,
195
+ items,
196
+ ctx: {
197
+ runId: nodeCtx.runId,
198
+ workflowId: nodeCtx.workflowId,
199
+ nodeId: nodeCtx.nodeId,
200
+ activationId: nodeCtx.activationId,
201
+ data: nodeCtx.data
202
+ }
203
+ };
204
+ if (!containsItemValueInUnknown(config)) return;
205
+ return await resolveItemValuesInUnknown(config, ivArgs);
206
+ }
207
+
140
208
  //#endregion
141
209
  //#region src/workflow/definition/ConnectionNodeIdFactory.ts
142
210
  /**
@@ -564,6 +632,64 @@ var ActivationEnqueueService = class {
564
632
  }
565
633
  };
566
634
 
635
+ //#endregion
636
+ //#region src/contracts/itemMeta.ts
637
+ /**
638
+ * Reads `meta._cm.originIndex` when present (used for fan-in merge-by-origin and Merge routing).
639
+ */
640
+ function getOriginIndexFromItem(item) {
641
+ const v = (item.meta?._cm)?.originIndex;
642
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
643
+ }
644
+
645
+ //#endregion
646
+ //#region src/execution/FanInMergeByOriginMerger.ts
647
+ /**
648
+ * Default fan-in: combine multi-port {@link NodeInputsByPort} into one {@link Items} batch for per-item nodes.
649
+ *
650
+ * This is used when a single-input per-item node has multiple inbound edges (for example, branch reconverge
651
+ * after an `If` / `Switch`). The default behavior is **append / union** (preserving item payloads) with a
652
+ * deterministic order:
653
+ *
654
+ * - When router origin metadata exists (`meta._cm.originIndex`), items are sorted by origin index so the
655
+ * downstream batch preserves original ordering across branches.
656
+ * - Otherwise, items are appended by port-key order, preserving each port's local order.
657
+ */
658
+ var FanInMergeByOriginMerger = class {
659
+ merge(inputsByPort) {
660
+ const portKeys = Object.keys(inputsByPort).sort();
661
+ if (portKeys.length === 0) return [];
662
+ if (portKeys.length === 1) return [...inputsByPort[portKeys[0]] ?? []];
663
+ const entries = [];
664
+ let anyOrigin = false;
665
+ for (let p = 0; p < portKeys.length; p++) {
666
+ const portKey = portKeys[p];
667
+ const items = inputsByPort[portKey] ?? [];
668
+ for (let i = 0; i < items.length; i++) {
669
+ const item = items[i];
670
+ const originIndex = getOriginIndexFromItem(item);
671
+ if (originIndex !== void 0) anyOrigin = true;
672
+ entries.push({
673
+ portKey,
674
+ portIndex: i,
675
+ item,
676
+ originIndex
677
+ });
678
+ }
679
+ }
680
+ if (!anyOrigin) return entries.map((e) => e.item);
681
+ const missingOriginRank = Number.MAX_SAFE_INTEGER;
682
+ return entries.slice().sort((a, b) => {
683
+ const ao = a.originIndex ?? missingOriginRank;
684
+ const bo = b.originIndex ?? missingOriginRank;
685
+ if (ao !== bo) return ao - bo;
686
+ const pk = a.portKey.localeCompare(b.portKey);
687
+ if (pk !== 0) return pk;
688
+ return a.portIndex - b.portIndex;
689
+ }).map((e) => e.item);
690
+ }
691
+ };
692
+
567
693
  //#endregion
568
694
  //#region src/execution/NodeInputContractError.ts
569
695
  var NodeInputContractError = class extends Error {
@@ -579,45 +705,58 @@ var NodeInputContractError = class extends Error {
579
705
  //#endregion
580
706
  //#region src/execution/NodeActivationRequestInputPreparer.ts
581
707
  /**
582
- * Maps and validates per-item inputs for {@link ItemNode} before enqueue persistence.
708
+ * Validates per-item inputs for {@link RunnableNode} before enqueue persistence (Zod on `item.json`).
709
+ * Does not rewrite `item.json` (wire stays as emitted upstream; engine passes parsed input via `execute` args).
710
+ * Converts multi-input activations into a single-input batch when the node is per-item only (engine fan-in).
583
711
  */
584
712
  var NodeActivationRequestInputPreparer = class {
713
+ fanInMerger = new FanInMergeByOriginMerger();
585
714
  constructor(workflowNodeInstanceFactory) {
586
715
  this.workflowNodeInstanceFactory = workflowNodeInstanceFactory;
587
716
  }
588
717
  async prepare(request) {
589
- if (request.kind !== "single") return request;
718
+ if (request.kind === "multi") return await this.prepareMulti(request);
719
+ return await this.prepareSingle(request);
720
+ }
721
+ async prepareMulti(request) {
722
+ const nodeInstance = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
723
+ if (!this.hasRunnableExecute(nodeInstance) || this.hasExecuteMulti(nodeInstance) || this.isTriggerNode(nodeInstance)) return request;
724
+ const merged = this.fanInMerger.merge(request.inputsByPort);
725
+ const single = {
726
+ ...request,
727
+ kind: "single",
728
+ input: merged
729
+ };
730
+ return await this.prepareSingle(single);
731
+ }
732
+ async prepareSingle(request) {
590
733
  const nodeInstance = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
591
- if (!this.hasExecuteOne(nodeInstance)) return request;
734
+ if (!this.hasRunnableExecute(nodeInstance) || this.isTriggerNode(nodeInstance)) return request;
592
735
  const inputSchema = this.resolveInputSchema(nodeInstance, request.ctx.config);
593
- const config = request.ctx.config;
594
- const mappedItems = [];
595
- for (let i = 0; i < request.input.length; i++) {
596
- const item = request.input[i];
736
+ const inputBatch = request.input ?? [];
737
+ for (let i = 0; i < inputBatch.length; i++) {
738
+ const item = inputBatch[i];
597
739
  try {
598
- const mappedRaw = config.mapInput ? await Promise.resolve(config.mapInput({
599
- item,
600
- itemIndex: i,
601
- items: request.input,
602
- ctx: request.ctx
603
- })) : item.json;
604
- const parsed = inputSchema.parse(mappedRaw);
605
- mappedItems.push({
606
- ...item,
607
- json: parsed
608
- });
740
+ if (Array.isArray(item.json)) throw new Error("Item JSON must not be a top-level array");
741
+ inputSchema.parse(item.json);
609
742
  } catch (cause) {
610
743
  const message = this.formatContractFailure(cause);
611
744
  throw new NodeInputContractError(`Node ${request.nodeId} activation ${request.activationId}: input contract failed: ${message}`, request.nodeId, request.activationId, cause);
612
745
  }
613
746
  }
614
- return {
747
+ return request.input === void 0 ? {
615
748
  ...request,
616
- input: mappedItems
617
- };
749
+ input: inputBatch
750
+ } : request;
751
+ }
752
+ isTriggerNode(nodeInstance) {
753
+ return typeof nodeInstance === "object" && nodeInstance !== null && nodeInstance.kind === "trigger";
618
754
  }
619
- hasExecuteOne(nodeInstance) {
620
- return typeof nodeInstance === "object" && nodeInstance !== null && typeof nodeInstance.executeOne === "function";
755
+ hasRunnableExecute(nodeInstance) {
756
+ return typeof nodeInstance === "object" && nodeInstance !== null && nodeInstance.kind === "node" && typeof nodeInstance.execute === "function";
757
+ }
758
+ hasExecuteMulti(nodeInstance) {
759
+ return typeof nodeInstance.executeMulti === "function";
621
760
  }
622
761
  resolveInputSchema(nodeInstance, config) {
623
762
  const fromInstance = nodeInstance.inputSchema;
@@ -774,6 +913,87 @@ var InProcessRetryRunner = class InProcessRetryRunner {
774
913
  }
775
914
  };
776
915
 
916
+ //#endregion
917
+ //#region src/execution/ItemValueResolver.ts
918
+ /**
919
+ * Resolves {@link import("../contracts/itemValue").ItemValue} leaves on runnable config before {@link RunnableNode.execute}.
920
+ */
921
+ var ItemValueResolver = class {
922
+ async resolveConfigForItem(ctx, item, itemIndex, items) {
923
+ if (!ctx) throw new Error("ItemValueResolver.resolveConfigForItem: ctx is required");
924
+ const resolvedConfig = await resolveItemValuesForExecution(ctx.config, ctx, item, itemIndex, items);
925
+ const merged = resolvedConfig !== void 0 && resolvedConfig !== null ? resolvedConfig : ctx.config;
926
+ if (merged === void 0 || merged === null) return ctx;
927
+ return {
928
+ ...ctx,
929
+ config: merged
930
+ };
931
+ }
932
+ };
933
+
934
+ //#endregion
935
+ //#region src/contracts/emitPorts.ts
936
+ const EMIT_PORTS_BRAND = Symbol.for("codemation.emitPorts");
937
+ function emitPorts(ports) {
938
+ return {
939
+ [EMIT_PORTS_BRAND]: true,
940
+ ports
941
+ };
942
+ }
943
+ function isPortsEmission(value) {
944
+ return typeof value === "object" && value !== null && EMIT_PORTS_BRAND in value && value[EMIT_PORTS_BRAND] === true;
945
+ }
946
+ function isUnbrandedPortsEmissionShape(value) {
947
+ return typeof value === "object" && value !== null && "ports" in value && !isPortsEmission(value);
948
+ }
949
+
950
+ //#endregion
951
+ //#region src/execution/NodeOutputNormalizer.ts
952
+ var NodeOutputNormalizer = class {
953
+ normalizeExecuteResult(args) {
954
+ const { baseItem, raw, carry } = args;
955
+ if (isPortsEmission(raw)) return this.emitPortsToOutputs(baseItem, raw, carry);
956
+ if (isUnbrandedPortsEmissionShape(raw)) throw new Error("execute() returned an unbranded `{ ports: ... }` object. Use emitPorts(...) for multi-port runnable outputs.");
957
+ if (Array.isArray(raw)) return this.arrayFanOutToMain(baseItem, raw, carry);
958
+ if (this.isItemLike(raw)) return { main: [this.applyLineage(baseItem, raw, carry)] };
959
+ return { main: [this.applyLineage(baseItem, { json: raw }, carry)] };
960
+ }
961
+ arrayFanOutToMain(baseItem, raw, carry) {
962
+ for (const el of raw) if (Array.isArray(el)) throw new Error("execute() fan-out arrays must contain only non-array JSON elements (nested arrays belong inside objects).");
963
+ return { main: raw.map((json) => this.applyLineage(baseItem, { json }, carry)) };
964
+ }
965
+ emitPortsToOutputs(baseItem, emission, carry) {
966
+ const out = {};
967
+ for (const [port, payload] of Object.entries(emission.ports)) {
968
+ if (payload === void 0) continue;
969
+ out[port] = this.normalizePortPayload(baseItem, payload, carry);
970
+ }
971
+ return out;
972
+ }
973
+ normalizePortPayload(baseItem, payload, carry) {
974
+ if (payload.length === 0) return [];
975
+ const el0 = payload[0];
976
+ if (this.isItemLike(el0)) return payload.map((it) => this.applyLineage(baseItem, it, carry));
977
+ return payload.map((json) => this.applyLineage(baseItem, { json }, carry));
978
+ }
979
+ isItemLike(value) {
980
+ return typeof value === "object" && value !== null && "json" in value;
981
+ }
982
+ applyLineage(baseItem, next, carry) {
983
+ if (carry === "carryThrough") return {
984
+ ...baseItem,
985
+ ...next,
986
+ json: next.json
987
+ };
988
+ return {
989
+ json: next.json,
990
+ ...next.binary ? { binary: next.binary } : {},
991
+ ...next.meta ? { meta: next.meta } : {},
992
+ ...next.paired ? { paired: next.paired } : {}
993
+ };
994
+ }
995
+ };
996
+
777
997
  //#endregion
778
998
  //#region src/execution/InProcessRetryRunnerFactory.ts
779
999
  var InProcessRetryRunnerFactory = class {
@@ -813,6 +1033,21 @@ var NodeActivationRequestComposer = class {
813
1033
  ctx
814
1034
  };
815
1035
  }
1036
+ createMultiFromDefinitionWithActivation(args) {
1037
+ const ctx = this.createNodeExecutionContext(args, args.definition, args.activationId);
1038
+ return {
1039
+ kind: "multi",
1040
+ runId: args.runId,
1041
+ activationId: args.activationId,
1042
+ workflowId: args.workflowId,
1043
+ nodeId: args.definition.id,
1044
+ parent: args.parent,
1045
+ executionOptions: args.executionOptions,
1046
+ batchId: args.batchId,
1047
+ inputsByPort: args.inputsByPort,
1048
+ ctx
1049
+ };
1050
+ }
816
1051
  createFromPlannedActivation(args) {
817
1052
  const activationId = this.activationIdFactory.makeActivationId();
818
1053
  const ctx = this.createNodeExecutionContext(args, args.nodeDefinition, activationId);
@@ -860,49 +1095,152 @@ var NodeActivationRequestComposer = class {
860
1095
  //#endregion
861
1096
  //#region src/execution/NodeExecutor.ts
862
1097
  var NodeExecutor = class {
863
- constructor(nodeInstanceFactory, retryRunner) {
1098
+ fanInMerger = new FanInMergeByOriginMerger();
1099
+ outputNormalizer = new NodeOutputNormalizer();
1100
+ itemValueResolver;
1101
+ constructor(nodeInstanceFactory, retryRunner, itemValueResolver) {
864
1102
  this.nodeInstanceFactory = nodeInstanceFactory;
865
1103
  this.retryRunner = retryRunner;
1104
+ this.itemValueResolver = itemValueResolver ?? new ItemValueResolver();
866
1105
  }
867
1106
  async execute(request) {
868
1107
  const policy = request.ctx.config.retryPolicy;
869
1108
  return await this.retryRunner.run(policy, async () => {
870
1109
  const nodeInstance = this.nodeInstanceFactory.createByType(request.ctx.config.type);
871
- if (request.kind === "multi") return await this.executeMultiInputNode(request, nodeInstance);
1110
+ if (request.kind === "multi") return await this.executeMultiInputActivation(request, nodeInstance);
872
1111
  return await this.executeSingleInputNode(request, nodeInstance);
873
1112
  });
874
1113
  }
875
- async executeMultiInputNode(request, node$1) {
1114
+ async executeMultiInputActivation(request, node$1) {
876
1115
  const multiInputNode = node$1;
877
- if (typeof multiInputNode.executeMulti !== "function") throw new Error(`Node ${request.nodeId} does not support executeMulti but received multi-input activation`);
878
- return await multiInputNode.executeMulti(request.inputsByPort, request.ctx);
1116
+ if (typeof multiInputNode.executeMulti === "function") {
1117
+ const raw = await multiInputNode.executeMulti(request.inputsByPort, request.ctx);
1118
+ this.assertNoPortEnvelopeBypass(request.nodeId, raw, "executeMulti()");
1119
+ return raw;
1120
+ }
1121
+ if (this.isRunnableNode(node$1)) {
1122
+ const merged = this.fanInMerger.merge(request.inputsByPort);
1123
+ const single = {
1124
+ ...request,
1125
+ kind: "single",
1126
+ input: merged
1127
+ };
1128
+ return await this.executeRunnableActivation(single, node$1);
1129
+ }
1130
+ throw new Error(`Node ${request.nodeId} does not support executeMulti or RunnableNode.execute but received multi-input activation`);
879
1131
  }
880
1132
  async executeSingleInputNode(request, node$1) {
881
- if (this.hasExecuteOne(node$1)) return await this.executeItemNode(request, node$1);
882
- const singleInputNode = node$1;
883
- if (typeof singleInputNode.execute !== "function") throw new Error(`Node ${request.nodeId} does not support execute but received single-input activation`);
884
- return await singleInputNode.execute(request.input, request.ctx);
1133
+ if (this.isTriggerNode(node$1)) {
1134
+ const raw = await node$1.execute(request.input, request.ctx);
1135
+ this.assertNoPortEnvelopeBypass(request.nodeId, raw, "trigger execute()");
1136
+ return raw;
1137
+ }
1138
+ if (this.isRunnableNode(node$1)) return await this.executeRunnableActivation(request, node$1);
1139
+ if (this.hasExecuteMulti(node$1)) return await this.executeMultiInputActivation(this.asMultiFromSingleActivation(request), node$1);
1140
+ throw new Error(`Node ${request.nodeId} does not support trigger or RunnableNode execution`);
885
1141
  }
886
- hasExecuteOne(node$1) {
887
- return typeof node$1 === "object" && node$1 !== null && typeof node$1.executeOne === "function";
1142
+ isTriggerNode(node$1) {
1143
+ return typeof node$1 === "object" && node$1 !== null && node$1.kind === "trigger";
888
1144
  }
889
- async executeItemNode(request, node$1) {
890
- const out = [];
891
- for (let i = 0; i < request.input.length; i++) {
892
- const item = request.input[i];
893
- const outputJson = await Promise.resolve(node$1.executeOne({
894
- input: item.json,
1145
+ isRunnableNode(node$1) {
1146
+ return typeof node$1 === "object" && node$1 !== null && node$1.kind === "node" && typeof node$1.execute === "function";
1147
+ }
1148
+ hasExecuteMulti(node$1) {
1149
+ return typeof node$1?.executeMulti === "function";
1150
+ }
1151
+ asMultiFromSingleActivation(request) {
1152
+ return {
1153
+ kind: "multi",
1154
+ runId: request.runId,
1155
+ activationId: request.activationId,
1156
+ workflowId: request.workflowId,
1157
+ nodeId: request.nodeId,
1158
+ parent: request.parent,
1159
+ executionOptions: request.executionOptions,
1160
+ batchId: request.batchId,
1161
+ ctx: request.ctx,
1162
+ inputsByPort: { in: request.input ?? [] }
1163
+ };
1164
+ }
1165
+ async executeRunnableActivation(request, node$1) {
1166
+ const runnableConfig = request.ctx.config;
1167
+ const carry = this.resolveLineageCarry(node$1, runnableConfig);
1168
+ const inputSchema = this.resolveInputSchema(node$1, runnableConfig);
1169
+ const inputBatch = request.input ?? [];
1170
+ if (inputBatch.length === 0 && runnableConfig.emptyBatchExecution === "runOnce") {
1171
+ const syntheticItem = { json: {} };
1172
+ const parsed = inputSchema.parse(syntheticItem.json);
1173
+ const runnableCtx = request.ctx;
1174
+ const resolvedCtx = await this.itemValueResolver.resolveConfigForItem(runnableCtx, syntheticItem, 0, inputBatch);
1175
+ const args = {
1176
+ input: parsed,
1177
+ item: syntheticItem,
1178
+ itemIndex: 0,
1179
+ items: inputBatch,
1180
+ ctx: this.pickExecutionContext(runnableCtx, resolvedCtx)
1181
+ };
1182
+ const raw = await Promise.resolve(node$1.execute(args));
1183
+ return this.outputNormalizer.normalizeExecuteResult({
1184
+ baseItem: syntheticItem,
1185
+ raw,
1186
+ carry
1187
+ });
1188
+ }
1189
+ const byPort = {};
1190
+ for (let i = 0; i < inputBatch.length; i++) {
1191
+ const item = inputBatch[i];
1192
+ this.assertItemJsonNotTopLevelArray(request.nodeId, item);
1193
+ const parsed = inputSchema.parse(item.json);
1194
+ const runnableCtx = request.ctx;
1195
+ const resolvedCtx = await this.itemValueResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
1196
+ const ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
1197
+ const args = {
1198
+ input: parsed,
895
1199
  item,
896
1200
  itemIndex: i,
897
- items: request.input,
898
- ctx: request.ctx
899
- }));
900
- out.push({
901
- ...item,
902
- json: outputJson
1201
+ items: inputBatch,
1202
+ ctx
1203
+ };
1204
+ const raw = await Promise.resolve(node$1.execute(args));
1205
+ const normalized = this.outputNormalizer.normalizeExecuteResult({
1206
+ baseItem: item,
1207
+ raw,
1208
+ carry
903
1209
  });
1210
+ for (const [port, batch] of Object.entries(normalized)) {
1211
+ if (!batch || batch.length === 0) continue;
1212
+ const list = byPort[port] ?? [];
1213
+ list.push(...batch);
1214
+ byPort[port] = list;
1215
+ }
904
1216
  }
905
- return { main: out };
1217
+ return byPort;
1218
+ }
1219
+ /** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
1220
+ pickExecutionContext(runnableCtx, resolvedCtx) {
1221
+ if (resolvedCtx != null && resolvedCtx.config != null) return resolvedCtx;
1222
+ return runnableCtx;
1223
+ }
1224
+ resolveInputSchema(nodeInstance, config) {
1225
+ const fromInstance = nodeInstance.inputSchema;
1226
+ if (fromInstance && typeof fromInstance.parse === "function") return fromInstance;
1227
+ const fromConfig = config.inputSchema;
1228
+ if (fromConfig && typeof fromConfig.parse === "function") return fromConfig;
1229
+ return z.unknown();
1230
+ }
1231
+ assertItemJsonNotTopLevelArray(nodeId, item) {
1232
+ if (Array.isArray(item.json)) throw new Error(`Node ${nodeId}: item.json must not be a top-level JSON array`);
1233
+ }
1234
+ assertNoPortEnvelopeBypass(nodeId, value, methodName) {
1235
+ if (isPortsEmission(value)) throw new Error(`Node ${nodeId}: ${methodName} must return NodeOutputs, not emitPorts(...).`);
1236
+ if (isUnbrandedPortsEmissionShape(value)) throw new Error(`Node ${nodeId}: ${methodName} returned an unbranded \`{ ports: ... }\` object. Return NodeOutputs instead.`);
1237
+ }
1238
+ resolveLineageCarry(node$1, config) {
1239
+ if (config.lineageCarry) return config.lineageCarry;
1240
+ const base = config;
1241
+ const declared = base.declaredOutputPorts;
1242
+ if ((declared && declared.length > 0 ? [...new Set([...declared, ...base.nodeErrorHandler ? ["error"] : []])] : base.nodeErrorHandler ? ["main", "error"] : ["main"]).length > 1) return "carryThrough";
1243
+ return "emitOnly";
906
1244
  }
907
1245
  };
908
1246
 
@@ -980,8 +1318,8 @@ var MissingRuntimeFallbacks = class {
980
1318
  var MissingRuntimeNode = class {
981
1319
  kind = "node";
982
1320
  outputPorts = ["main"];
983
- async execute(items) {
984
- return { main: items };
1321
+ execute(args) {
1322
+ return args.item;
985
1323
  }
986
1324
  };
987
1325
 
@@ -1170,6 +1508,7 @@ var WorkflowSnapshotCodec = class {
1170
1508
  }
1171
1509
  restoreNonSerializableProperties(liveRecord, hydrated) {
1172
1510
  for (const [key, value] of Object.entries(liveRecord)) if (typeof value === "function" || typeof value === "symbol") hydrated[key] = value;
1511
+ for (const sym of Object.getOwnPropertySymbols(liveRecord)) hydrated[sym] = liveRecord[sym];
1173
1512
  }
1174
1513
  restoreTypeProperty(record) {
1175
1514
  const tokenId = typeof record.tokenId === "string" ? record.tokenId : void 0;
@@ -1189,7 +1528,10 @@ var WorkflowSnapshotCodec = class {
1189
1528
  }
1190
1529
  asRecord(value) {
1191
1530
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1192
- return { ...value };
1531
+ const record = value;
1532
+ const out = { ...record };
1533
+ for (const sym of Object.getOwnPropertySymbols(value)) out[sym] = record[sym];
1534
+ return out;
1193
1535
  }
1194
1536
  };
1195
1537
 
@@ -1566,19 +1908,6 @@ var WorkflowTopology = class WorkflowTopology {
1566
1908
  const classifier = WorkflowExecutableNodeClassifierFactory.create(wf);
1567
1909
  const defs = /* @__PURE__ */ new Map();
1568
1910
  for (const n of wf.nodes) if (classifier.isExecutableNodeId(n.id)) defs.set(n.id, n);
1569
- const outgoing = /* @__PURE__ */ new Map();
1570
- for (const e of wf.edges) {
1571
- if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
1572
- const list = outgoing.get(e.from.nodeId) ?? [];
1573
- list.push({
1574
- output: e.from.output,
1575
- to: {
1576
- nodeId: e.to.nodeId,
1577
- input: e.to.input
1578
- }
1579
- });
1580
- outgoing.set(e.from.nodeId, list);
1581
- }
1582
1911
  const incomingByNode = /* @__PURE__ */ new Map();
1583
1912
  for (const e of wf.edges) {
1584
1913
  if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
@@ -1588,21 +1917,51 @@ var WorkflowTopology = class WorkflowTopology {
1588
1917
  nodeId: e.from.nodeId,
1589
1918
  output: e.from.output
1590
1919
  },
1591
- input: e.to.input
1920
+ input: e.to.input,
1921
+ collectKey: e.to.input
1592
1922
  });
1593
1923
  incomingByNode.set(e.to.nodeId, list);
1594
1924
  }
1595
- const expected = /* @__PURE__ */ new Map();
1596
- for (const [toNodeId, inputs] of incomingByNode.entries()) {
1925
+ const duplicateInputCounts = /* @__PURE__ */ new Map();
1926
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1597
1927
  const counts = /* @__PURE__ */ new Map();
1598
- for (const edge of inputs) counts.set(edge.input, (counts.get(edge.input) ?? 0) + 1);
1599
- for (const [k, n] of counts.entries()) if (n > 1) throw new Error(`Node ${toNodeId} has multiple edges into input '${k}'. Use a Merge node upstream.`);
1928
+ for (const edge of edges) counts.set(edge.input, (counts.get(edge.input) ?? 0) + 1);
1929
+ duplicateInputCounts.set(toNodeId, counts);
1930
+ }
1931
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1932
+ const counts = duplicateInputCounts.get(toNodeId) ?? /* @__PURE__ */ new Map();
1933
+ for (let i = 0; i < edges.length; i++) {
1934
+ const edge = edges[i];
1935
+ const collectKey = (counts.get(edge.input) ?? 0) > 1 ? `${edge.from.nodeId}:${edge.from.output}` : edge.input;
1936
+ edges[i] = {
1937
+ ...edge,
1938
+ collectKey
1939
+ };
1940
+ }
1941
+ }
1942
+ const outgoing = /* @__PURE__ */ new Map();
1943
+ for (const e of wf.edges) {
1944
+ if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
1945
+ const collectKey = ((duplicateInputCounts.get(e.to.nodeId) ?? /* @__PURE__ */ new Map()).get(e.to.input) ?? 0) > 1 ? `${e.from.nodeId}:${e.from.output}` : e.to.input;
1946
+ const list = outgoing.get(e.from.nodeId) ?? [];
1947
+ list.push({
1948
+ output: e.from.output,
1949
+ to: {
1950
+ nodeId: e.to.nodeId,
1951
+ input: e.to.input,
1952
+ collectKey
1953
+ }
1954
+ });
1955
+ outgoing.set(e.from.nodeId, list);
1956
+ }
1957
+ const expected = /* @__PURE__ */ new Map();
1958
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1600
1959
  const order = [];
1601
1960
  const seen = /* @__PURE__ */ new Set();
1602
- for (const edge of inputs) {
1603
- if (seen.has(edge.input)) continue;
1604
- seen.add(edge.input);
1605
- order.push(edge.input);
1961
+ for (const edge of edges) {
1962
+ if (seen.has(edge.collectKey)) continue;
1963
+ seen.add(edge.collectKey);
1964
+ order.push(edge.collectKey);
1606
1965
  }
1607
1966
  expected.set(toNodeId, order);
1608
1967
  }
@@ -2108,6 +2467,11 @@ var RunContinuationService = class {
2108
2467
  batchId
2109
2468
  });
2110
2469
  const next = planner.nextActivation(queue);
2470
+ const finishedAt = completedSnapshot.finishedAt ?? completedSnapshot.updatedAt;
2471
+ const mergedSnapshots = {
2472
+ ...args.state.nodeSnapshotsByNodeId ?? {},
2473
+ [args.args.nodeId]: completedSnapshot
2474
+ };
2111
2475
  if (!next) {
2112
2476
  const lastNodeId = WorkflowExecutableNodeClassifierFactory.create(args.workflow).lastExecutableNodeIdInDefinitionOrder(args.workflow);
2113
2477
  const outputs = data.getOutputItems(lastNodeId, "main");
@@ -2117,11 +2481,8 @@ var RunContinuationService = class {
2117
2481
  status: "completed",
2118
2482
  queue: [],
2119
2483
  outputsByNode: data.dump(),
2120
- nodeSnapshotsByNodeId: {
2121
- ...args.state.nodeSnapshotsByNodeId ?? {},
2122
- [args.args.nodeId]: completedSnapshot
2123
- },
2124
- finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt
2484
+ nodeSnapshotsByNodeId: mergedSnapshots,
2485
+ finishedAtIso: finishedAt
2125
2486
  });
2126
2487
  await this.workflowExecutionRepository.save(completedState);
2127
2488
  await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
@@ -2156,11 +2517,8 @@ var RunContinuationService = class {
2156
2517
  status: "failed",
2157
2518
  queue: queue.map((q) => ({ ...q })),
2158
2519
  outputsByNode: data.dump(),
2159
- nodeSnapshotsByNodeId: {
2160
- ...args.state.nodeSnapshotsByNodeId ?? {},
2161
- [args.args.nodeId]: completedSnapshot
2162
- },
2163
- finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt
2520
+ nodeSnapshotsByNodeId: mergedSnapshots,
2521
+ finishedAtIso: finishedAt
2164
2522
  });
2165
2523
  await this.workflowExecutionRepository.save(failedState);
2166
2524
  await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
@@ -2211,10 +2569,6 @@ var RunContinuationService = class {
2211
2569
  executionOptions: args.state.executionOptions,
2212
2570
  nodeDefinition: nextDefinition
2213
2571
  });
2214
- const mergedSnapshots = {
2215
- ...args.state.nodeSnapshotsByNodeId ?? {},
2216
- [args.args.nodeId]: completedSnapshot
2217
- };
2218
2572
  try {
2219
2573
  const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
2220
2574
  runId: args.state.runId,
@@ -2245,7 +2599,6 @@ var RunContinuationService = class {
2245
2599
  return result;
2246
2600
  } catch (cause) {
2247
2601
  const error = cause instanceof Error ? cause : new Error(String(cause));
2248
- const finishedAt = completedSnapshot.finishedAt ?? completedSnapshot.updatedAt;
2249
2602
  const result = await this.terminateRunAfterActivationEnqueueRejected({
2250
2603
  wf: args.workflow,
2251
2604
  state: args.state,
@@ -2533,7 +2886,7 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2533
2886
  const frontierNodeIds = [];
2534
2887
  for (const nodeId of this.topology.defsById.keys()) {
2535
2888
  if (!requiredNodeIds.has(nodeId) || this.isNodeSatisfied(currentState, nodeId)) continue;
2536
- if ((this.topology.incomingByNode.get(nodeId) ?? []).every((edge) => this.isEdgeSatisfied(currentState, nodeId, edge.input))) frontierNodeIds.push(nodeId);
2889
+ if ((this.topology.incomingByNode.get(nodeId) ?? []).every((edge) => this.isEdgeSatisfied(currentState, nodeId, edge.collectKey))) frontierNodeIds.push(nodeId);
2537
2890
  }
2538
2891
  return frontierNodeIds;
2539
2892
  }
@@ -2551,13 +2904,13 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2551
2904
  if (requiredNodeIds.has(nodeId)) return;
2552
2905
  if (this.isNodeSatisfied(currentState, nodeId) && !this.isNodeSatisfiedByOutputsOnly(currentState, nodeId)) return;
2553
2906
  requiredNodeIds.add(nodeId);
2554
- for (const edge of this.topology.incomingByNode.get(nodeId) ?? []) if (!this.isEdgeSatisfied(currentState, nodeId, edge.input) || this.isNodeSatisfiedByOutputsOnly(currentState, edge.from.nodeId)) this.collectRequiredNode(requiredNodeIds, currentState, edge.from.nodeId);
2907
+ for (const edge of this.topology.incomingByNode.get(nodeId) ?? []) if (!this.isEdgeSatisfied(currentState, nodeId, edge.collectKey) || this.isNodeSatisfiedByOutputsOnly(currentState, edge.from.nodeId)) this.collectRequiredNode(requiredNodeIds, currentState, edge.from.nodeId);
2555
2908
  }
2556
2909
  buildFrontierQueue(nodeId, currentState) {
2557
2910
  const incomingEdges = this.topology.incomingByNode.get(nodeId) ?? [];
2558
2911
  if (incomingEdges.length === 0) return [];
2559
2912
  const expectedInputs = this.topology.expectedInputsByNode.get(nodeId) ?? [];
2560
- if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") {
2913
+ if (this.usesCollect(nodeId)) {
2561
2914
  const received = {};
2562
2915
  for (const input$2 of expectedInputs) received[input$2] = this.resolveInput(currentState, nodeId, input$2);
2563
2916
  return [{
@@ -2571,7 +2924,7 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2571
2924
  }];
2572
2925
  }
2573
2926
  const input$1 = expectedInputs[0] ?? "in";
2574
- const incomingEdge = incomingEdges.find((edge) => edge.input === input$1);
2927
+ const incomingEdge = incomingEdges.find((edge) => edge.collectKey === input$1);
2575
2928
  return [{
2576
2929
  nodeId,
2577
2930
  input: this.resolveInput(currentState, nodeId, input$1),
@@ -2591,16 +2944,17 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2591
2944
  isNodeSatisfiedByOutputsOnly(currentState, nodeId) {
2592
2945
  return this.hasOutputs(currentState, nodeId) && !this.hasCompletedSnapshot(currentState, nodeId);
2593
2946
  }
2594
- isEdgeSatisfied(currentState, nodeId, input$1) {
2595
- const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.input === input$1);
2947
+ isEdgeSatisfied(currentState, nodeId, collectKey) {
2948
+ const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.collectKey === collectKey);
2596
2949
  if (!incomingEdge) return false;
2597
- if (!this.hasOutputPort(currentState, incomingEdge.from.nodeId, incomingEdge.from.output)) return false;
2950
+ const fromNodeId = incomingEdge.from.nodeId;
2951
+ if (!this.isNodeSatisfied(currentState, fromNodeId)) return false;
2598
2952
  if (this.usesCollect(nodeId)) return true;
2599
- if (this.resolveOutputItems(currentState, incomingEdge.from.nodeId, incomingEdge.from.output).length > 0) return true;
2600
- return this.shouldContinueAfterEmptyOutputFromSource(incomingEdge.from.nodeId);
2953
+ if (this.resolveOutputItems(currentState, fromNodeId, incomingEdge.from.output).length > 0) return true;
2954
+ return this.shouldContinueAfterEmptyOutputFromSource(fromNodeId);
2601
2955
  }
2602
- resolveInput(currentState, nodeId, input$1) {
2603
- const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.input === input$1);
2956
+ resolveInput(currentState, nodeId, collectKey) {
2957
+ const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.collectKey === collectKey);
2604
2958
  if (!incomingEdge) return [];
2605
2959
  return this.resolveOutputItems(currentState, incomingEdge.from.nodeId, incomingEdge.from.output);
2606
2960
  }
@@ -2611,17 +2965,13 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2611
2965
  const snapshot = currentState.nodeSnapshotsByNodeId[nodeId];
2612
2966
  return snapshot?.status === "completed" || snapshot?.status === "skipped";
2613
2967
  }
2614
- hasOutputPort(currentState, nodeId, output$1) {
2615
- const outputs = currentState.outputsByNode[nodeId];
2616
- if (!outputs) return false;
2617
- return Object.prototype.hasOwnProperty.call(outputs, output$1);
2618
- }
2619
2968
  resolveOutputItems(currentState, nodeId, output$1) {
2620
2969
  return currentState.outputsByNode[nodeId]?.[output$1] ?? [];
2621
2970
  }
2622
2971
  usesCollect(nodeId) {
2623
2972
  const expectedInputs = this.topology.expectedInputsByNode.get(nodeId) ?? [];
2624
- return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
2973
+ if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") return true;
2974
+ return (this.topology.incomingByNode.get(nodeId) ?? []).length > 1;
2625
2975
  }
2626
2976
  shouldContinueAfterEmptyOutputFromSource(nodeId) {
2627
2977
  const definition = this.topology.defsById.get(nodeId);
@@ -3513,7 +3863,6 @@ var NodeExecutionRequestHandlerService = class {
3513
3863
  const resolvedParent = request.parent ?? state.parent;
3514
3864
  const data = this.runDataFactory.create(state.outputsByNode);
3515
3865
  const limits = this.resolveEngineLimitsFromState(state);
3516
- const persistedInput = pendingExecution.inputsByPort.in ?? request.input;
3517
3866
  const base = this.runExecutionContextFactory.create({
3518
3867
  runId: state.runId,
3519
3868
  workflowId: state.workflowId,
@@ -3525,7 +3874,25 @@ var NodeExecutionRequestHandlerService = class {
3525
3874
  data,
3526
3875
  nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, resolvedParent)
3527
3876
  });
3528
- const activationRequest = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
3877
+ const inputsByPort = pendingExecution.inputsByPort;
3878
+ const portKeys = Object.keys(inputsByPort);
3879
+ const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
3880
+ const batchId = pendingExecution.batchId ?? "batch_1";
3881
+ const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
3882
+ activationId: request.activationId,
3883
+ runId: request.runId,
3884
+ workflowId: request.workflowId,
3885
+ parent: resolvedParent,
3886
+ executionOptions: request.executionOptions ?? state.executionOptions,
3887
+ base,
3888
+ data,
3889
+ definition: {
3890
+ id: definition.id,
3891
+ config: definition.config
3892
+ },
3893
+ batchId,
3894
+ inputsByPort
3895
+ }) : this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
3529
3896
  activationId: request.activationId,
3530
3897
  runId: request.runId,
3531
3898
  workflowId: request.workflowId,
@@ -3537,8 +3904,8 @@ var NodeExecutionRequestHandlerService = class {
3537
3904
  id: definition.id,
3538
3905
  config: definition.config
3539
3906
  },
3540
- batchId: pendingExecution.batchId ?? "batch_1",
3541
- input: persistedInput
3907
+ batchId,
3908
+ input: inputsByPort.in ?? request.input ?? []
3542
3909
  });
3543
3910
  await this.continuation.markNodeRunning({
3544
3911
  runId: activationRequest.runId,
@@ -3623,7 +3990,7 @@ var RunQueuePlanner = class {
3623
3990
  continue;
3624
3991
  }
3625
3992
  const inst = this.nodeInstances.get(toNodeId);
3626
- if (!this.isMultiInputNode(inst)) throw new Error(`Node ${toNodeId} has ${inputs.length} inbound edges. Insert a Merge node to combine branches.`);
3993
+ if (!this.isMultiInputNode(inst) && !this.supportsEngineFanInMerge(inst)) throw new Error(`Node ${toNodeId} has ${inputs.length} inbound edges but does not support multi-input execution.`);
3627
3994
  }
3628
3995
  }
3629
3996
  seedFromTrigger(args) {
@@ -3632,7 +3999,7 @@ var RunQueuePlanner = class {
3632
3999
  if (e.output !== "main") continue;
3633
4000
  this.enqueueEdge(queue, {
3634
4001
  batchId: args.batchId,
3635
- to: e.to,
4002
+ to: this.toEnqueueTarget(e),
3636
4003
  from: {
3637
4004
  nodeId: args.startNodeId,
3638
4005
  output: "main"
@@ -3647,7 +4014,7 @@ var RunQueuePlanner = class {
3647
4014
  const outItems = args.outputs[e.output] ?? [];
3648
4015
  this.enqueueEdge(queue, {
3649
4016
  batchId: args.batchId,
3650
- to: e.to,
4017
+ to: this.toEnqueueTarget(e),
3651
4018
  from: {
3652
4019
  nodeId: args.fromNodeId,
3653
4020
  output: e.output
@@ -3729,29 +4096,35 @@ var RunQueuePlanner = class {
3729
4096
  */
3730
4097
  usesTopologyCollectMerge(toNodeId) {
3731
4098
  const expectedInputs = this.topology.expectedInputsByNode.get(toNodeId) ?? [];
3732
- return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
4099
+ if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") return true;
4100
+ return (this.topology.incomingByNode.get(toNodeId) ?? []).length > 1;
4101
+ }
4102
+ toEnqueueTarget(edge) {
4103
+ return edge.to;
3733
4104
  }
3734
- enqueueEdge(queue, args) {
4105
+ enqueueEdge(queue, args, emptyPathSourceNodeId) {
3735
4106
  const target = this.nodeInstances.get(args.to.nodeId);
3736
4107
  if (!(this.usesTopologyCollectMerge(args.to.nodeId) || this.isMultiInputNode(target))) {
3737
4108
  if (args.items.length === 0) {
3738
- if (this.shouldContinueAfterEmptyOutputFromSource(args.from.nodeId)) {
4109
+ const continueSourceNodeId = emptyPathSourceNodeId ?? args.from.nodeId;
4110
+ if (this.shouldContinueAfterEmptyOutputFromSource(continueSourceNodeId)) {
3739
4111
  queue.push({
3740
4112
  nodeId: args.to.nodeId,
3741
4113
  input: args.items,
3742
- toInput: args.to.input,
4114
+ toInput: args.to.collectKey,
3743
4115
  batchId: args.batchId,
3744
4116
  from: args.from
3745
4117
  });
3746
4118
  return;
3747
4119
  }
3748
- this.propagateEmptyPath(queue, args.to.nodeId, args.batchId);
4120
+ const source = emptyPathSourceNodeId ?? args.from.nodeId;
4121
+ this.propagateEmptyPath(queue, args.to.nodeId, args.batchId, source);
3749
4122
  return;
3750
4123
  }
3751
4124
  queue.push({
3752
4125
  nodeId: args.to.nodeId,
3753
4126
  input: args.items,
3754
- toInput: args.to.input,
4127
+ toInput: args.to.collectKey,
3755
4128
  batchId: args.batchId,
3756
4129
  from: args.from
3757
4130
  });
@@ -3772,14 +4145,14 @@ var RunQueuePlanner = class {
3772
4145
  queue.push(collect);
3773
4146
  }
3774
4147
  const received = collect.collect.received;
3775
- received[args.to.input] = args.items;
4148
+ received[args.to.collectKey] = args.items;
3776
4149
  }
3777
4150
  shouldContinueAfterEmptyOutputFromSource(fromNodeId) {
3778
4151
  const def = this.topology.defsById.get(fromNodeId);
3779
4152
  if (!def) return false;
3780
4153
  return def.config.continueWhenEmptyOutput === true;
3781
4154
  }
3782
- propagateEmptyPath(queue, nodeId, batchId) {
4155
+ propagateEmptyPath(queue, nodeId, batchId, emptyPathSourceNodeId) {
3783
4156
  for (const edge of this.topology.outgoingByNode.get(nodeId) ?? []) this.enqueueEdge(queue, {
3784
4157
  batchId,
3785
4158
  to: edge.to,
@@ -3788,11 +4161,17 @@ var RunQueuePlanner = class {
3788
4161
  output: edge.output
3789
4162
  },
3790
4163
  items: []
3791
- });
4164
+ }, emptyPathSourceNodeId);
3792
4165
  }
3793
4166
  isMultiInputNode(n) {
3794
4167
  return typeof n?.executeMulti === "function";
3795
4168
  }
4169
+ hasRunnableExecute(n) {
4170
+ return typeof n === "object" && n !== null && n.kind === "node" && typeof n.execute === "function";
4171
+ }
4172
+ supportsEngineFanInMerge(n) {
4173
+ return this.hasRunnableExecute(n) && !this.isMultiInputNode(n);
4174
+ }
3796
4175
  describeUnsatisfiedCollect(queueEntry) {
3797
4176
  const batchId = queueEntry.batchId ?? "batch_1";
3798
4177
  const expectedInputs = queueEntry.collect?.expectedInputs ?? [];
@@ -3824,7 +4203,7 @@ var RunQueuePlanner = class {
3824
4203
  }
3825
4204
  findSources(nodeId, input$1) {
3826
4205
  const matches = [];
3827
- for (const [sourceNodeId, edges] of this.topology.outgoingByNode.entries()) for (const edge of edges) if (edge.to.nodeId === nodeId && edge.to.input === input$1) matches.push(this.formatNodeLabel(sourceNodeId));
4206
+ for (const [sourceNodeId, edges] of this.topology.outgoingByNode.entries()) for (const edge of edges) if (edge.to.nodeId === nodeId && edge.to.collectKey === input$1) matches.push(this.formatNodeLabel(sourceNodeId));
3828
4207
  return matches;
3829
4208
  }
3830
4209
  formatInputList(inputs) {
@@ -4499,5 +4878,5 @@ var WorkflowRepositoryWebhookTriggerMatcherFactory = class {
4499
4878
  };
4500
4879
 
4501
4880
  //#endregion
4502
- export { injectAll as $, NodeExecutor as A, WorkflowExecutableNodeClassifier as B, RunPolicySnapshotFactory as C, PersistedWorkflowTokenRegistry as D, WorkflowSnapshotCodec as E, CredentialResolverFactory as F, tool as G, chatModel as H, DefaultExecutionBinaryService as I, StackTraceCallSitePathResolver as J, InjectableRuntimeDecoratorComposer as K, UnavailableBinaryStorage as L, InProcessRetryRunner as M, DefaultExecutionContextFactory as N, MissingRuntimeTriggerToken as O, DefaultAsyncSleeper as P, inject as Q, NodeEventPublisher as R, ConfigDrivenOffloadPolicy as S, NodeInstanceFactory as T, getPersistedRuntimeTypeMetadata as U, ConnectionNodeIdFactory as V, node as W, container$1 as X, PersistedRuntimeTypeNameResolver as Y, delay as Z, EngineExecutionLimitsPolicy as _, InMemoryLiveWorkflowRepository as a, singleton as at, HintOnlyOffloadPolicy as b, EngineFactory as c, InMemoryRunDataFactory as d, injectable as et, InMemoryBinaryStorage as f, ENGINE_EXECUTION_LIMITS_DEFAULTS as g, RunTerminalPersistenceCoordinator as h, RunIntentService as i, registry as it, InProcessRetryRunnerFactory as j, NodeExecutorFactory as k, Engine as l, WorkflowPolicyErrorServices as m, WorkflowRepositoryWebhookTriggerMatcher as n, instancePerContainerCachingFactory as nt, EngineWorkflowRunnerServiceFactory as o, CoreTokens as ot, WorkflowStoragePolicyEvaluator as p, PersistedRuntimeTypeMetadataStore as q, RunIntentServiceFactory as r, predicateAwareClassFactory as rt, EngineWorkflowRunnerService as s, WorkflowRepositoryWebhookTriggerMatcherFactory as t, instanceCachingFactory as tt, RunFinishedAtFactory as u, LocalOnlyScheduler as v, NodeInstanceFactoryFactory as w, DefaultDrivingScheduler as x, InlineDrivingScheduler as y, WorkflowExecutableNodeClassifierFactory as z };
4503
- //# sourceMappingURL=runtime-Cy-3FTI_.js.map
4881
+ export { getPersistedRuntimeTypeMetadata as $, NodeExecutor as A, CredentialResolverFactory as B, RunPolicySnapshotFactory as C, PersistedWorkflowTokenRegistry as D, WorkflowSnapshotCodec as E, isUnbrandedPortsEmissionShape as F, WorkflowExecutableNodeClassifierFactory as G, DefaultExecutionBinaryService as H, ItemValueResolver as I, isItemValue as J, WorkflowExecutableNodeClassifier as K, InProcessRetryRunner as L, NodeOutputNormalizer as M, emitPorts as N, MissingRuntimeTriggerToken as O, isPortsEmission as P, chatModel as Q, DefaultExecutionContextFactory as R, ConfigDrivenOffloadPolicy as S, NodeInstanceFactory as T, UnavailableBinaryStorage as U, getOriginIndexFromItem as V, NodeEventPublisher as W, resolveItemValuesForExecution as X, itemValue as Y, resolveItemValuesInUnknown as Z, EngineExecutionLimitsPolicy as _, InMemoryLiveWorkflowRepository as a, PersistedRuntimeTypeNameResolver as at, HintOnlyOffloadPolicy as b, EngineFactory as c, inject as ct, InMemoryRunDataFactory as d, instanceCachingFactory as dt, node as et, InMemoryBinaryStorage as f, instancePerContainerCachingFactory as ft, ENGINE_EXECUTION_LIMITS_DEFAULTS as g, CoreTokens as gt, RunTerminalPersistenceCoordinator as h, singleton as ht, RunIntentService as i, StackTraceCallSitePathResolver as it, InProcessRetryRunnerFactory as j, NodeExecutorFactory as k, Engine as l, injectAll as lt, WorkflowPolicyErrorServices as m, registry as mt, WorkflowRepositoryWebhookTriggerMatcher as n, InjectableRuntimeDecoratorComposer as nt, EngineWorkflowRunnerServiceFactory as o, container$1 as ot, WorkflowStoragePolicyEvaluator as p, predicateAwareClassFactory as pt, ConnectionNodeIdFactory as q, RunIntentServiceFactory as r, PersistedRuntimeTypeMetadataStore as rt, EngineWorkflowRunnerService as s, delay as st, WorkflowRepositoryWebhookTriggerMatcherFactory as t, tool as tt, RunFinishedAtFactory as u, injectable as ut, LocalOnlyScheduler as v, NodeInstanceFactoryFactory as w, DefaultDrivingScheduler as x, InlineDrivingScheduler as y, DefaultAsyncSleeper as z };
4882
+ //# sourceMappingURL=runtime-BdH94eBR.js.map