@codemation/core 0.3.0 → 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 (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts → EngineRuntimeRegistration.types-DU6MsjU9.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-Dd4yD31l.d.cts → EngineWorkflowRunnerService-BBkL4VQF.d.cts} +2 -2
  4. package/dist/{InMemoryRunDataFactory-OUzDmAHt.d.cts → InMemoryRunDataFactory-CsYEMJK2.d.cts} +11 -3
  5. package/dist/{RunIntentService-Bkg4oYrM.d.cts → RunIntentService-BvlTpmEb.d.cts} +224 -237
  6. package/dist/{RunIntentService-BAKikN8h.d.ts → RunIntentService-zbTchO9T.d.ts} +305 -259
  7. package/dist/bootstrap/index.cjs +2 -2
  8. package/dist/bootstrap/index.d.cts +19 -7
  9. package/dist/bootstrap/index.d.ts +3 -3
  10. package/dist/bootstrap/index.js +2 -2
  11. package/dist/{bootstrap-DwS5S7s9.cjs → bootstrap-DHH2uo-W.cjs} +4 -2
  12. package/dist/bootstrap-DHH2uo-W.cjs.map +1 -0
  13. package/dist/{bootstrap-BD6CobHl.js → bootstrap-DbUlOl11.js} +4 -2
  14. package/dist/bootstrap-DbUlOl11.js.map +1 -0
  15. package/dist/{index-uCm9l0nw.d.ts → index-CUt13qs1.d.ts} +62 -34
  16. package/dist/index.cjs +22 -15
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +108 -42
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +13 -16
  21. package/dist/index.js.map +1 -1
  22. package/dist/{runtime-Cy-3FTI_.js → runtime-BdH94eBR.js} +502 -123
  23. package/dist/runtime-BdH94eBR.js.map +1 -0
  24. package/dist/{runtime-ZJUpWmPH.cjs → runtime-feFn8OmG.cjs} +561 -122
  25. package/dist/runtime-feFn8OmG.cjs.map +1 -0
  26. package/dist/testing.cjs +40 -36
  27. package/dist/testing.cjs.map +1 -1
  28. package/dist/testing.d.cts +17 -26
  29. package/dist/testing.d.ts +17 -26
  30. package/dist/testing.js +40 -36
  31. package/dist/testing.js.map +1 -1
  32. package/dist/{workflowActivationPolicy-BzyzXLa_.cjs → workflowActivationPolicy-6V3OJD3N.cjs} +65 -19
  33. package/dist/workflowActivationPolicy-6V3OJD3N.cjs.map +1 -0
  34. package/dist/{workflowActivationPolicy-B8HzTk3o.js → workflowActivationPolicy-Td9HTOuD.js} +65 -19
  35. package/dist/workflowActivationPolicy-Td9HTOuD.js.map +1 -0
  36. package/package.json +2 -1
  37. package/src/ai/AgentConfigInspectorFactory.ts +4 -0
  38. package/src/ai/AgentMessageConfigNormalizerFactory.ts +7 -0
  39. package/src/ai/AgentToolFactory.ts +2 -2
  40. package/src/ai/AiHost.ts +11 -10
  41. package/src/ai/NodeBackedToolConfig.ts +1 -1
  42. package/src/authoring/defineNode.types.ts +48 -72
  43. package/src/authoring/index.ts +1 -1
  44. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
  45. package/src/contracts/emitPorts.ts +27 -0
  46. package/src/contracts/index.ts +3 -0
  47. package/src/contracts/itemMeta.ts +11 -0
  48. package/src/contracts/itemValue.ts +147 -0
  49. package/src/contracts/runtimeTypes.ts +39 -22
  50. package/src/contracts/workflowTypes.ts +26 -56
  51. package/src/execution/FanInMergeByOriginMerger.ts +67 -0
  52. package/src/execution/ItemValueResolver.ts +27 -0
  53. package/src/execution/NodeActivationRequestComposer.ts +25 -0
  54. package/src/execution/NodeActivationRequestInputPreparer.ts +57 -25
  55. package/src/execution/NodeExecutor.ts +199 -30
  56. package/src/execution/NodeOutputNormalizer.ts +90 -0
  57. package/src/execution/index.ts +2 -0
  58. package/src/index.ts +2 -0
  59. package/src/orchestration/NodeExecutionRequestHandlerService.ts +39 -18
  60. package/src/orchestration/RunContinuationService.ts +11 -17
  61. package/src/planning/CurrentStateFrontierPlanner.ts +20 -20
  62. package/src/planning/RunQueuePlanner.ts +56 -19
  63. package/src/planning/WorkflowTopologyPlanner.ts +57 -33
  64. package/src/testing/ItemHarnessNode.ts +4 -10
  65. package/src/testing/ItemHarnessNodeConfig.ts +7 -16
  66. package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
  67. package/src/testing/SubWorkflowRunnerTestNode.ts +28 -43
  68. package/src/testing/SwitchHarnessNode.ts +54 -0
  69. package/src/types/index.ts +3 -0
  70. package/src/workflow/dsl/ChainCursorResolver.ts +68 -23
  71. package/src/workflow/dsl/WorkflowBuilder.ts +3 -5
  72. package/src/workflow/dsl/workflowBuilderTypes.ts +5 -8
  73. package/src/workflowSnapshots/MissingRuntimeNode.ts +4 -4
  74. package/src/workflowSnapshots/MissingRuntimeNodeConfig.ts +2 -2
  75. package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +16 -7
  76. package/dist/bootstrap-BD6CobHl.js.map +0 -1
  77. package/dist/bootstrap-DwS5S7s9.cjs.map +0 -1
  78. package/dist/runtime-Cy-3FTI_.js.map +0 -1
  79. package/dist/runtime-ZJUpWmPH.cjs.map +0 -1
  80. package/dist/workflowActivationPolicy-B8HzTk3o.js.map +0 -1
  81. package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +0 -1
@@ -164,6 +164,74 @@ function chatModel(options = {}) {
164
164
  return InjectableRuntimeDecoratorComposer.compose("chatModel", options, require("url").pathToFileURL(__filename).href);
165
165
  }
166
166
 
