botmux-workflow-core 3.16.0 → 3.17.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.
@@ -233,6 +233,104 @@ function isRecord(value) {
233
233
  return typeof value === "object" && value !== null && !Array.isArray(value);
234
234
  }
235
235
 
236
+ // src/workflows/v3/artifact-contract.ts
237
+ var MANIFEST_FILE_KINDS = [
238
+ "markdown",
239
+ "json",
240
+ "text",
241
+ "code",
242
+ "log",
243
+ "binary",
244
+ "directory"
245
+ ];
246
+ var MANIFEST_SUMMARY_MAX_BYTES = 4 * 1024;
247
+ var MANIFEST_PREVIEW_MAX_BYTES = 4 * 1024;
248
+ var MANIFEST_STATUSES = ["ok", "fail"];
249
+ var MANIFEST_SCHEMA_VERSION = 1;
250
+
251
+ // src/workflows/v3/artifact-contract-declarations.ts
252
+ var V3_ARTIFACT_OUTPUT_KEY_RE = /^[A-Za-z0-9._-]+$/;
253
+ var V3_ARTIFACT_OUTPUT_MAX_COUNT = 32;
254
+ var V3_ARTIFACT_OUTPUT_MAX_BYTES = 4 * 1024;
255
+ function normalizeArtifactOutputs(value, where, problems) {
256
+ if (value === void 0) return void 0;
257
+ if (!isRecord2(value) || Object.keys(value).length === 0) {
258
+ problems.push(`${where}.outputs must be a non-empty object when present`);
259
+ return void 0;
260
+ }
261
+ const entries = Object.entries(value);
262
+ if (entries.length > V3_ARTIFACT_OUTPUT_MAX_COUNT) {
263
+ problems.push(`${where}.outputs has ${entries.length} entries (max ${V3_ARTIFACT_OUTPUT_MAX_COUNT})`);
264
+ }
265
+ if (Buffer.byteLength(JSON.stringify(value), "utf-8") > V3_ARTIFACT_OUTPUT_MAX_BYTES) {
266
+ problems.push(`${where}.outputs exceeds ${V3_ARTIFACT_OUTPUT_MAX_BYTES} serialized bytes`);
267
+ }
268
+ const out = /* @__PURE__ */ Object.create(null);
269
+ const paths = /* @__PURE__ */ new Set();
270
+ for (const [key, raw] of entries) {
271
+ const itemWhere = `${where}.outputs.${JSON.stringify(key)}`;
272
+ if (!V3_ARTIFACT_OUTPUT_KEY_RE.test(key)) {
273
+ problems.push(`${itemWhere} key must match ${V3_ARTIFACT_OUTPUT_KEY_RE}`);
274
+ continue;
275
+ }
276
+ if (!isRecord2(raw)) {
277
+ problems.push(`${itemWhere} must be { path, kind }`);
278
+ continue;
279
+ }
280
+ const extra = Object.keys(raw).filter((field) => field !== "path" && field !== "kind");
281
+ if (extra.length > 0) {
282
+ problems.push(`${itemWhere} has unsupported key(s): ${extra.join(", ")} (allowed: path, kind)`);
283
+ continue;
284
+ }
285
+ if (!isPortableRelativeArtifactPath(raw.path)) {
286
+ problems.push(`${itemWhere}.path must be a portable relative path without '.', '..', or empty segments`);
287
+ continue;
288
+ }
289
+ if (typeof raw.kind !== "string" || !MANIFEST_FILE_KINDS.includes(raw.kind)) {
290
+ problems.push(`${itemWhere}.kind must be one of ${MANIFEST_FILE_KINDS.join(" | ")}`);
291
+ continue;
292
+ }
293
+ if (paths.has(raw.path)) {
294
+ problems.push(`${where}.outputs contains duplicate path ${JSON.stringify(raw.path)}`);
295
+ continue;
296
+ }
297
+ paths.add(raw.path);
298
+ out[key] = { path: raw.path, kind: raw.kind };
299
+ }
300
+ return out;
301
+ }
302
+ function validateManifestArtifactContract(outputs, manifest) {
303
+ if (!outputs) return { ok: true, problems: [] };
304
+ const problems = [];
305
+ for (const [key, declaration] of Object.entries(outputs)) {
306
+ const matches = manifest.files.filter((file) => file.path === declaration.path);
307
+ if (matches.length === 0) {
308
+ problems.push(
309
+ `output ${JSON.stringify(key)} requires path ${JSON.stringify(declaration.path)} (available: ${manifest.files.map((file) => `${file.path}:${file.kind}`).join(", ") || "none"})`
310
+ );
311
+ continue;
312
+ }
313
+ if (matches.length > 1) {
314
+ problems.push(`output ${JSON.stringify(key)} path ${JSON.stringify(declaration.path)} appears more than once`);
315
+ continue;
316
+ }
317
+ if (matches[0].kind !== declaration.kind) {
318
+ problems.push(
319
+ `output ${JSON.stringify(key)} path ${JSON.stringify(declaration.path)} has kind ${JSON.stringify(matches[0].kind)}, expected ${JSON.stringify(declaration.kind)}`
320
+ );
321
+ }
322
+ }
323
+ return { ok: problems.length === 0, problems };
324
+ }
325
+ function isPortableRelativeArtifactPath(value) {
326
+ if (typeof value !== "string" || value.length === 0 || value.startsWith("/") || value.includes("\\") || value.includes("\0") || /^[A-Za-z]:/.test(value)) return false;
327
+ const segments = value.split("/");
328
+ return segments.every((segment) => segment !== "" && segment !== "." && segment !== "..");
329
+ }
330
+ function isRecord2(value) {
331
+ return typeof value === "object" && value !== null && !Array.isArray(value);
332
+ }
333
+
236
334
  // src/workflows/v3/dag.ts