167
+ //#endregion
168
+ //#region src/contracts/itemValue.ts
169
+ const ITEM_VALUE_BRAND = Symbol.for("codemation.itemValue");
170
+ function itemValue(fn) {
171
+ return {
172
+ [ITEM_VALUE_BRAND]: true,
173
+ fn
174
+ };
175
+ }
176
+ function isItemValue(value) {
177
+ if (typeof value !== "object" || value === null) return false;
178
+ const v = value;
179
+ if (v[ITEM_VALUE_BRAND] === true) return true;
180
+ const keys = Object.keys(v);
181
+ if (keys.length === 1 && keys[0] === "fn" && typeof v.fn === "function") return true;
182
+ for (const sym of Object.getOwnPropertySymbols(v)) if (sym.description === "codemation.itemValue" && v[sym] === true) return true;
183
+ return false;
184
+ }
185
+ function containsItemValueInUnknown(value, seen = /* @__PURE__ */ new WeakSet()) {
186
+ if (isItemValue(value)) return true;
187
+ if (value === null || typeof value !== "object") return false;
188
+ if (seen.has(value)) return false;
189
+ seen.add(value);
190
+ if (Array.isArray(value)) return value.some((entry) => containsItemValueInUnknown(entry, seen));
191
+ for (const entry of Object.values(value)) if (containsItemValueInUnknown(entry, seen)) return true;
192
+ return false;
193
+ }
194
+ /**
195
+ * Deep-resolves {@link itemValue} leaves. Returns a new graph (does not mutate the original config object).
196
+ */
197
+ async function resolveItemValuesInUnknown(value, args, seen = /* @__PURE__ */ new WeakSet()) {
198
+ if (isItemValue(value)) return await Promise.resolve(value.fn(args));
199
+ if (value === null || typeof value !== "object") return value;
200
+ if (seen.has(value)) return value;
201
+ seen.add(value);
202
+ if (Array.isArray(value)) {
203
+ const out$1 = [];
204
+ for (let i = 0; i < value.length; i++) out$1.push(await resolveItemValuesInUnknown(value[i], args, seen));
205
+ return out$1;
206
+ }
207
+ const rec = value;
208
+ const entries = Object.entries(rec);
209
+ const proto = Object.getPrototypeOf(value);
210
+ if (proto !== Object.prototype && proto !== null && entries.length === 0) return value;
211
+ const out = Object.create(proto);
212
+ for (const [k, v] of entries) out[k] = await resolveItemValuesInUnknown(v, args, seen);
213
+ return out;
214
+ }
215
+ /**
216
+ * Clones runnable config (best-effort) so per-item {@link itemValue} resolution never mutates shared instances.
217
+ */
218
+ async function resolveItemValuesForExecution(config, nodeCtx, item, itemIndex, items) {
219
+ const ivArgs = {
220
+ item,
221
+ itemIndex,
222
+ items,
223
+ ctx: {
224
+ runId: nodeCtx.runId,
225
+ workflowId: nodeCtx.workflowId,
226
+ nodeId: nodeCtx.nodeId,
227
+ activationId: nodeCtx.activationId,
228
+ data: nodeCtx.data
229
+ }
230
+ };
231
+ if (!containsItemValueInUnknown(config)) return;
232
+ return await resolveItemValuesInUnknown(config, ivArgs);
233
+ }
234
+
167
235
  //#endregion
168
236
  //#region src/workflow/definition/ConnectionNodeIdFactory.ts
169
237
  /**
@@ -591,6 +659,64 @@ var ActivationEnqueueService = class {
591
659
  }
592
660
  };
593
661
 
662
+ //#endregion
663
+ //#region src/contracts/itemMeta.ts
664
+ /**
665
+ * Reads `meta._cm.originIndex` when present (used for fan-in merge-by-origin and Merge routing).
666
+ */
667
+ function getOriginIndexFromItem(item) {
668
+ const v = (item.meta?._cm)?.originIndex;
669
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
670
+ }
671
+
672
+ //#endregion
673
+ //#region src/execution/FanInMergeByOriginMerger.ts
674
+ /**
675
+ * Default fan-in: combine multi-port {@link NodeInputsByPort} into one {@link Items} batch for per-item nodes.
676
+ *
677
+ * This is used when a single-input per-item node has multiple inbound edges (for example, branch reconverge
678
+ * after an `If` / `Switch`). The default behavior is **append / union** (preserving item payloads) with a
679
+ * deterministic order:
680
+ *
681
+ * - When router origin metadata exists (`meta._cm.originIndex`), items are sorted by origin index so the
682
+ * downstream batch preserves original ordering across branches.
683
+ * - Otherwise, items are appended by port-key order, preserving each port's local order.
684
+ */
685
+ var FanInMergeByOriginMerger = class {
686
+ merge(inputsByPort) {
687
+ const portKeys = Object.keys(inputsByPort).sort();
688
+ if (portKeys.length === 0) return [];
689
+ if (portKeys.length === 1) return [...inputsByPort[portKeys[0]] ?? []];
690
+ const entries = [];
691
+ let anyOrigin = false;
692
+ for (let p = 0; p < portKeys.length; p++) {
693
+ const portKey = portKeys[p];
694
+ const items = inputsByPort[portKey] ?? [];
695
+ for (let i = 0; i < items.length; i++) {
696
+ const item = items[i];
697
+ const originIndex = getOriginIndexFromItem(item);
698
+ if (originIndex !== void 0) anyOrigin = true;
699
+ entries.push({
700
+ portKey,
701
+ portIndex: i,
702
+ item,
703
+ originIndex
704
+ });
705
+ }
706
+ }
707
+ if (!anyOrigin) return entries.map((e) => e.item);
708
+ const missingOriginRank = Number.MAX_SAFE_INTEGER;
709
+ return entries.slice().sort((a, b) => {
710
+ const ao = a.originIndex ?? missingOriginRank;
711
+ const bo = b.originIndex ?? missingOriginRank;
712
+ if (ao !== bo) return ao - bo;
713
+ const pk = a.portKey.localeCompare(b.portKey);
714
+ if (pk !== 0) return pk;
715
+ return a.portIndex - b.portIndex;
716
+ }).map((e) => e.item);
717
+ }
718
+ };
719
+
594
720
  //#endregion
595
721
  //#region src/execution/NodeInputContractError.ts