237
335
  var NODE_KINDS = ["goal", "host", "loop"];
238
336
  var V3_HOST_EXECUTORS = ["feishu-send", "feishu-reply", "botmux-schedule"];
@@ -283,6 +381,10 @@ function validateDag(raw) {
283
381
  if (!isObject(raw)) {
284
382
  throw new DagValidationError(["root must be a JSON object"]);
285
383
  }
384
+ const schemaVersion = raw.schemaVersion === void 0 ? 1 : raw.schemaVersion;
385
+ if (schemaVersion !== 1 && schemaVersion !== 2) {
386
+ problems.push(`schemaVersion must be 1 or 2 (got ${JSON.stringify(raw.schemaVersion)})`);
387
+ }
286
388
  if (typeof raw.runId !== "string" || !SEGMENT_RE.test(raw.runId)) {
287
389
  problems.push(`runId must be a path-safe string matching ${SEGMENT_RE} (got ${JSON.stringify(raw.runId)})`);
288
390
  }
@@ -319,9 +421,9 @@ function validateDag(raw) {
319
421
  if (fromList.includes(id)) problems.push(`node "${id}" depends on itself`);
320
422
  if (new Set(fromList).size !== fromList.length) problems.push(`node "${id}".depends has duplicates`);
321
423
  const triggerRule = normTriggerRule(n.triggerRule, depends.length, `node "${id}"`, problems);
322
- const inputs = normInputs(n.inputs, id, problems);
424
+ const inputs = normInputs(n.inputs, id, problems, schemaVersion === 2 ? 2 : 1);
323
425
  if (type === "loop") {
324
- const loopFields = normLoopFields(n, id, problems);
426
+ const loopFields = normLoopFields(n, id, problems, schemaVersion === 2 ? 2 : 1);
325
427
  if (loopFields) {
326
428
  nodes.push({
327
429
  id,
@@ -338,6 +440,7 @@ function validateDag(raw) {
338
440
  continue;
339
441
  }
340
442
  if (type === "host") {
443
+ if (n.outputs !== void 0) problems.push(`host node "${id}".outputs is not supported`);
341
444
  if (n.goal !== void 0) problems.push(`host node "${id}".goal is not supported`);
342
445
  if (n.bot !== void 0) problems.push(`host node "${id}".bot is not supported \u2014 host nodes do not spawn a CLI`);
343
446
  if (n.override !== void 0) problems.push(`host node "${id}".override is not supported`);
@@ -413,6 +516,10 @@ function validateDag(raw) {
413
516
  const humanGate = normHumanGate(n.humanGate, `node "${id}"`, problems);
414
517
  const override = normOverride(n.override, `node "${id}"`, problems);
415
518
  const revisitTo = normRevisitTo(n.revisitTo, id, problems);
519
+ if (schemaVersion !== 2 && n.outputs !== void 0) {
520
+ problems.push(`goal node "${id}".outputs requires schemaVersion 2`);
521
+ }
522
+ const outputs = schemaVersion === 2 ? normalizeArtifactOutputs(n.outputs, `goal node "${id}"`, problems) : void 0;
416
523
  nodes.push({
417
524
  id,
418
525
  type,
@@ -422,6 +529,7 @@ function validateDag(raw) {
422
529
  triggerRule,
423
530
  override,
424
531
  inputs,
532
+ outputs,
425
533
  timeoutSec,
426
534
  humanGate,
427
535
  resultSchema,
@@ -437,6 +545,14 @@ function validateDag(raw) {
437
545
  problems.push(`node "${node.id}".inputs references unknown node "${inp.from}"`);
438
546
  } else if (!node.depends.some((d) => d.from === inp.from)) {
439
547
  problems.push(`node "${node.id}".inputs.from "${inp.from}" must also be in depends`);
548
+ } else if (inp.output !== void 0) {
549
+ const source = nodes.find((candidate) => candidate.id === inp.from);
550
+ const outputs = source?.type === "loop" ? source.body?.nodes.find((bodyNode) => bodyNode.id === source.output?.from)?.outputs : source?.outputs;
551
+ if (!outputs || !Object.prototype.hasOwnProperty.call(outputs, inp.output)) {
552
+ problems.push(
553
+ `node "${node.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
554
+ );
555
+ }
440
556
  }
441
557
  }
442
558
  if (node.type === "host") {
@@ -515,7 +631,11 @@ function validateDag(raw) {
515
631
  }
516
632
  }
517
633
  if (problems.length > 0) throw new DagValidationError(problems);
518
- const dag = { runId: raw.runId, nodes };
634
+ const dag = {
635
+ ...raw.schemaVersion !== void 0 ? { schemaVersion } : {},
636
+ runId: raw.runId,
637
+ nodes
638
+ };
519
639
  topologicalOrder(dag);
520
640
  return dag;
521
641
  }
@@ -829,7 +949,7 @@ function downstreamCone(nodeId, nodes) {
829
949
  function nodeByIdUnsafe(nodes, nodeId) {
830
950
  return nodes.find((node) => node.id === nodeId);
831
951
  }
832
- function normLoopFields(n, id, problems) {
952
+ function normLoopFields(n, id, problems, schemaVersion) {
833
953
  const where = `loop node "${id}"`;
834
954
  const before = problems.length;
835
955
  if (n.timeoutSec !== void 0) {
@@ -900,10 +1020,14 @@ function normLoopFields(n, id, problems) {
900
1020
  const bFromList = bdepends.map((d) => d.from);
901
1021
  if (bFromList.includes(bid)) problems.push(`${where}.body node "${bid}" depends on itself`);
902
1022
  if (new Set(bFromList).size !== bFromList.length) problems.push(`${where}.body node "${bid}".depends has duplicates`);
903
- const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems);
1023
+ const binputs = normInputs(b.inputs, `${id}.body.${bid}`, problems, schemaVersion);
904
1024
  const btimeout = normTimeoutSec(b.timeoutSec, `${where}.body node "${bid}"`, problems);
905
1025
  const bschema = normResultSchema(b.resultSchema, `${id}.body.${bid}`, problems);
906
1026
  const boverride = normOverride(b.override, `${where}.body node "${bid}"`, problems);
1027
+ if (schemaVersion !== 2 && b.outputs !== void 0) {
1028
+ problems.push(`${where}.body node "${bid}".outputs requires schemaVersion 2`);
1029
+ }
1030
+ const boutputs = schemaVersion === 2 ? normalizeArtifactOutputs(b.outputs, `${where}.body node "${bid}"`, problems) : void 0;
907
1031
  bodyNodes.push({
908
1032
  id: bid,
909
1033
  type: "goal",
@@ -912,6 +1036,7 @@ function normLoopFields(n, id, problems) {
912
1036
  depends: bdepends,
913
1037
  override: boverride,
914
1038
  inputs: binputs,
1039
+ outputs: boutputs,
915
1040
  timeoutSec: btimeout,
916
1041
  humanGate: null,
917
1042
  resultSchema: bschema
@@ -926,6 +1051,13 @@ function normLoopFields(n, id, problems) {
926
1051
  problems.push(`${where}.body node "${bn.id}".inputs references unknown body node "${inp.from}"`);
927
1052
  } else if (!bn.depends.some((d) => d.from === inp.from)) {
928
1053
  problems.push(`${where}.body node "${bn.id}".inputs.from "${inp.from}" must also be in depends`);
1054
+ } else if (inp.output !== void 0) {
1055
+ const source = bodyNodes.find((candidate) => candidate.id === inp.from);
1056
+ if (!source?.outputs || !Object.prototype.hasOwnProperty.call(source.outputs, inp.output)) {
1057
+ problems.push(
1058
+ `${where}.body node "${bn.id}".inputs output ${JSON.stringify(inp.output)} is not declared by source "${inp.from}"`
1059
+ );
1060
+ }
929
1061
  }
930
1062
  }
931
1063
  }
@@ -1151,7 +1283,7 @@ function normResultSchema(v, id, problems) {
1151
1283
  }
1152
1284
  return schema;
1153
1285
  }
1154
- function normInputs(v, id, problems) {
1286
+ function normInputs(v, id, problems, schemaVersion) {
1155
1287
  if (v === void 0) return [];
1156
1288
  if (!Array.isArray(v)) {
1157
1289
  problems.push(`node "${id}".inputs must be an array`);
@@ -1164,9 +1296,29 @@ function normInputs(v, id, problems) {
1164
1296
  problems.push(`node "${id}".inputs[${j}] must be { from: <nodeId>, select? }`);
1165
1297
  continue;
1166
1298
  }
1167
- const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select");
1299
+ const extra = Object.keys(inp).filter((k) => k !== "from" && k !== "select" && k !== "output");
1168
1300
  if (extra.length > 0) {
1169
- problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select)`);
1301
+ problems.push(`node "${id}".inputs[${j}] has unsupported key(s): ${extra.join(", ")} (allowed: from, select/output)`);
1302
+ continue;
1303
+ }
1304
+ if (schemaVersion === 2) {
1305
+ if (inp.select !== void 0) {
1306
+ problems.push(`node "${id}".inputs[${j}].select is legacy-only; schemaVersion 2 must use output`);
1307
+ continue;
1308
+ }
1309
+ if (inp.output === void 0) {
1310
+ out.push({ from: inp.from });
1311
+ continue;
1312
+ }
1313
+ if (typeof inp.output !== "string" || !SEGMENT_RE.test(inp.output)) {
1314
+ problems.push(`node "${id}".inputs[${j}].output must be a stable key matching ${SEGMENT_RE}`);
1315
+ continue;
1316
+ }
1317
+ out.push({ from: inp.from, output: inp.output });
1318
+ continue;
1319
+ }
1320
+ if (inp.output !== void 0) {
1321
+ problems.push(`node "${id}".inputs[${j}].output requires schemaVersion 2`);
1170
1322
  continue;
1171
1323
  }
1172
1324
  if (inp.select === void 0) {
@@ -3235,21 +3387,6 @@ function writePendingWait(runDir, input) {
3235
3387
  return wait;
3236
3388
  }
3237
3389
 
3238
- // src/workflows/v3/artifact-contract.ts
3239
- var MANIFEST_FILE_KINDS = [
3240
- "markdown",
3241
- "json",
3242
- "text",
3243
- "code",
3244
- "log",
3245
- "binary",
3246
- "directory"
3247
- ];
3248
- var MANIFEST_SUMMARY_MAX_BYTES = 4 * 1024;
3249
- var MANIFEST_PREVIEW_MAX_BYTES = 4 * 1024;
3250
- var MANIFEST_STATUSES = ["ok", "fail"];
3251
- var MANIFEST_SCHEMA_VERSION = 1;
3252
-
3253
3390
  // src/workflows/v3/contract.ts
3254
3391
  var GOAL_ENV = {
3255
3392
  /** Path to the single-sentence goal text file. */
@@ -3730,7 +3867,7 @@ function readAndVerifyV3HostSuccessResult(input) {
3730
3867
  `v3 host success result is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
3731
3868
  );
3732
3869
  }
3733
- if (!isRecord2(raw)) throw new Error("v3 host success result must be an object");
3870
+ if (!isRecord3(raw)) throw new Error("v3 host success result must be an object");
3734
3871
  const allowed = /* @__PURE__ */ new Set([
3735
3872
  "schemaVersion",
3736
3873
  "runId",
@@ -3763,13 +3900,13 @@ function readAndVerifyV3HostSuccessResult(input) {
3763
3900
  ]) {
3764
3901
  if (raw[key] !== input[key]) throw new Error(`v3 host success result ${key} mismatch`);
3765
3902
  }
3766
- if (!isRecord2(raw.externalRefs)) throw new Error("v3 host success result externalRefs must be an object");
3903
+ if (!isRecord3(raw.externalRefs)) throw new Error("v3 host success result externalRefs must be an object");
3767
3904
  canonicalJson(raw.output);
3768
3905
  canonicalJson(raw.externalRefs);
3769
3906
  return raw;
3770
3907
  }
3771
3908
  function parsePreparedHostInput(raw) {
3772
- if (!isRecord2(raw)) throw new Error("v3 host input sidecar must be an object");
3909
+ if (!isRecord3(raw)) throw new Error("v3 host input sidecar must be an object");
3773
3910
  const allowed = /* @__PURE__ */ new Set([
3774
3911
  "schemaVersion",
3775
3912
  "runId",
@@ -3890,12 +4027,12 @@ function assertDirectory(path, label) {
3890
4027
  function sha256(value) {
3891
4028
  return createHash5("sha256").update(value).digest("hex");
3892
4029
  }
3893
- function isRecord2(value) {
4030
+ function isRecord3(value) {
3894
4031
  return typeof value === "object" && value !== null && !Array.isArray(value);
3895
4032
  }
3896
4033
 
3897
4034
  // src/workflows/v3/shared-node-runtime.ts
3898
- function renderGoalFile(goal, resultSchema, loopCtx, nodeInstructions, hasWorkflowParams = false) {
4035
+ function renderGoalFile(goal, resultSchema, loopCtx, nodeInstructions, hasWorkflowParams = false, outputs) {
3899
4036
  const E = GOAL_ENV;
3900
4037
  const kinds = MANIFEST_FILE_KINDS.join(" | ");
3901
4038
  const [okStatus, failStatus] = MANIFEST_STATUSES;
@@ -3913,6 +4050,16 @@ function renderGoalFile(goal, resultSchema, loopCtx, nodeInstructions, hasWorkfl
3913
4050
  `List \`result.json\` in the manifest \`files\` array like any other product (its \`path\` is exactly "result.json"). A missing or schema-violating result.json blocks this node.`,
3914
4051
  ""
3915
4052
  ] : [];
4053
+ const artifactSection = outputs ? [
4054
+ "## Public artifact outputs (REQUIRED for this node)",
4055
+ "These stable output keys are part of the Workflow interface. Write every declared path under the output directory and list it in the success Manifest with the exact kind:",
4056
+ "",
4057
+ " " + JSON.stringify(outputs),
4058
+ "",
4059
+ "Manifest `name` is presentation-only and may be human-readable; downstream nodes resolve these products by output key \u2192 declared path.",
4060
+ "A missing path or mismatched kind blocks the run and requires a Workflow revision; ordinary retry is disabled.",
4061
+ ""
4062
+ ] : [];
3916
4063
  const loopSection = loopCtx ? [
3917
4064
  "## Loop context",
3918
4065
  `This node runs inside loop "${loopCtx.loopId}", iteration ${loopCtx.iteration} of at most ${loopCtx.maxIterations}.`,
@@ -3937,6 +4084,7 @@ function renderGoalFile(goal, resultSchema, loopCtx, nodeInstructions, hasWorkfl
3937
4084
  ...instructionsSection,
3938
4085
  ...paramsSection,
3939
4086
  ...loopSection,
4087
+ ...artifactSection,
3940
4088
  "## How to complete this node",
3941
4089
  "You are an autonomous agent completing exactly ONE botmux v3 workflow node.",
3942
4090
  "Work toward the goal above until it is done, then stop. Do NOT ask the user with interactive tools (they are disabled in this mode). If you genuinely need a human DECISION to proceed, use the human-ask escape hatch described below (also available as the `botmux-goal-ask` skill).",
@@ -3978,6 +4126,8 @@ function classifyTerminal(errorClass, opts) {
3978
4126
  switch (errorClass) {
3979
4127
  case "manifestInvalid":
3980
4128
  // agent wrote a bad manifest — a retry may fix it
4129
+ case "artifactContractInvalid":
4130
+ // definition/manifest public product mismatch
3981
4131
  case "resultInvalid":
3982
4132
  return "blocked";
3983
4133
  case "workerError":
@@ -5482,7 +5632,8 @@ async function runWorkflow(dag, deps, opts) {
5482
5632
  node.resultSchema,
5483
5633
  loopCtx,
5484
5634
  node.override?.systemPromptAppend,
5485
- !!workflowDataPath
5635
+ !!workflowDataPath,
5636
+ node.outputs
5486
5637
  )
5487
5638
  );
5488
5639
  const effSnap = mergeNodeCapability(botSnap, node.override);
@@ -5720,6 +5871,20 @@ async function runWorkflow(dag, deps, opts) {
5720
5871
  return;
5721
5872
  }
5722
5873
  }
5874
+ const artifactContract = validateManifestArtifactContract(node.outputs, verdict.manifest);
5875
+ if (!artifactContract.ok) {
5876
+ appendWorkerOutcome({
5877
+ type: "nodeBlocked",
5878
+ nodeId: node.id,
5879
+ ...instanceId ? { instanceId } : {},
5880
+ attemptId,
5881
+ errorClass: "artifactContractInvalid",
5882
+ errorCode: "OUTPUT_CONTRACT_VIOLATION",
5883
+ recovery: "reviseWorkflow",
5884
+ message: artifactContract.problems.join("; ")
5885
+ });
5886
+ return;
5887
+ }
5723
5888
  appendWorkerOutcome({
5724
5889
  type: "nodeSucceeded",
5725
5890
  nodeId: node.id,
@@ -5975,8 +6140,9 @@ ${request.reason}
5975
6140
  depends: bodyDef.depends.map((d) => ({ from: loopInstanceId(ref.loopId, ref.iteration, d.from) })),
5976
6141
  inputs: bodyDef.inputs.map((r) => ({
5977
6142
  from: loopInstanceId(ref.loopId, ref.iteration, r.from),
5978
- ...r.select ? { select: r.select } : {}
6143
+ ...r.select ? { select: r.select } : {},
5979
6144
  // P3: 实例化时保留 selector
6145
+ ...r.output !== void 0 ? { output: r.output } : {}
5980
6146
  }))
5981
6147
  };
5982
6148
  }
@@ -6176,7 +6342,7 @@ ${request.reason}
6176
6342
  });
6177
6343
  }
6178
6344
  const latestSuccess = (key) => [...events].reverse().find((e) => e.type === "nodeSucceeded" && (e.instanceId ?? e.nodeId) === key);
6179
- const pushFrom = (label, nodeId, filter) => {
6345
+ const pushFrom = (label, nodeId, filter, output) => {
6180
6346
  const key = snap.nodes.get(nodeId)?.effectiveInstanceId ?? nodeId;
6181
6347
  const succ = latestSuccess(key);
6182
6348
  if (!succ) return;
@@ -6186,6 +6352,7 @@ ${request.reason}
6186
6352
  if (filter && !filter(f)) continue;
6187
6353
  inputs.push({
6188
6354
  from: label,
6355
+ ...output ? { output } : {},
6189
6356
  ...succ.instanceId ? { instanceId: succ.instanceId } : {},
6190
6357
  name: f.name,
6191
6358
  path: join6(upstreamOutputDir, f.path),
@@ -6218,9 +6385,14 @@ ${request.reason}
6218
6385
  };
6219
6386
  const selectorMisses = [];
6220
6387
  const pushRef = (ref) => {
6221
- const filter = ref.select ? (f) => ref.select.name !== void 0 ? f.name === ref.select.name : f.path === ref.select.path : void 0;
6388
+ const source = nodesById.get(ref.from) ?? (loopRef ? nodesById.get(loopRef.loopId).body.nodes.find(
6389
+ (bodyNode) => loopInstanceId(loopRef.loopId, loopRef.iteration, bodyNode.id) === ref.from
6390
+ ) : void 0);
6391
+ const declaredOutputs = source?.type === "loop" ? source.body?.nodes.find((bodyNode) => bodyNode.id === source.output?.from)?.outputs : source?.outputs;
6392
+ const declared = ref.output ? declaredOutputs?.[ref.output] : void 0;
6393
+ const filter = declared ? (f) => f.path === declared.path : ref.select ? (f) => ref.select.name !== void 0 ? f.name === ref.select.name : f.path === ref.select.path : void 0;
6222
6394
  const before = inputs.length;
6223
- pushFrom(ref.from, ref.from, filter);
6395
+ pushFrom(ref.from, ref.from, filter, ref.output);
6224
6396
  if (ref.select && inputs.length === before) {
6225
6397
  selectorMisses.push({ from: ref.from, reason: "selectorMiss" });
6226
6398
  }