596
722
  var NodeInputContractError = class extends Error {
@@ -606,45 +732,58 @@ var NodeInputContractError = class extends Error {
606
732
  //#endregion
607
733
  //#region src/execution/NodeActivationRequestInputPreparer.ts
608
734
  /**
609
- * Maps and validates per-item inputs for {@link ItemNode} before enqueue persistence.
735
+ * Validates per-item inputs for {@link RunnableNode} before enqueue persistence (Zod on `item.json`).
736
+ * Does not rewrite `item.json` (wire stays as emitted upstream; engine passes parsed input via `execute` args).
737
+ * Converts multi-input activations into a single-input batch when the node is per-item only (engine fan-in).
610
738
  */
611
739
  var NodeActivationRequestInputPreparer = class {
740
+ fanInMerger = new FanInMergeByOriginMerger();
612
741
  constructor(workflowNodeInstanceFactory) {
613
742
  this.workflowNodeInstanceFactory = workflowNodeInstanceFactory;
614
743
  }
615
744
  async prepare(request) {
616
- if (request.kind !== "single") return request;
745
+ if (request.kind === "multi") return await this.prepareMulti(request);
746
+ return await this.prepareSingle(request);
747
+ }
748
+ async prepareMulti(request) {
617
749
  const nodeInstance = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
618
- if (!this.hasExecuteOne(nodeInstance)) return request;
750
+ if (!this.hasRunnableExecute(nodeInstance) || this.hasExecuteMulti(nodeInstance) || this.isTriggerNode(nodeInstance)) return request;
751
+ const merged = this.fanInMerger.merge(request.inputsByPort);
752
+ const single = {
753
+ ...request,
754
+ kind: "single",
755
+ input: merged
756
+ };
757
+ return await this.prepareSingle(single);
758
+ }
759
+ async prepareSingle(request) {
760
+ const nodeInstance = this.workflowNodeInstanceFactory.createByType(request.ctx.config.type);
761
+ if (!this.hasRunnableExecute(nodeInstance) || this.isTriggerNode(nodeInstance)) return request;
619
762
  const inputSchema = this.resolveInputSchema(nodeInstance, request.ctx.config);
620
- const config = request.ctx.config;
621
- const mappedItems = [];
622
- for (let i = 0; i < request.input.length; i++) {
623
- const item = request.input[i];
763
+ const inputBatch = request.input ?? [];
764
+ for (let i = 0; i < inputBatch.length; i++) {
765
+ const item = inputBatch[i];
624
766
  try {
625
- const mappedRaw = config.mapInput ? await Promise.resolve(config.mapInput({
626
- item,
627
- itemIndex: i,
628
- items: request.input,
629
- ctx: request.ctx
630
- })) : item.json;
631
- const parsed = inputSchema.parse(mappedRaw);
632
- mappedItems.push({
633
- ...item,
634
- json: parsed
635
- });
767
+ if (Array.isArray(item.json)) throw new Error("Item JSON must not be a top-level array");
768
+ inputSchema.parse(item.json);
636
769
  } catch (cause) {
637
770
  const message = this.formatContractFailure(cause);
638
771
  throw new NodeInputContractError(`Node ${request.nodeId} activation ${request.activationId}: input contract failed: ${message}`, request.nodeId, request.activationId, cause);
639
772
  }
640
773
  }
641
- return {
774
+ return request.input === void 0 ? {
642
775
  ...request,
643
- input: mappedItems
644
- };
776
+ input: inputBatch
777
+ } : request;
778
+ }
779
+ isTriggerNode(nodeInstance) {
780
+ return typeof nodeInstance === "object" && nodeInstance !== null && nodeInstance.kind === "trigger";
781
+ }
782
+ hasRunnableExecute(nodeInstance) {
783
+ return typeof nodeInstance === "object" && nodeInstance !== null && nodeInstance.kind === "node" && typeof nodeInstance.execute === "function";
645
784
  }
646
- hasExecuteOne(nodeInstance) {
647
- return typeof nodeInstance === "object" && nodeInstance !== null && typeof nodeInstance.executeOne === "function";
785
+ hasExecuteMulti(nodeInstance) {
786
+ return typeof nodeInstance.executeMulti === "function";
648
787
  }
649
788
  resolveInputSchema(nodeInstance, config) {
650
789
  const fromInstance = nodeInstance.inputSchema;
@@ -801,6 +940,87 @@ var InProcessRetryRunner = class InProcessRetryRunner {
801
940
  }
802
941
  };
803
942
 
943
+ //#endregion
944
+ //#region src/execution/ItemValueResolver.ts
945
+ /**
946
+ * Resolves {@link import("../contracts/itemValue").ItemValue} leaves on runnable config before {@link RunnableNode.execute}.
947
+ */
948
+ var ItemValueResolver = class {
949
+ async resolveConfigForItem(ctx, item, itemIndex, items) {
950
+ if (!ctx) throw new Error("ItemValueResolver.resolveConfigForItem: ctx is required");
951
+ const resolvedConfig = await resolveItemValuesForExecution(ctx.config, ctx, item, itemIndex, items);
952
+ const merged = resolvedConfig !== void 0 && resolvedConfig !== null ? resolvedConfig : ctx.config;
953
+ if (merged === void 0 || merged === null) return ctx;
954
+ return {
955
+ ...ctx,
956
+ config: merged
957
+ };
958
+ }
959
+ };
960
+
961
+ //#endregion
962
+ //#region src/contracts/emitPorts.ts
963
+ const EMIT_PORTS_BRAND = Symbol.for("codemation.emitPorts");
964
+ function emitPorts(ports) {
965
+ return {
966
+ [EMIT_PORTS_BRAND]: true,
967
+ ports
968
+ };
969
+ }
970
+ function isPortsEmission(value) {
971
+ return typeof value === "object" && value !== null && EMIT_PORTS_BRAND in value && value[EMIT_PORTS_BRAND] === true;
972
+ }
973
+ function isUnbrandedPortsEmissionShape(value) {
974
+ return typeof value === "object" && value !== null && "ports" in value && !isPortsEmission(value);
975
+ }
976
+
977
+ //#endregion
978
+ //#region src/execution/NodeOutputNormalizer.ts
979
+ var NodeOutputNormalizer = class {
980
+ normalizeExecuteResult(args) {
981
+ const { baseItem, raw, carry } = args;
982
+ if (isPortsEmission(raw)) return this.emitPortsToOutputs(baseItem, raw, carry);
983
+ if (isUnbrandedPortsEmissionShape(raw)) throw new Error("execute() returned an unbranded `{ ports: ... }` object. Use emitPorts(...) for multi-port runnable outputs.");
984
+ if (Array.isArray(raw)) return this.arrayFanOutToMain(baseItem, raw, carry);
985
+ if (this.isItemLike(raw)) return { main: [this.applyLineage(baseItem, raw, carry)] };
986
+ return { main: [this.applyLineage(baseItem, { json: raw }, carry)] };
987
+ }
988
+ arrayFanOutToMain(baseItem, raw, carry) {
989
+ 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).");
990
+ return { main: raw.map((json) => this.applyLineage(baseItem, { json }, carry)) };
991
+ }
992
+ emitPortsToOutputs(baseItem, emission, carry) {
993
+ const out = {};
994
+ for (const [port, payload] of Object.entries(emission.ports)) {
995
+ if (payload === void 0) continue;
996
+ out[port] = this.normalizePortPayload(baseItem, payload, carry);
997
+ }
998
+ return out;
999
+ }
1000
+ normalizePortPayload(baseItem, payload, carry) {
1001
+ if (payload.length === 0) return [];
1002
+ const el0 = payload[0];
1003
+ if (this.isItemLike(el0)) return payload.map((it) => this.applyLineage(baseItem, it, carry));
1004
+ return payload.map((json) => this.applyLineage(baseItem, { json }, carry));
1005
+ }
1006
+ isItemLike(value) {
1007
+ return typeof value === "object" && value !== null && "json" in value;
1008
+ }
1009
+ applyLineage(baseItem, next, carry) {
1010
+ if (carry === "carryThrough") return {
1011
+ ...baseItem,
1012
+ ...next,
1013
+ json: next.json
1014
+ };
1015
+ return {
1016
+ json: next.json,
1017
+ ...next.binary ? { binary: next.binary } : {},
1018
+ ...next.meta ? { meta: next.meta } : {},
1019
+ ...next.paired ? { paired: next.paired } : {}
1020
+ };
1021
+ }
1022
+ };
1023
+
804
1024
  //#endregion
805
1025
  //#region src/execution/InProcessRetryRunnerFactory.ts
806
1026
  var InProcessRetryRunnerFactory = class {
@@ -840,6 +1060,21 @@ var NodeActivationRequestComposer = class {
840
1060
  ctx
841
1061
  };
842
1062
  }
1063
+ createMultiFromDefinitionWithActivation(args) {
1064
+ const ctx = this.createNodeExecutionContext(args, args.definition, args.activationId);
1065
+ return {
1066
+ kind: "multi",
1067
+ runId: args.runId,
1068
+ activationId: args.activationId,
1069
+ workflowId: args.workflowId,
1070
+ nodeId: args.definition.id,
1071
+ parent: args.parent,
1072
+ executionOptions: args.executionOptions,
1073
+ batchId: args.batchId,
1074
+ inputsByPort: args.inputsByPort,
1075
+ ctx
1076
+ };
1077
+ }
843
1078
  createFromPlannedActivation(args) {
844
1079
  const activationId = this.activationIdFactory.makeActivationId();
845
1080
  const ctx = this.createNodeExecutionContext(args, args.nodeDefinition, activationId);
@@ -887,49 +1122,152 @@ var NodeActivationRequestComposer = class {
887
1122
  //#endregion
888
1123
  //#region src/execution/NodeExecutor.ts
889
1124
  var NodeExecutor = class {
890
- constructor(nodeInstanceFactory, retryRunner) {
1125
+ fanInMerger = new FanInMergeByOriginMerger();
1126
+ outputNormalizer = new NodeOutputNormalizer();
1127
+ itemValueResolver;
1128
+ constructor(nodeInstanceFactory, retryRunner, itemValueResolver) {
891
1129
  this.nodeInstanceFactory = nodeInstanceFactory;
892
1130
  this.retryRunner = retryRunner;
1131
+ this.itemValueResolver = itemValueResolver ?? new ItemValueResolver();
893
1132
  }
894
1133
  async execute(request) {
895
1134
  const policy = request.ctx.config.retryPolicy;
896
1135
  return await this.retryRunner.run(policy, async () => {
897
1136
  const nodeInstance = this.nodeInstanceFactory.createByType(request.ctx.config.type);
898
- if (request.kind === "multi") return await this.executeMultiInputNode(request, nodeInstance);
1137
+ if (request.kind === "multi") return await this.executeMultiInputActivation(request, nodeInstance);
899
1138
  return await this.executeSingleInputNode(request, nodeInstance);
900
1139
  });
901
1140
  }
902
- async executeMultiInputNode(request, node$1) {
1141
+ async executeMultiInputActivation(request, node$1) {
903
1142
  const multiInputNode = node$1;
904
- if (typeof multiInputNode.executeMulti !== "function") throw new Error(`Node ${request.nodeId} does not support executeMulti but received multi-input activation`);
905
- return await multiInputNode.executeMulti(request.inputsByPort, request.ctx);
1143
+ if (typeof multiInputNode.executeMulti === "function") {
1144
+ const raw = await multiInputNode.executeMulti(request.inputsByPort, request.ctx);
1145
+ this.assertNoPortEnvelopeBypass(request.nodeId, raw, "executeMulti()");
1146
+ return raw;
1147
+ }
1148
+ if (this.isRunnableNode(node$1)) {
1149
+ const merged = this.fanInMerger.merge(request.inputsByPort);
1150
+ const single = {
1151
+ ...request,
1152
+ kind: "single",
1153
+ input: merged
1154
+ };
1155
+ return await this.executeRunnableActivation(single, node$1);
1156
+ }
1157
+ throw new Error(`Node ${request.nodeId} does not support executeMulti or RunnableNode.execute but received multi-input activation`);
906
1158
  }
907
1159
  async executeSingleInputNode(request, node$1) {
908
- if (this.hasExecuteOne(node$1)) return await this.executeItemNode(request, node$1);
909
- const singleInputNode = node$1;
910
- if (typeof singleInputNode.execute !== "function") throw new Error(`Node ${request.nodeId} does not support execute but received single-input activation`);
911
- return await singleInputNode.execute(request.input, request.ctx);
1160
+ if (this.isTriggerNode(node$1)) {
1161
+ const raw = await node$1.execute(request.input, request.ctx);
1162
+ this.assertNoPortEnvelopeBypass(request.nodeId, raw, "trigger execute()");
1163
+ return raw;
1164
+ }
1165
+ if (this.isRunnableNode(node$1)) return await this.executeRunnableActivation(request, node$1);
1166
+ if (this.hasExecuteMulti(node$1)) return await this.executeMultiInputActivation(this.asMultiFromSingleActivation(request), node$1);
1167
+ throw new Error(`Node ${request.nodeId} does not support trigger or RunnableNode execution`);
912
1168
  }
913
- hasExecuteOne(node$1) {
914
- return typeof node$1 === "object" && node$1 !== null && typeof node$1.executeOne === "function";
1169
+ isTriggerNode(node$1) {
1170
+ return typeof node$1 === "object" && node$1 !== null && node$1.kind === "trigger";
915
1171
  }
916
- async executeItemNode(request, node$1) {
917
- const out = [];
918
- for (let i = 0; i < request.input.length; i++) {
919
- const item = request.input[i];
920
- const outputJson = await Promise.resolve(node$1.executeOne({
921
- input: item.json,
1172
+ isRunnableNode(node$1) {
1173
+ return typeof node$1 === "object" && node$1 !== null && node$1.kind === "node" && typeof node$1.execute === "function";
1174
+ }
1175
+ hasExecuteMulti(node$1) {
1176
+ return typeof node$1?.executeMulti === "function";
1177
+ }
1178
+ asMultiFromSingleActivation(request) {
1179
+ return {
1180
+ kind: "multi",
1181
+ runId: request.runId,
1182
+ activationId: request.activationId,
1183
+ workflowId: request.workflowId,
1184
+ nodeId: request.nodeId,
1185
+ parent: request.parent,
1186
+ executionOptions: request.executionOptions,
1187
+ batchId: request.batchId,
1188
+ ctx: request.ctx,
1189
+ inputsByPort: { in: request.input ?? [] }
1190
+ };
1191
+ }
1192
+ async executeRunnableActivation(request, node$1) {
1193
+ const runnableConfig = request.ctx.config;
1194
+ const carry = this.resolveLineageCarry(node$1, runnableConfig);
1195
+ const inputSchema = this.resolveInputSchema(node$1, runnableConfig);
1196
+ const inputBatch = request.input ?? [];
1197
+ if (inputBatch.length === 0 && runnableConfig.emptyBatchExecution === "runOnce") {
1198
+ const syntheticItem = { json: {} };
1199
+ const parsed = inputSchema.parse(syntheticItem.json);
1200
+ const runnableCtx = request.ctx;
1201
+ const resolvedCtx = await this.itemValueResolver.resolveConfigForItem(runnableCtx, syntheticItem, 0, inputBatch);
1202
+ const args = {
1203
+ input: parsed,
1204
+ item: syntheticItem,
1205
+ itemIndex: 0,
1206
+ items: inputBatch,
1207
+ ctx: this.pickExecutionContext(runnableCtx, resolvedCtx)
1208
+ };
1209
+ const raw = await Promise.resolve(node$1.execute(args));
1210
+ return this.outputNormalizer.normalizeExecuteResult({
1211
+ baseItem: syntheticItem,
1212
+ raw,
1213
+ carry
1214
+ });
1215
+ }
1216
+ const byPort = {};
1217
+ for (let i = 0; i < inputBatch.length; i++) {
1218
+ const item = inputBatch[i];
1219
+ this.assertItemJsonNotTopLevelArray(request.nodeId, item);
1220
+ const parsed = inputSchema.parse(item.json);
1221
+ const runnableCtx = request.ctx;
1222
+ const resolvedCtx = await this.itemValueResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
1223
+ const ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
1224
+ const args = {
1225
+ input: parsed,
922
1226
  item,
923
1227
  itemIndex: i,
924
- items: request.input,
925
- ctx: request.ctx
926
- }));
927
- out.push({
928
- ...item,
929
- json: outputJson
1228
+ items: inputBatch,
1229
+ ctx
1230
+ };
1231
+ const raw = await Promise.resolve(node$1.execute(args));
1232
+ const normalized = this.outputNormalizer.normalizeExecuteResult({
1233
+ baseItem: item,
1234
+ raw,
1235
+ carry
930
1236
  });
1237
+ for (const [port, batch] of Object.entries(normalized)) {
1238
+ if (!batch || batch.length === 0) continue;
1239
+ const list = byPort[port] ?? [];
1240
+ list.push(...batch);
1241
+ byPort[port] = list;
1242
+ }
931
1243
  }
932
- return { main: out };
1244
+ return byPort;
1245
+ }
1246
+ /** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
1247
+ pickExecutionContext(runnableCtx, resolvedCtx) {
1248
+ if (resolvedCtx != null && resolvedCtx.config != null) return resolvedCtx;
1249
+ return runnableCtx;
1250
+ }
1251
+ resolveInputSchema(nodeInstance, config) {
1252
+ const fromInstance = nodeInstance.inputSchema;
1253
+ if (fromInstance && typeof fromInstance.parse === "function") return fromInstance;
1254
+ const fromConfig = config.inputSchema;
1255
+ if (fromConfig && typeof fromConfig.parse === "function") return fromConfig;
1256
+ return zod.z.unknown();
1257
+ }
1258
+ assertItemJsonNotTopLevelArray(nodeId, item) {
1259
+ if (Array.isArray(item.json)) throw new Error(`Node ${nodeId}: item.json must not be a top-level JSON array`);
1260
+ }
1261
+ assertNoPortEnvelopeBypass(nodeId, value, methodName) {
1262
+ if (isPortsEmission(value)) throw new Error(`Node ${nodeId}: ${methodName} must return NodeOutputs, not emitPorts(...).`);
1263
+ if (isUnbrandedPortsEmissionShape(value)) throw new Error(`Node ${nodeId}: ${methodName} returned an unbranded \`{ ports: ... }\` object. Return NodeOutputs instead.`);
1264
+ }
1265
+ resolveLineageCarry(node$1, config) {
1266
+ if (config.lineageCarry) return config.lineageCarry;
1267
+ const base = config;
1268
+ const declared = base.declaredOutputPorts;
1269
+ if ((declared && declared.length > 0 ? [...new Set([...declared, ...base.nodeErrorHandler ? ["error"] : []])] : base.nodeErrorHandler ? ["main", "error"] : ["main"]).length > 1) return "carryThrough";
1270
+ return "emitOnly";
933
1271
  }
934
1272
  };
935
1273
 
@@ -1007,8 +1345,8 @@ var MissingRuntimeFallbacks = class {
1007
1345
  var MissingRuntimeNode = class {
1008
1346
  kind = "node";
1009
1347
  outputPorts = ["main"];
1010
- async execute(items) {
1011
- return { main: items };
1348
+ execute(args) {
1349
+ return args.item;
1012
1350
  }
1013
1351
  };
1014
1352
 
@@ -1197,6 +1535,7 @@ var WorkflowSnapshotCodec = class {
1197
1535
  }
1198
1536
  restoreNonSerializableProperties(liveRecord, hydrated) {
1199
1537
  for (const [key, value] of Object.entries(liveRecord)) if (typeof value === "function" || typeof value === "symbol") hydrated[key] = value;
1538
+ for (const sym of Object.getOwnPropertySymbols(liveRecord)) hydrated[sym] = liveRecord[sym];
1200
1539
  }
1201
1540
  restoreTypeProperty(record) {
1202
1541
  const tokenId = typeof record.tokenId === "string" ? record.tokenId : void 0;
@@ -1216,7 +1555,10 @@ var WorkflowSnapshotCodec = class {
1216
1555
  }
1217
1556
  asRecord(value) {
1218
1557
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1219
- return { ...value };
1558
+ const record = value;
1559
+ const out = { ...record };
1560
+ for (const sym of Object.getOwnPropertySymbols(value)) out[sym] = record[sym];
1561
+ return out;
1220
1562
  }
1221
1563
  };
1222
1564
 
@@ -1593,19 +1935,6 @@ var WorkflowTopology = class WorkflowTopology {
1593
1935
  const classifier = WorkflowExecutableNodeClassifierFactory.create(wf);
1594
1936
  const defs = /* @__PURE__ */ new Map();
1595
1937
  for (const n of wf.nodes) if (classifier.isExecutableNodeId(n.id)) defs.set(n.id, n);
1596
- const outgoing = /* @__PURE__ */ new Map();
1597
- for (const e of wf.edges) {
1598
- if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
1599
- const list = outgoing.get(e.from.nodeId) ?? [];
1600
- list.push({
1601
- output: e.from.output,
1602
- to: {
1603
- nodeId: e.to.nodeId,
1604
- input: e.to.input
1605
- }
1606
- });
1607
- outgoing.set(e.from.nodeId, list);
1608
- }
1609
1938
  const incomingByNode = /* @__PURE__ */ new Map();
1610
1939
  for (const e of wf.edges) {
1611
1940
  if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
@@ -1615,21 +1944,51 @@ var WorkflowTopology = class WorkflowTopology {
1615
1944
  nodeId: e.from.nodeId,
1616
1945
  output: e.from.output
1617
1946
  },
1618
- input: e.to.input
1947
+ input: e.to.input,
1948
+ collectKey: e.to.input
1619
1949
  });
1620
1950
  incomingByNode.set(e.to.nodeId, list);
1621
1951
  }
1622
- const expected = /* @__PURE__ */ new Map();
1623
- for (const [toNodeId, inputs] of incomingByNode.entries()) {
1952
+ const duplicateInputCounts = /* @__PURE__ */ new Map();
1953
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1624
1954
  const counts = /* @__PURE__ */ new Map();
1625
- for (const edge of inputs) counts.set(edge.input, (counts.get(edge.input) ?? 0) + 1);
1626
- 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.`);
1955
+ for (const edge of edges) counts.set(edge.input, (counts.get(edge.input) ?? 0) + 1);
1956
+ duplicateInputCounts.set(toNodeId, counts);
1957
+ }
1958
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1959
+ const counts = duplicateInputCounts.get(toNodeId) ?? /* @__PURE__ */ new Map();
1960
+ for (let i = 0; i < edges.length; i++) {
1961
+ const edge = edges[i];
1962
+ const collectKey = (counts.get(edge.input) ?? 0) > 1 ? `${edge.from.nodeId}:${edge.from.output}` : edge.input;
1963
+ edges[i] = {
1964
+ ...edge,
1965
+ collectKey
1966
+ };
1967
+ }
1968
+ }
1969
+ const outgoing = /* @__PURE__ */ new Map();
1970
+ for (const e of wf.edges) {
1971
+ if (!classifier.isExecutableNodeId(e.from.nodeId) || !classifier.isExecutableNodeId(e.to.nodeId)) continue;
1972
+ 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;
1973
+ const list = outgoing.get(e.from.nodeId) ?? [];
1974
+ list.push({
1975
+ output: e.from.output,
1976
+ to: {
1977
+ nodeId: e.to.nodeId,
1978
+ input: e.to.input,
1979
+ collectKey
1980
+ }
1981
+ });
1982
+ outgoing.set(e.from.nodeId, list);
1983
+ }
1984
+ const expected = /* @__PURE__ */ new Map();
1985
+ for (const [toNodeId, edges] of incomingByNode.entries()) {
1627
1986
  const order = [];
1628
1987
  const seen = /* @__PURE__ */ new Set();
1629
- for (const edge of inputs) {
1630
- if (seen.has(edge.input)) continue;
1631
- seen.add(edge.input);
1632
- order.push(edge.input);
1988
+ for (const edge of edges) {
1989
+ if (seen.has(edge.collectKey)) continue;
1990
+ seen.add(edge.collectKey);
1991
+ order.push(edge.collectKey);
1633
1992
  }
1634
1993
  expected.set(toNodeId, order);
1635
1994
  }
@@ -2135,6 +2494,11 @@ var RunContinuationService = class {
2135
2494
  batchId
2136
2495
  });
2137
2496
  const next = planner.nextActivation(queue);
2497
+ const finishedAt = completedSnapshot.finishedAt ?? completedSnapshot.updatedAt;
2498
+ const mergedSnapshots = {
2499
+ ...args.state.nodeSnapshotsByNodeId ?? {},
2500
+ [args.args.nodeId]: completedSnapshot
2501
+ };
2138
2502
  if (!next) {
2139
2503
  const lastNodeId = WorkflowExecutableNodeClassifierFactory.create(args.workflow).lastExecutableNodeIdInDefinitionOrder(args.workflow);
2140
2504
  const outputs = data.getOutputItems(lastNodeId, "main");
@@ -2144,11 +2508,8 @@ var RunContinuationService = class {
2144
2508
  status: "completed",
2145
2509
  queue: [],
2146
2510
  outputsByNode: data.dump(),
2147
- nodeSnapshotsByNodeId: {
2148
- ...args.state.nodeSnapshotsByNodeId ?? {},
2149
- [args.args.nodeId]: completedSnapshot
2150
- },
2151
- finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt
2511
+ nodeSnapshotsByNodeId: mergedSnapshots,
2512
+ finishedAtIso: finishedAt
2152
2513
  });
2153
2514
  await this.workflowExecutionRepository.save(completedState);
2154
2515
  await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
@@ -2183,11 +2544,8 @@ var RunContinuationService = class {
2183
2544
  status: "failed",
2184
2545
  queue: queue.map((q) => ({ ...q })),
2185
2546
  outputsByNode: data.dump(),
2186
- nodeSnapshotsByNodeId: {
2187
- ...args.state.nodeSnapshotsByNodeId ?? {},
2188
- [args.args.nodeId]: completedSnapshot
2189
- },
2190
- finishedAtIso: completedSnapshot.finishedAt ?? completedSnapshot.updatedAt
2547
+ nodeSnapshotsByNodeId: mergedSnapshots,
2548
+ finishedAtIso: finishedAt
2191
2549
  });
2192
2550
  await this.workflowExecutionRepository.save(failedState);
2193
2551
  await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
@@ -2238,10 +2596,6 @@ var RunContinuationService = class {
2238
2596
  executionOptions: args.state.executionOptions,
2239
2597
  nodeDefinition: nextDefinition
2240
2598
  });
2241
- const mergedSnapshots = {
2242
- ...args.state.nodeSnapshotsByNodeId ?? {},
2243
- [args.args.nodeId]: completedSnapshot
2244
- };
2245
2599
  try {
2246
2600
  const { queuedSnapshot, result } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
2247
2601
  runId: args.state.runId,
@@ -2272,7 +2626,6 @@ var RunContinuationService = class {
2272
2626
  return result;
2273
2627
  } catch (cause) {
2274
2628
  const error = cause instanceof Error ? cause : new Error(String(cause));
2275
- const finishedAt = completedSnapshot.finishedAt ?? completedSnapshot.updatedAt;
2276
2629
  const result = await this.terminateRunAfterActivationEnqueueRejected({
2277
2630
  wf: args.workflow,
2278
2631
  state: args.state,
@@ -2560,7 +2913,7 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2560
2913
  const frontierNodeIds = [];
2561
2914
  for (const nodeId of this.topology.defsById.keys()) {
2562
2915
  if (!requiredNodeIds.has(nodeId) || this.isNodeSatisfied(currentState, nodeId)) continue;
2563
- if ((this.topology.incomingByNode.get(nodeId) ?? []).every((edge) => this.isEdgeSatisfied(currentState, nodeId, edge.input))) frontierNodeIds.push(nodeId);
2916
+ if ((this.topology.incomingByNode.get(nodeId) ?? []).every((edge) => this.isEdgeSatisfied(currentState, nodeId, edge.collectKey))) frontierNodeIds.push(nodeId);
2564
2917
  }
2565
2918
  return frontierNodeIds;
2566
2919
  }
@@ -2578,13 +2931,13 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2578
2931
  if (requiredNodeIds.has(nodeId)) return;
2579
2932
  if (this.isNodeSatisfied(currentState, nodeId) && !this.isNodeSatisfiedByOutputsOnly(currentState, nodeId)) return;
2580
2933
  requiredNodeIds.add(nodeId);
2581
- 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);
2934
+ 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);
2582
2935
  }
2583
2936
  buildFrontierQueue(nodeId, currentState) {
2584
2937
  const incomingEdges = this.topology.incomingByNode.get(nodeId) ?? [];
2585
2938
  if (incomingEdges.length === 0) return [];
2586
2939
  const expectedInputs = this.topology.expectedInputsByNode.get(nodeId) ?? [];
2587
- if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") {
2940
+ if (this.usesCollect(nodeId)) {
2588
2941
  const received = {};
2589
2942
  for (const input$1 of expectedInputs) received[input$1] = this.resolveInput(currentState, nodeId, input$1);
2590
2943
  return [{
@@ -2598,7 +2951,7 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2598
2951
  }];
2599
2952
  }
2600
2953
  const input = expectedInputs[0] ?? "in";
2601
- const incomingEdge = incomingEdges.find((edge) => edge.input === input);
2954
+ const incomingEdge = incomingEdges.find((edge) => edge.collectKey === input);
2602
2955
  return [{
2603
2956
  nodeId,
2604
2957
  input: this.resolveInput(currentState, nodeId, input),
@@ -2618,16 +2971,17 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2618
2971
  isNodeSatisfiedByOutputsOnly(currentState, nodeId) {
2619
2972
  return this.hasOutputs(currentState, nodeId) && !this.hasCompletedSnapshot(currentState, nodeId);
2620
2973
  }
2621
- isEdgeSatisfied(currentState, nodeId, input) {
2622
- const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.input === input);
2974
+ isEdgeSatisfied(currentState, nodeId, collectKey) {
2975
+ const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.collectKey === collectKey);
2623
2976
  if (!incomingEdge) return false;
2624
- if (!this.hasOutputPort(currentState, incomingEdge.from.nodeId, incomingEdge.from.output)) return false;
2977
+ const fromNodeId = incomingEdge.from.nodeId;
2978
+ if (!this.isNodeSatisfied(currentState, fromNodeId)) return false;
2625
2979
  if (this.usesCollect(nodeId)) return true;
2626
- if (this.resolveOutputItems(currentState, incomingEdge.from.nodeId, incomingEdge.from.output).length > 0) return true;
2627
- return this.shouldContinueAfterEmptyOutputFromSource(incomingEdge.from.nodeId);
2980
+ if (this.resolveOutputItems(currentState, fromNodeId, incomingEdge.from.output).length > 0) return true;
2981
+ return this.shouldContinueAfterEmptyOutputFromSource(fromNodeId);
2628
2982
  }
2629
- resolveInput(currentState, nodeId, input) {
2630
- const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.input === input);
2983
+ resolveInput(currentState, nodeId, collectKey) {
2984
+ const incomingEdge = (this.topology.incomingByNode.get(nodeId) ?? []).find((edge) => edge.collectKey === collectKey);
2631
2985
  if (!incomingEdge) return [];
2632
2986
  return this.resolveOutputItems(currentState, incomingEdge.from.nodeId, incomingEdge.from.output);
2633
2987
  }
@@ -2638,17 +2992,13 @@ var CurrentStateFrontierPlanner = class CurrentStateFrontierPlanner {
2638
2992
  const snapshot = currentState.nodeSnapshotsByNodeId[nodeId];
2639
2993
  return snapshot?.status === "completed" || snapshot?.status === "skipped";
2640
2994
  }
2641
- hasOutputPort(currentState, nodeId, output) {
2642
- const outputs = currentState.outputsByNode[nodeId];
2643
- if (!outputs) return false;
2644
- return Object.prototype.hasOwnProperty.call(outputs, output);
2645
- }
2646
2995
  resolveOutputItems(currentState, nodeId, output) {
2647
2996
  return currentState.outputsByNode[nodeId]?.[output] ?? [];
2648
2997
  }
2649
2998
  usesCollect(nodeId) {
2650
2999
  const expectedInputs = this.topology.expectedInputsByNode.get(nodeId) ?? [];
2651
- return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
3000
+ if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") return true;
3001
+ return (this.topology.incomingByNode.get(nodeId) ?? []).length > 1;
2652
3002
  }
2653
3003
  shouldContinueAfterEmptyOutputFromSource(nodeId) {
2654
3004
  const definition = this.topology.defsById.get(nodeId);
@@ -3540,7 +3890,6 @@ var NodeExecutionRequestHandlerService = class {
3540
3890
  const resolvedParent = request.parent ?? state.parent;
3541
3891
  const data = this.runDataFactory.create(state.outputsByNode);
3542
3892
  const limits = this.resolveEngineLimitsFromState(state);
3543
- const persistedInput = pendingExecution.inputsByPort.in ?? request.input;
3544
3893
  const base = this.runExecutionContextFactory.create({
3545
3894
  runId: state.runId,
3546
3895
  workflowId: state.workflowId,
@@ -3552,7 +3901,11 @@ var NodeExecutionRequestHandlerService = class {
3552
3901
  data,
3553
3902
  nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, resolvedParent)
3554
3903
  });
3555
- const activationRequest = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
3904
+ const inputsByPort = pendingExecution.inputsByPort;
3905
+ const portKeys = Object.keys(inputsByPort);
3906
+ const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
3907
+ const batchId = pendingExecution.batchId ?? "batch_1";
3908
+ const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
3556
3909
  activationId: request.activationId,
3557
3910
  runId: request.runId,
3558
3911
  workflowId: request.workflowId,
@@ -3564,8 +3917,22 @@ var NodeExecutionRequestHandlerService = class {
3564
3917
  id: definition.id,
3565
3918
  config: definition.config
3566
3919
  },
3567
- batchId: pendingExecution.batchId ?? "batch_1",
3568
- input: persistedInput
3920
+ batchId,
3921
+ inputsByPort
3922
+ }) : this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
3923
+ activationId: request.activationId,
3924
+ runId: request.runId,
3925
+ workflowId: request.workflowId,
3926
+ parent: resolvedParent,
3927
+ executionOptions: request.executionOptions ?? state.executionOptions,
3928
+ base,
3929
+ data,
3930
+ definition: {
3931
+ id: definition.id,
3932
+ config: definition.config
3933
+ },
3934
+ batchId,
3935
+ input: inputsByPort.in ?? request.input ?? []
3569
3936
  });
3570
3937
  await this.continuation.markNodeRunning({
3571
3938
  runId: activationRequest.runId,
@@ -3650,7 +4017,7 @@ var RunQueuePlanner = class {
3650
4017
  continue;
3651
4018
  }
3652
4019
  const inst = this.nodeInstances.get(toNodeId);
3653
- if (!this.isMultiInputNode(inst)) throw new Error(`Node ${toNodeId} has ${inputs.length} inbound edges. Insert a Merge node to combine branches.`);
4020
+ if (!this.isMultiInputNode(inst) && !this.supportsEngineFanInMerge(inst)) throw new Error(`Node ${toNodeId} has ${inputs.length} inbound edges but does not support multi-input execution.`);
3654
4021
  }
3655
4022
  }
3656
4023
  seedFromTrigger(args) {
@@ -3659,7 +4026,7 @@ var RunQueuePlanner = class {
3659
4026
  if (e.output !== "main") continue;
3660
4027
  this.enqueueEdge(queue, {
3661
4028
  batchId: args.batchId,
3662
- to: e.to,
4029
+ to: this.toEnqueueTarget(e),
3663
4030
  from: {
3664
4031
  nodeId: args.startNodeId,
3665
4032
  output: "main"
@@ -3674,7 +4041,7 @@ var RunQueuePlanner = class {
3674
4041
  const outItems = args.outputs[e.output] ?? [];
3675
4042
  this.enqueueEdge(queue, {
3676
4043
  batchId: args.batchId,
3677
- to: e.to,
4044
+ to: this.toEnqueueTarget(e),
3678
4045
  from: {
3679
4046
  nodeId: args.fromNodeId,
3680
4047
  output: e.output
@@ -3756,29 +4123,35 @@ var RunQueuePlanner = class {
3756
4123
  */
3757
4124
  usesTopologyCollectMerge(toNodeId) {
3758
4125
  const expectedInputs = this.topology.expectedInputsByNode.get(toNodeId) ?? [];
3759
- return expectedInputs.length !== 1 || expectedInputs[0] !== "in";
4126
+ if (expectedInputs.length !== 1 || expectedInputs[0] !== "in") return true;
4127
+ return (this.topology.incomingByNode.get(toNodeId) ?? []).length > 1;
3760
4128
  }
3761
- enqueueEdge(queue, args) {
4129
+ toEnqueueTarget(edge) {
4130
+ return edge.to;
4131
+ }
4132
+ enqueueEdge(queue, args, emptyPathSourceNodeId) {
3762
4133
  const target = this.nodeInstances.get(args.to.nodeId);
3763
4134
  if (!(this.usesTopologyCollectMerge(args.to.nodeId) || this.isMultiInputNode(target))) {
3764
4135
  if (args.items.length === 0) {
3765
- if (this.shouldContinueAfterEmptyOutputFromSource(args.from.nodeId)) {
4136
+ const continueSourceNodeId = emptyPathSourceNodeId ?? args.from.nodeId;
4137
+ if (this.shouldContinueAfterEmptyOutputFromSource(continueSourceNodeId)) {
3766
4138
  queue.push({
3767
4139
  nodeId: args.to.nodeId,
3768
4140
  input: args.items,
3769
- toInput: args.to.input,
4141
+ toInput: args.to.collectKey,
3770
4142
  batchId: args.batchId,
3771
4143
  from: args.from
3772
4144
  });
3773
4145
  return;
3774
4146
  }
3775
- this.propagateEmptyPath(queue, args.to.nodeId, args.batchId);
4147
+ const source = emptyPathSourceNodeId ?? args.from.nodeId;
4148
+ this.propagateEmptyPath(queue, args.to.nodeId, args.batchId, source);
3776
4149
  return;
3777
4150
  }
3778
4151
  queue.push({
3779
4152
  nodeId: args.to.nodeId,
3780
4153
  input: args.items,
3781
- toInput: args.to.input,
4154
+ toInput: args.to.collectKey,
3782
4155
  batchId: args.batchId,
3783
4156
  from: args.from
3784
4157
  });
@@ -3799,14 +4172,14 @@ var RunQueuePlanner = class {
3799
4172
  queue.push(collect);
3800
4173
  }
3801
4174
  const received = collect.collect.received;
3802
- received[args.to.input] = args.items;
4175
+ received[args.to.collectKey] = args.items;
3803
4176
  }
3804
4177
  shouldContinueAfterEmptyOutputFromSource(fromNodeId) {
3805
4178
  const def = this.topology.defsById.get(fromNodeId);
3806
4179
  if (!def) return false;
3807
4180
  return def.config.continueWhenEmptyOutput === true;
3808
4181
  }
3809
- propagateEmptyPath(queue, nodeId, batchId) {
4182
+ propagateEmptyPath(queue, nodeId, batchId, emptyPathSourceNodeId) {
3810
4183
  for (const edge of this.topology.outgoingByNode.get(nodeId) ?? []) this.enqueueEdge(queue, {
3811
4184
  batchId,
3812
4185
  to: edge.to,
@@ -3815,11 +4188,17 @@ var RunQueuePlanner = class {
3815
4188
  output: edge.output
3816
4189
  },
3817
4190
  items: []
3818
- });
4191
+ }, emptyPathSourceNodeId);
3819
4192
  }
3820
4193
  isMultiInputNode(n) {
3821
4194
  return typeof n?.executeMulti === "function";
3822
4195
  }
4196
+ hasRunnableExecute(n) {
4197
+ return typeof n === "object" && n !== null && n.kind === "node" && typeof n.execute === "function";
4198
+ }
4199
+ supportsEngineFanInMerge(n) {
4200
+ return this.hasRunnableExecute(n) && !this.isMultiInputNode(n);
4201
+ }
3823
4202
  describeUnsatisfiedCollect(queueEntry) {
3824
4203
  const batchId = queueEntry.batchId ?? "batch_1";
3825
4204
  const expectedInputs = queueEntry.collect?.expectedInputs ?? [];
@@ -3851,7 +4230,7 @@ var RunQueuePlanner = class {
3851
4230
  }
3852
4231
  findSources(nodeId, input) {
3853
4232
  const matches = [];
3854
- for (const [sourceNodeId, edges] of this.topology.outgoingByNode.entries()) for (const edge of edges) if (edge.to.nodeId === nodeId && edge.to.input === input) matches.push(this.formatNodeLabel(sourceNodeId));
4233
+ for (const [sourceNodeId, edges] of this.topology.outgoingByNode.entries()) for (const edge of edges) if (edge.to.nodeId === nodeId && edge.to.collectKey === input) matches.push(this.formatNodeLabel(sourceNodeId));
3855
4234
  return matches;
3856
4235
  }
3857
4236
  formatInputList(inputs) {
@@ -4658,6 +5037,12 @@ Object.defineProperty(exports, 'InlineDrivingScheduler', {
4658
5037
  return InlineDrivingScheduler;
4659
5038
  }
4660
5039
  });
5040
+ Object.defineProperty(exports, 'ItemValueResolver', {
5041
+ enumerable: true,
5042
+ get: function () {
5043
+ return ItemValueResolver;
5044
+ }
5045
+ });
4661
5046
  Object.defineProperty(exports, 'LocalOnlyScheduler', {
4662
5047
  enumerable: true,
4663
5048
  get: function () {
@@ -4700,6 +5085,12 @@ Object.defineProperty(exports, 'NodeInstanceFactoryFactory', {
4700
5085
  return NodeInstanceFactoryFactory;
4701
5086
  }
4702
5087
  });
5088
+ Object.defineProperty(exports, 'NodeOutputNormalizer', {
5089
+ enumerable: true,
5090
+ get: function () {
5091
+ return NodeOutputNormalizer;
5092
+ }
5093
+ });
4703
5094
  Object.defineProperty(exports, 'PersistedRuntimeTypeMetadataStore', {
4704
5095
  enumerable: true,
4705
5096
  get: function () {
@@ -4814,22 +5205,70 @@ Object.defineProperty(exports, 'chatModel', {
4814
5205
  return chatModel;
4815
5206
  }
4816
5207
  });
5208
+ Object.defineProperty(exports, 'emitPorts', {
5209
+ enumerable: true,
5210
+ get: function () {
5211
+ return emitPorts;
5212
+ }
5213
+ });
5214
+ Object.defineProperty(exports, 'getOriginIndexFromItem', {
5215
+ enumerable: true,
5216
+ get: function () {
5217
+ return getOriginIndexFromItem;
5218
+ }
5219
+ });
4817
5220
  Object.defineProperty(exports, 'getPersistedRuntimeTypeMetadata', {
4818
5221
  enumerable: true,
4819
5222
  get: function () {
4820
5223
  return getPersistedRuntimeTypeMetadata;
4821
5224
  }
4822
5225
  });
5226
+ Object.defineProperty(exports, 'isItemValue', {
5227
+ enumerable: true,
5228
+ get: function () {
5229
+ return isItemValue;
5230
+ }
5231
+ });
5232
+ Object.defineProperty(exports, 'isPortsEmission', {
5233
+ enumerable: true,
5234
+ get: function () {
5235
+ return isPortsEmission;
5236
+ }
5237
+ });
5238
+ Object.defineProperty(exports, 'isUnbrandedPortsEmissionShape', {
5239
+ enumerable: true,
5240
+ get: function () {
5241
+ return isUnbrandedPortsEmissionShape;
5242
+ }
5243
+ });
5244
+ Object.defineProperty(exports, 'itemValue', {
5245
+ enumerable: true,
5246
+ get: function () {
5247
+ return itemValue;
5248
+ }
5249
+ });
4823
5250
  Object.defineProperty(exports, 'node', {
4824
5251
  enumerable: true,
4825
5252
  get: function () {
4826
5253
  return node;
4827
5254
  }
4828
5255
  });
5256
+ Object.defineProperty(exports, 'resolveItemValuesForExecution', {
5257
+ enumerable: true,
5258
+ get: function () {
5259
+ return resolveItemValuesForExecution;
5260
+ }
5261
+ });
5262
+ Object.defineProperty(exports, 'resolveItemValuesInUnknown', {
5263
+ enumerable: true,
5264
+ get: function () {
5265
+ return resolveItemValuesInUnknown;
5266
+ }
5267
+ });
4829
5268
  Object.defineProperty(exports, 'tool', {
4830
5269
  enumerable: true,
4831
5270
  get: function () {
4832
5271
  return tool;
4833
5272
  }
4834
5273
  });
4835
- //# sourceMappingURL=runtime-ZJUpWmPH.cjs.map
5274
+ //# sourceMappingURL=runtime-feFn8OmG.cjs.map