@uipath/maestro-builder-sdk 6.45.0 → 6.45.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api-index.md CHANGED
@@ -133,11 +133,11 @@ could hand you spans for a version you do not have.
133
133
  | `FilterCondition` | type | `dist/nodes/transform/flow/spec.d.ts:10-11` | Supporting types | Comparison a filter rule applies to a field. |
134
134
  | `FilterMatch` | type | `dist/nodes/transform/flow/spec.d.ts:8-9` | Supporting types | How a filter combines its rules: every rule must match, or any one. |
135
135
  | `FilterRule` | interface | `dist/nodes/transform/flow/spec.d.ts:12-24` | Supporting types | 3 field(s) |
136
- | `flow` | function | `dist/flow-sdk.d.ts:1762-1777` | Flow construction | Start building a flow with the given id. |
136
+ | `flow` | function | `dist/flow-sdk.d.ts:1763-1778` | Flow construction | Start building a flow with the given id. |
137
137
  | `FlowAction` | class | `dist/core/node-classes.d.ts:162-168` | Supporting types | A node placed as a step — pass an instance to `.step(name, …)`. |
138
138
  | `FlowActionSpec` | type | `dist/flow-sdk.d.ts:55-56` | Supporting types | What `.step()` accepts here: the shared actions plus this SDK's subflow. |
139
139
  | `FlowActionSpecs` | interface | `dist/core/actions.d.ts:905-942` | Supporting types | Every action kind, keyed by kind, to the body of its spec — the arm's fields other than `kind`. |
140
- | `FlowBuilder` | class | `dist/flow-sdk.d.ts:1382-1761` | Builders | 15 member(s) |
140
+ | `FlowBuilder` | class | `dist/flow-sdk.d.ts:1382-1762` | Builders | 15 member(s) |
141
141
  | `FlowLayout` | interface | `dist/flow-sdk.d.ts:807-820` | Supporting types | Designer layout for a flow, keyed by AUTHOR-level ids: node entries by step name (plus the trigger id and `end`), edge… |
142
142
  | `FlowNode` | class | `dist/core/node-classes.d.ts:126-161` | Supporting types | The abstract root of every definition-backed node the builder can place. |
143
143
  | `FlowResource` | class | `dist/core/node-classes.d.ts:176-183` | Supporting types | A node attached to another semantic node — an inline agent's tool, context, memory, or escalation. |
@@ -241,7 +241,7 @@ could hand you spans for a version you do not have.
241
241
  | `STICKY_NOTE_COLORS` | const | `dist/flow-sdk.d.ts:760-765` | Flow construction | The designer's sticky-note palette. |
242
242
  | `StickyNote` | interface | `dist/flow-sdk.d.ts:772-806` | Supporting types | A canvas sticky note — free text pinned to the diagram, for the humans reading it. |
243
243
  | `StickyNoteColor` | type | `dist/flow-sdk.d.ts:766-771` | Supporting types | A sticky note's swatch. |
244
- | `subflow` | function | `dist/flow-sdk.d.ts:1778-1817` | Flow construction | Run another flow as one step, and read back what it returns. |
244
+ | `subflow` | function | `dist/flow-sdk.d.ts:1779-1818` | Flow construction | Run another flow as one step, and read back what it returns. |
245
245
  | `SubflowSpec` | interface | `dist/flow-sdk.d.ts:47-54` | Supporting types | A step that runs a child flow. |
246
246
  | `summarize` | function | `dist/nodes/ai-patterns/flow/factories.d.ts:8-48` | Actions | Summarize a document with citations — the platform's **Summarize** node. |
247
247
  | `SummarizeInputs` | interface | `dist/nodes/ai-patterns/flow/spec.d.ts:8-40` | Option shapes | 3 field(s) |
@@ -105,7 +105,7 @@ not find it either.
105
105
  | `breakEnabled` | property | `LoopOptions` | `dist/flow-sdk.d.ts:541-545` |
106
106
  | `build` | method | `BpmnBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3581-3586` |
107
107
  | `build` | method | `CaseBuilder` | `dist/case/case-sdk.d.ts:1344-1349` |
108
- | `build` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1754-1760` |
108
+ | `build` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1755-1761` |
109
109
  | `businessRule` | property | `BuiltTask` | `dist/case/case-sdk.d.ts:542-543` |
110
110
  | `businessRule` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2577-2641` |
111
111
  | `businessRule` | method | `TaskBuilder` | `dist/case/case-sdk.d.ts:833-861` |
@@ -481,7 +481,7 @@ not find it either.
481
481
  | `inclusiveGateway` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:2305-2312` |
482
482
  | `inlineAgent` | property | `FlowActionSpecs` | `dist/nodes/agent/flow/spec.d.ts:443-446` |
483
483
  | `input` | method | `CaseBuilder` | `dist/case/case-sdk.d.ts:1235-1260` |
484
- | `input` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1695-1707` |
484
+ | `input` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1696-1708` |
485
485
  | `input` | property | `HumanTaskOpts` | `dist/bpmn/bpmn-sdk.d.ts:1381-1382` |
486
486
  | `input` | property | `OrchestratorOpts` | `dist/bpmn/bpmn-sdk.d.ts:1267-1268` |
487
487
  | `input` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3137-3145` |
@@ -746,7 +746,7 @@ not find it either.
746
746
  | `onTimer` | method | `ActivityBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3382-3390` |
747
747
  | `onTimer` | method | `ActivityBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3391-3399` |
748
748
  | `openTails` | property | `BuiltFlow` | `dist/flow-sdk.d.ts:875-880` |
749
- | `openTails` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1656-1694` |
749
+ | `openTails` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1656-1695` |
750
750
  | `operation` | property | `Aggregation` | `dist/nodes/transform/flow/spec.d.ts:37` |
751
751
  | `operation` | property | `ConnectorToolRef` | `dist/core/actions.d.ts:686-687` |
752
752
  | `operation` | property | `WaitConnectorPlaceholderSpec` | `dist/case/case-sdk.d.ts:386-387` |
@@ -762,7 +762,7 @@ not find it either.
762
762
  | `outcomes` | property | `EscalationRef` | `dist/core/actions.d.ts:851-856` |
763
763
  | `outcomes` | property | `HitlInputs` | `dist/nodes/hitl/flow/spec.d.ts:211-225` |
764
764
  | `output` | method | `CaseBuilder` | `dist/case/case-sdk.d.ts:1261-1273` |
765
- | `output` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1708-1743` |
765
+ | `output` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1709-1744` |
766
766
  | `output` | property | `RegistryTypeSpec` | `dist/bpmn/typed-node.d.ts:97` |
767
767
  | `output` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3146-3154` |
768
768
  | `outputColumns` | property | `BatchTransformInputs` | `dist/nodes/ai-patterns/flow/spec.d.ts:56-60` |
@@ -1151,7 +1151,7 @@ not find it either.
1151
1151
  | `value` | property | `TypedInputRow` | `dist/bpmn/typed-node.d.ts:250-251` |
1152
1152
  | `values` | property | `DataFabricCreateInputs` | `dist/nodes/data-fabric/flow/spec.d.ts:132-137` |
1153
1153
  | `var` | method | `CaseBuilder` | `dist/case/case-sdk.d.ts:1274-1298` |
1154
- | `var` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1744-1753` |
1154
+ | `var` | method | `FlowBuilder` | `dist/flow-sdk.d.ts:1745-1754` |
1155
1155
  | `var` | method | `ScopeBuilder` | `dist/bpmn/bpmn-sdk.d.ts:3128-3136` |
1156
1156
  | `var` | property | `TaskOutputBinding` | `dist/case/case-sdk.d.ts:531-532` |
1157
1157
  | `var` | property | `TypedOutputRow` | `dist/bpmn/typed-node.d.ts:296-297` |
package/dist/check.js CHANGED
@@ -651,7 +651,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
651
651
  level: 'error',
652
652
  code: 'SWITCH_CASE_ID',
653
653
  message: typeof caseId !== 'string' || caseId === ''
654
- ? `Switch "${s.name}" case ${JSON.stringify(c.value)} has an id that is not a non-empty string. Leave \`id\` out for the positional case_<n>.`
654
+ ? `Switch "${s.name}" case ${i + 1} has an id that is not a non-empty string. Leave \`id\` out for the positional case_<n>.`
655
655
  : `Switch "${s.name}" has two cases with the id "${caseId}", so both would leave from one 'case-${caseId}' port. Give one a different \`id\`.`,
656
656
  step: s.name,
657
657
  });
@@ -3267,8 +3267,8 @@ function checkArmScopedReads(built, diags) {
3267
3267
  */
3268
3268
  function checkStepRefs(built, diags, reservedIds = []) {
3269
3269
  const order = new Map();
3270
- /** Steps that live inside a port list — a path only that port reaches. */
3271
- const inPortList = new Set();
3270
+ /** Step name → the `.stepToList` whose port path it lives in — a path only that port reaches. */
3271
+ const portListOf = new Map();
3272
3272
  /** Step name → the id of the scope it lives in (a loop body is its own). */
3273
3273
  const scopeOf = new Map();
3274
3274
  /** Step name → the parallel arm it lives in, as `<parallel>#<index>`. */
@@ -3321,7 +3321,7 @@ function checkStepRefs(built, diags, reservedIds = []) {
3321
3321
  if (!order.has(name))
3322
3322
  order.set(name, seq);
3323
3323
  if (portList)
3324
- inPortList.add(name);
3324
+ portListOf.set(name, portList);
3325
3325
  scopeOf.set(name, scope);
3326
3326
  if (arm)
3327
3327
  armOf.set(name, arm);
@@ -3329,7 +3329,7 @@ function checkStepRefs(built, diags, reservedIds = []) {
3329
3329
  }
3330
3330
  seq++;
3331
3331
  if (s.kind === 'stepToList')
3332
- index(s.body, true, scope, arm);
3332
+ index(s.body, s, scope, arm);
3333
3333
  // A `.stepSwitch` routes EVERY declared exit, as `.switch` routes every
3334
3334
  // case, so its arms are not side paths: they inherit `portList`, as a
3335
3335
  // `.switch` case does. So a ref into a step of an arm is legal from another
@@ -3356,7 +3356,7 @@ function checkStepRefs(built, diags, reservedIds = []) {
3356
3356
  if (!order.has(fork.name))
3357
3357
  order.set(fork.name, seq);
3358
3358
  if (portList)
3359
- inPortList.add(fork.name);
3359
+ portListOf.set(fork.name, portList);
3360
3360
  scopeOf.set(fork.name, scope);
3361
3361
  if (arm)
3362
3362
  armOf.set(fork.name, arm);
@@ -3368,11 +3368,11 @@ function checkStepRefs(built, diags, reservedIds = []) {
3368
3368
  index(s.body, portList, s.name, arm);
3369
3369
  }
3370
3370
  };
3371
- index(built.steps, false, '', undefined);
3371
+ index(built.steps, undefined, '', undefined);
3372
3372
  // An entry-point prefix is a path of its own that may end at the shared body's
3373
3373
  // End, so its Ends count toward the duplicate-name rule below (#1231).
3374
3374
  for (const ep of built.entryPoints ?? [])
3375
- index(ep.steps, false, '', undefined);
3375
+ index(ep.steps, undefined, '', undefined);
3376
3376
  // Two `.return`s asking for the same End name with different content are two
3377
3377
  // End nodes, which compile refuses rather than renaming the second `<name>2`
3378
3378
  // (#1231). One End shared by several paths is spelled once, with every other
@@ -3425,14 +3425,14 @@ function checkStepRefs(built, diags, reservedIds = []) {
3425
3425
  }
3426
3426
  /** Every ref, with where it sits. */
3427
3427
  const found = [];
3428
- const collect = (steps, from, scope, arm, enclosing = []) => {
3428
+ const collect = (steps, from, scope, arm, enclosing = [], paths = []) => {
3429
3429
  let source = from;
3430
3430
  for (const [i, s] of steps.entries()) {
3431
3431
  if (s.kind === 'action' || s.kind === 'stepSwitch')
3432
3432
  source = s.name;
3433
3433
  if (s.kind === 'stepToRef') {
3434
3434
  found.push({
3435
- target: s.target, port: s.port, from: source, scope, arm, enclosing,
3435
+ target: s.target, port: s.port, from: source, scope, arm, enclosing, paths,
3436
3436
  // Only a default-port ref hands the path off, so only it can strand
3437
3437
  // what follows; a side exit leaves the chain running.
3438
3438
  after: s.port === 'output' ? steps.length - i - 1 : 0,
@@ -3440,16 +3440,16 @@ function checkStepRefs(built, diags, reservedIds = []) {
3440
3440
  continue;
3441
3441
  }
3442
3442
  if (s.kind === 'stepToList')
3443
- collect(s.body, source, scope, arm, enclosing);
3443
+ collect(s.body, source, scope, arm, enclosing, [...paths, s]);
3444
3444
  else if (s.kind === 'stepSwitch')
3445
- s.cases.forEach((c) => collect(c.body, s.name, scope, arm, enclosing));
3445
+ s.cases.forEach((c) => collect(c.body, s.name, scope, arm, enclosing, paths));
3446
3446
  else if (s.kind === 'branch') {
3447
- collect(s.then, source, scope, arm, enclosing);
3448
- collect(s.otherwise, source, scope, arm, enclosing);
3447
+ collect(s.then, source, scope, arm, enclosing, paths);
3448
+ collect(s.otherwise, source, scope, arm, enclosing, paths);
3449
3449
  }
3450
3450
  else if (s.kind === 'switch') {
3451
- s.cases.forEach((c) => collect(c.body, source, scope, arm, enclosing));
3452
- collect(s.default ?? [], source, scope, arm, enclosing);
3451
+ s.cases.forEach((c) => collect(c.body, source, scope, arm, enclosing, paths));
3452
+ collect(s.default ?? [], source, scope, arm, enclosing, paths);
3453
3453
  }
3454
3454
  else if (s.kind === 'parallel') {
3455
3455
  // Arms that all end in `.stepToRef(<one step>)` (or stop) have no Merge:
@@ -3461,11 +3461,11 @@ function checkStepRefs(built, diags, reservedIds = []) {
3461
3461
  const key = `${s.name}#${i}`;
3462
3462
  if (refJoinTarget !== undefined)
3463
3463
  refJoin.set(key, refJoinTarget);
3464
- collect(a, source, scope, key, enclosing);
3464
+ collect(a, source, scope, key, enclosing, paths);
3465
3465
  });
3466
3466
  }
3467
3467
  else if (s.kind === 'loop' || s.kind === 'doWhile')
3468
- collect(s.body, source, s.name, arm, [...enclosing, s.name]);
3468
+ collect(s.body, source, s.name, arm, [...enclosing, s.name], paths);
3469
3469
  }
3470
3470
  };
3471
3471
  /** The step every arm of a parallel refs instead of a Merge, by `<parallel>#<arm>`. */
@@ -3475,7 +3475,7 @@ function checkStepRefs(built, diags, reservedIds = []) {
3475
3475
  collect(built.steps, undefined, '', undefined);
3476
3476
  for (const ep of built.entryPoints ?? [])
3477
3477
  collect(ep.steps, ep.id, '', undefined);
3478
- for (const { target, from, after, scope, arm, enclosing } of found) {
3478
+ for (const { target, from, after, scope, arm, enclosing, paths } of found) {
3479
3479
  const where = from ? `after "${from}"` : 'at the start of the flow';
3480
3480
  const say = (level, code, message, suggestion) => {
3481
3481
  diags.push({ level, code, message, step: from ?? 'stepToRef', ...(suggestion ? { suggestion } : {}) });
@@ -3517,7 +3517,10 @@ function checkStepRefs(built, diags, reservedIds = []) {
3517
3517
  + `next iteration, or .break() to leave the loop.`, '.continue()');
3518
3518
  continue;
3519
3519
  }
3520
- if (inPortList.has(target) && !terminal) {
3520
+ // A ref inside the port path its target is on (a step's own `.onError`
3521
+ // rejoining the error path it sits on) runs only when that port fired too.
3522
+ const targetPath = portListOf.get(target);
3523
+ if (targetPath && !paths.includes(targetPath) && !terminal) {
3521
3524
  say('error', 'REF_INTO_PORT_LIST', `.stepToRef('${target}') ${where} targets a step inside another port's path. That path runs only when ITS `
3522
3525
  + `port fired — it is not somewhere another edge can jump into. Point at a step on the main path, or end `
3523
3526
  + `this one with .return() / .terminate(). (A named End or a Terminate is the exception: any path may end `
package/dist/decompile.js CHANGED
@@ -602,6 +602,36 @@ class Graph {
602
602
  && (!e.sourcePort.startsWith('outcome-') || e.sourcePort === primary)
603
603
  && !side?.has(e.sourcePort))));
604
604
  }
605
+ /**
606
+ * `true` when a decision or switch has an arm with no edge at all: a
607
+ * decision's `true` or `false`, a switch's `case-<id>`, or its `default` when
608
+ * the switch has one. Such an arm is a dead end — the flow stops there — so in
609
+ * the post-dominator walk it is a path to the scope's EXIT, exactly like a
610
+ * step with no successor (#1228). Without that, the arms "reconverged" at the
611
+ * wired arm's first step: `dec@true -> a`, `dec@false` unwired decompiled to
612
+ * `.branch("dec", …, (t) => t, (e) => e).step("a", …)`, and compiling it wired
613
+ * `dec@false -> a`, an edge the file never had.
614
+ *
615
+ * Only the two structured forks need this. Every other exit that may be
616
+ * unwired (`branch-*`, a non-primary `outcome-*`, a raw node's side exit) is
617
+ * a side path, not a successor, so it never takes part in a join; and a node
618
+ * whose declared exits are all wired (`fullyWiredExits`) has none unwired.
619
+ */
620
+ hasUnwiredArm(id) {
621
+ const node = this.byId.get(id);
622
+ if (!node)
623
+ return false;
624
+ const wired = new Set(this.outEdges(id).map((e) => e.sourcePort));
625
+ if (node.type === T.decision)
626
+ return !wired.has('true') || !wired.has('false');
627
+ if (node.type !== T.switch)
628
+ return false;
629
+ const inputs = (node.inputs ?? {});
630
+ const arms = (inputs.cases ?? []).map((c) => `case-${String(c?.id ?? '')}`);
631
+ if (inputs.hasDefault === true)
632
+ arms.push('default');
633
+ return arms.some((p) => !wired.has(p));
634
+ }
605
635
  /**
606
636
  * A multi-exit `rawNode`'s SIDE exits when not every exit is wired: each
607
637
  * declared exit except the one its main path leaves on (`contributionPort`,
@@ -1307,7 +1337,10 @@ function postDominators(entry, scope, graph, container) {
1307
1337
  // first step.
1308
1338
  const leaves = container !== undefined
1309
1339
  && graph.successorEdges(n).some((e) => e.targetNodeId === container);
1310
- const ss = s.length === 0 ? [EXIT] : leaves ? [...s, EXIT] : s;
1340
+ // A decision or switch arm with no edge is a dead end: a path to EXIT too,
1341
+ // or the fork "reconverges" at the wired arm's first step and compiling
1342
+ // wires the empty arm there (#1228).
1343
+ const ss = s.length === 0 ? [EXIT] : leaves || graph.hasUnwiredArm(n) ? [...s, EXIT] : s;
1311
1344
  succ.set(n, ss);
1312
1345
  for (const t of ss) {
1313
1346
  addP(n, t);
@@ -1848,15 +1881,15 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
1848
1881
  cursor = fork.next.cursor;
1849
1882
  continue;
1850
1883
  }
1851
- // A merge node with one edge in and one out is a pass-through the
1852
- // designer left in place (a join whose arms were later removed). The
1853
- // layout names it, so it is kept: it falls through to the regular step
1854
- // path below and comes back as the raw node of its own definition
1855
- // (#1310). Skipping it dropped the node, and compile then refused the
1856
- // `.layout()` key. A bare merge with several edges in (a join whose fork
1857
- // was not detected) is still skipped — best effort.
1884
+ // A merge node with one edge in and at most one out is a pass-through
1885
+ // (or a dead end) the designer left in place (a join whose arms were
1886
+ // later removed). The layout names it, so it is kept: it falls through
1887
+ // to the regular step path below and comes back as the raw node of its
1888
+ // own definition (#1310). Skipping it dropped the node, and compile then
1889
+ // refused the `.layout()` key. A bare merge with several edges in (a
1890
+ // join whose fork was not detected) is still skipped — best effort.
1858
1891
  const ins = ctx.graph.inEdges(node.id).filter((e) => scope.has(e.sourceNodeId));
1859
- if (outs.length !== 1 || ins.length !== 1) {
1892
+ if (outs.length > 1 || ins.length !== 1) {
1860
1893
  const next = continueOn(node, 'output', ctx, refInto);
1861
1894
  if (next.brk || next.cont) {
1862
1895
  segs.push(next.brk ? '.break()' : '.continue()');
@@ -428,8 +428,9 @@ export interface FlowNodeType<Spec> extends NodeType<Spec, FlowNodeContribution,
428
428
  readonly errorEnvelope?: string;
429
429
  /**
430
430
  * `true`: a path that ends on this node is capped with an End even under
431
- * `.openTails()`. A human task sets it: the product's `hitl-completed`
432
- * rule wants an edge after the task.
431
+ * `.openTails()`, unless the node already has a `completed` or `outcome-*`
432
+ * edge. A human task sets it: the product's `hitl-completed` rule wants an
433
+ * edge after the task.
433
434
  */
434
435
  readonly capsOpenTail?: boolean;
435
436
  /**
@@ -1671,12 +1671,13 @@ declare class FlowBuilder extends StepList {
1671
1671
  *
1672
1672
  * It changes only paths that fall off. `.return(...)`, `.terminate(...)`, a
1673
1673
  * `.stepSwitch` exit with no arm and a human-task outcome with
1674
- * `action: 'End'` still emit their End. A human task that ends a path is
1675
- * still capped with an End (the product's `hitl-completed` rule wants an edge
1676
- * after it), so a file whose human task has no outgoing edge gains one on a
1677
- * round trip. A path left open maps no outputs, so a flow that declares
1678
- * `.output(...)` answers nothing on that path. Use `.return(...)` on the
1679
- * paths that must answer.
1674
+ * `action: 'End'` still emit their End. A human task that ends a path with
1675
+ * no outcome wired is still capped with an End (the product's
1676
+ * `hitl-completed` rule wants an edge after it), so a file whose human task
1677
+ * has no outgoing edge gains one on a round trip; one whose other outcome is
1678
+ * wired keeps the open one open. A path left open maps no outputs, so a flow
1679
+ * that declares `.output(...)` answers nothing on that path. Use
1680
+ * `.return(...)` on the paths that must answer.
1680
1681
  *
1681
1682
  * A subflow's child flow has its own setting; it does not inherit the
1682
1683
  * caller's.
package/dist/flow-sdk.js CHANGED
@@ -1325,12 +1325,13 @@ class FlowBuilder extends StepList {
1325
1325
  *
1326
1326
  * It changes only paths that fall off. `.return(...)`, `.terminate(...)`, a
1327
1327
  * `.stepSwitch` exit with no arm and a human-task outcome with
1328
- * `action: 'End'` still emit their End. A human task that ends a path is
1329
- * still capped with an End (the product's `hitl-completed` rule wants an edge
1330
- * after it), so a file whose human task has no outgoing edge gains one on a
1331
- * round trip. A path left open maps no outputs, so a flow that declares
1332
- * `.output(...)` answers nothing on that path. Use `.return(...)` on the
1333
- * paths that must answer.
1328
+ * `action: 'End'` still emit their End. A human task that ends a path with
1329
+ * no outcome wired is still capped with an End (the product's
1330
+ * `hitl-completed` rule wants an edge after it), so a file whose human task
1331
+ * has no outgoing edge gains one on a round trip; one whose other outcome is
1332
+ * wired keeps the open one open. A path left open maps no outputs, so a flow
1333
+ * that declares `.output(...)` answers nothing on that path. Use
1334
+ * `.return(...)` on the paths that must answer.
1334
1335
  *
1335
1336
  * A subflow's child flow has its own setting; it does not inherit the
1336
1337
  * caller's.
package/dist/merge.js CHANGED
@@ -262,23 +262,83 @@ function withDisplayEdits(node, orig, e, base) {
262
262
  * The original with the author's edits applied, by the delta rule of
263
263
  * merge-variables.ts: where `e` equals the baseline `b` the original's value
264
264
  * stays (absent stays absent), and inside objects all three have, the rule
265
- * recurses key by key. Lists are taken whole, and so is a changed expression
266
- * value, whose `type` says how to read its `expression`: decompile turns a
267
- * legacy `{ type: 'literal', expression: '{{ $vars.x }}' }` agent input into a
268
- * `jsExpression`, so an edited reference merged key by key would keep
269
- * `literal` and hand the agent the text. Applied to an edited node, a
265
+ * recurses key by key. A list whose entries have ids goes entry by entry
266
+ * ({@link withListEdits}), so relabelling one form field does not rewrite the
267
+ * others in compile's spelling, and an edited condition keeps the original's
268
+ * spelling ({@link inOriginalSpelling}). Any other list is taken whole, and so is a
269
+ * changed expression value, whose `type` says how to read its `expression`:
270
+ * decompile turns a legacy `{ type: 'literal', expression: '{{ $vars.x }}' }`
271
+ * agent input into a `jsExpression`, so an edited reference merged key by key
272
+ * would keep `literal` and hand the agent the text. Applied to an edited node, a
270
273
  * one-input edit to a connector keeps the rest of its `detail` as written, and
271
274
  * compile's `model`, derived `outputs` and other spellings stay out of it.
272
275
  */
273
276
  function withEdits(o, b, e) {
274
277
  if (stableJson(e) === stableJson(b))
275
278
  return o === undefined ? o : jsonClone(o);
279
+ const respelled = inOriginalSpelling(o, b, e);
280
+ if (respelled !== undefined)
281
+ return respelled;
282
+ if (Array.isArray(o) && Array.isArray(b) && Array.isArray(e))
283
+ return withListEdits(o, b, e) ?? jsonClone(e);
276
284
  if (!isRecord(o) || !isRecord(b) || !isRecord(e) || isExpressionValue(o) || isExpressionValue(e)) {
277
285
  return e === undefined ? e : jsonClone(e);
278
286
  }
279
287
  return mergeObject(o, b, e, withEdits);
280
288
  }
281
289
  const isExpressionValue = (v) => typeof v.type === 'string' && Object.hasOwn(v, 'expression');
290
+ /**
291
+ * An edited condition `e` in the original's spelling, where compile writes it
292
+ * bare: Studio Web writes a decision or switch condition as `=js: <x>` or a
293
+ * `{ type: 'jsExpression', expression: <x> }` envelope, compile as `<x>` (#1308).
294
+ * `undefined` when `o` is not one of those spellings of `b`.
295
+ */
296
+ function inOriginalSpelling(o, b, e) {
297
+ if (typeof b !== 'string' || typeof e !== 'string' || e.startsWith('='))
298
+ return undefined;
299
+ if (typeof o === 'string') {
300
+ const prefix = /^=js:\s*/.exec(o)?.[0];
301
+ return prefix && o.slice(prefix.length) === b ? prefix + e : undefined;
302
+ }
303
+ return isRecord(o) && o.type === 'jsExpression' && o.expression === b ? { ...jsonClone(o), expression: e } : undefined;
304
+ }
305
+ /** What `after` changed from `before`, key by key; `undefined` when nothing. */
306
+ function changedFrom(before, after) {
307
+ if (stableJson(after) === stableJson(before))
308
+ return undefined;
309
+ if (!isRecord(before) || !isRecord(after) || isExpressionValue(before) || isExpressionValue(after))
310
+ return after;
311
+ const out = {};
312
+ for (const k of Object.keys(after)) {
313
+ const d = changedFrom(before[k], after[k]);
314
+ if (d !== undefined)
315
+ out[k] = d;
316
+ }
317
+ return out;
318
+ }
319
+ /** The `id` of every entry, when every entry is an object with its own string `id`. */
320
+ function entryIds(list) {
321
+ const ids = list.map((x) => (isRecord(x) && typeof x.id === 'string' ? x.id : undefined));
322
+ return ids.every((id) => id !== undefined) && new Set(ids).size === ids.length ? ids : undefined;
323
+ }
324
+ /**
325
+ * {@link withEdits} over a list whose entries all have their own `id` (a human
326
+ * task's form fields, a switch's cases), entry by entry: an entry the author
327
+ * did not change stays as the original wrote it. `undefined` when the lists
328
+ * cannot be lined up that way (no ids, or the round trip changed which entries
329
+ * there are), and the caller takes the edited list whole.
330
+ */
331
+ function withListEdits(o, b, e) {
332
+ const [oi, bi, ei] = [entryIds(o), entryIds(b), entryIds(e)];
333
+ if (!oi || !bi || !ei || oi.length !== bi.length || !oi.every((id) => bi.includes(id)))
334
+ return undefined;
335
+ const at = (list, ids, id) => list[ids.indexOf(id)];
336
+ // The author kept the entries and their order: keep the original's order too.
337
+ const order = ei.join('\0') === bi.join('\0') ? oi : ei;
338
+ return order.map((id) => (bi.includes(id)
339
+ ? withEdits(at(o, oi, id), at(b, bi, id), at(e, ei, id))
340
+ : jsonClone(at(e, ei, id))));
341
+ }
282
342
  /**
283
343
  * Whether an edited node can merge field by field onto its original: all three
284
344
  * have one type and one `typeVersion`. A node whose version the edit or the
@@ -369,6 +429,9 @@ function mergeFlowFindings(original, edited, options) {
369
429
  // references are the original's now, not the recompile's
370
430
  // (see withoutRowsMintedForKeptNodes).
371
431
  const keptFromOriginal = new Set();
432
+ // The edited nodes merged field by field, with what the merge took from the
433
+ // edit: their other fields are the original's, and so are the bindings those need.
434
+ const fieldwise = new Map();
372
435
  /** Root node ids taken from `edited` that the original also has (the author edited them). */
373
436
  const editedIds = new Set();
374
437
  for (const e of edited.nodes ?? []) {
@@ -385,9 +448,14 @@ function mergeFlowFindings(original, edited, options) {
385
448
  // field (withEdits), else the edited node; then re-attach original layout.
386
449
  // An EMBEDDED agent stays embedded: the original plus the author's change
387
450
  // to the node, never the edited shell whole (#1220, merge-embedded-agent.ts).
451
+ const byField = !keepsEmbeddedAgent(orig, e) && !!orig && !!base && mergesByField(orig, base, e);
388
452
  const taken = keepsEmbeddedAgent(orig, e) ? mergeEmbeddedAgentNode(orig, e, base)
389
- : orig && base && mergesByField(orig, base, e) ? withEdits(orig, base, e)
453
+ : byField ? withEdits(orig, base, e)
390
454
  : jsonClone(e);
455
+ // Against the merged node, not the edit: a key the original lacks comes
456
+ // from the edit whole, unchanged children and their bindings included.
457
+ if (byField)
458
+ fieldwise.set(e.id, changedFrom(orig, taken));
391
459
  const node = orig && base ? withDisplayEdits(taken, orig, e, base) : taken;
392
460
  const placed = withOriginalSource(withOriginalLayout(node, orig, layoutNodesOf(original)), orig, base);
393
461
  finalNodes.push(placed);
@@ -493,9 +561,10 @@ function mergeFlowFindings(original, edited, options) {
493
561
  // ── Bindings: union by what each row binds (original wins), so resources for
494
562
  // newly-added nodes come from `edited` while the original's stay intact.
495
563
  // A row the no-edit recompile minted only for nodes the merge kept from the
496
- // original is not carried (#1133). ──
564
+ // original, or for fields of an edited node the edit did not change, is
565
+ // not carried (#1133). ──
497
566
  result.bindings = mergeBindings(original.bindings, edited.bindings);
498
- result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal);
567
+ result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal, fieldwise);
499
568
  // metadata / solutionId / projectId / layout / runtime were carried by the
500
569
  // initial clone of `original` and are intentionally left untouched, apart
501
570
  // from the `layout.nodes` entries of removed nodes (dropped above) and added
@@ -733,11 +802,16 @@ function mergeBindings(orig, edited) {
733
802
  * only when no other row has that name, and for a `""`-keyed row only when the
734
803
  * node spells none of the other same-named rows' keys.
735
804
  *
805
+ * An edited node merged field by field (`fieldwise`, with what the merge took
806
+ * from the edit) is the original's in every other field, so it needs such a row
807
+ * only when what it took references it. A classic-CLI IxP extract
808
+ * whose `fileRef` the author edits still carries its model inline.
809
+ *
736
810
  * Kept: a row no edited node references (one a sidecar file uses, say), a row
737
811
  * a subflow node or an edited node references, and every row without a baseline.
738
812
  */
739
- function withoutRowsMintedForKeptNodes(rows, original, edited, baseline, keptFromOriginal) {
740
- if (!baseline || !rows || keptFromOriginal.size === 0)
813
+ function withoutRowsMintedForKeptNodes(rows, original, edited, baseline, keptFromOriginal, fieldwise) {
814
+ if (!baseline || !rows || (keptFromOriginal.size === 0 && fieldwise.size === 0))
741
815
  return rows;
742
816
  const contextByType = new Map();
743
817
  for (const d of (edited.definitions ?? [])) {
@@ -745,9 +819,9 @@ function withoutRowsMintedForKeptNodes(rows, original, edited, baseline, keptFro
745
819
  continue;
746
820
  contextByType.set(d.nodeType, (contextByType.get(d.nodeType) ?? '') + JSON.stringify(d.model.context));
747
821
  }
748
- const describe = (n, kept) => ({ kept, text: JSON.stringify(n) + (contextByType.get(n.type) ?? '') });
822
+ const describe = (n, kept, changed) => ({ kept, changed, text: JSON.stringify(n) + (contextByType.get(n.type) ?? '') });
749
823
  const nodes = [
750
- ...(edited.nodes ?? []).map((n) => describe(n, keptFromOriginal.has(n.id))),
824
+ ...(edited.nodes ?? []).map((n) => describe(n, keptFromOriginal.has(n.id), fieldwise.has(n.id) ? JSON.stringify(fieldwise.get(n.id) ?? {}) : undefined)),
751
825
  ...Object.values(edited.subflows ?? {}).flatMap((sf) => (sf?.nodes ?? []).map((n) => describe(n, false))),
752
826
  ];
753
827
  const spelled = (s) => JSON.stringify(s).slice(1, -1);
@@ -774,6 +848,7 @@ function withoutRowsMintedForKeptNodes(rows, original, edited, baseline, keptFro
774
848
  : others.length === 0;
775
849
  };
776
850
  const referencers = nodes.filter((n) => references(n.text));
777
- return !(referencers.length > 0 && referencers.every((n) => n.kept));
851
+ const needs = (n) => !n.kept && (n.changed === undefined || references(n.changed));
852
+ return !(referencers.length > 0 && !referencers.some(needs));
778
853
  });
779
854
  }
package/dist/serialize.js CHANGED
@@ -2217,13 +2217,19 @@ export function serialize(built, opts = {}) {
2217
2217
  *
2218
2218
  * All of them by default. Under `.openTails()` (#1142) only the tails
2219
2219
  * of a node whose type sets `capsOpenTail` (a human task: the product's
2220
- * `hitl-completed` rule wants an edge after it). Every other tail is left
2221
- * open, which is the shape the classic CLI and the canvas write.
2220
+ * `hitl-completed` rule wants an edge after it) and that has no outcome
2221
+ * edge yet: the rule is met by any `completed` or `outcome-*` edge, so a
2222
+ * task whose Reject is wired keeps its Approve open. Every other tail is
2223
+ * left open, which is the shape the classic CLI and the canvas write.
2222
2224
  */
2223
2225
  function tailsToCap(scope, tails) {
2224
2226
  if (!scope.openTails)
2225
2227
  return [...tails];
2226
- return tails.filter((t) => capsOpenTail.has(t.node));
2228
+ const isOutcome = (port) => port === 'completed' || String(port).startsWith('outcome-');
2229
+ // A `.stepToRef` edge is still pending here: resolveRejoins runs after.
2230
+ const hasOutcomeEdge = (node) => scope.edges.some((e) => e.sourceNodeId === node && isOutcome(e.sourcePort))
2231
+ || scope.pendingEdges.some((p) => p.from.node === node && isOutcome(p.from.port));
2232
+ return tails.filter((t) => capsOpenTail.has(t.node) && !hasOutcomeEdge(t.node));
2227
2233
  }
2228
2234
  /**
2229
2235
  * Emit a child flow into its own scope and register it under the calling
@@ -3178,7 +3184,7 @@ export function serialize(built, opts = {}) {
3178
3184
  // DYNAMIC, one per case, so they are synthesized here instead of copied
3179
3185
  // from the definition. The emitted shape mirrors the platform's
3180
3186
  // `walkSwitchStatement` (the canonical emitter) exactly:
3181
- // inputs.cases[] = { id, label, expression }, inputs.hasDefault,
3187
+ // inputs.cases[] = { id, label, expression }, inputs.hasDefault (1.0.0),
3182
3188
  // source ports `case-<id>` per case + `default` for the default arm.
3183
3189
  // Each case `expression` is a full boolean condition (the node evaluates
3184
3190
  // them in order and takes the first truthy one), auto-evaluated as JS by
@@ -3197,7 +3203,9 @@ export function serialize(built, opts = {}) {
3197
3203
  ? renameRefs(c.when.js, scope.rename)
3198
3204
  : `${renameRefs(step.on?.js ?? 'undefined', scope.rename)} === ${caseLiteral(c.value)}`,
3199
3205
  }));
3200
- node.inputs = { cases, hasDefault: step.default !== undefined };
3206
+ // Switch 1.1 dropped `hasDefault`: its `default` handle is always there.
3207
+ const declared = bundledNodeDefs.get(id)?.inputDefinition?.properties;
3208
+ node.inputs = { cases, ...(!declared || Object.hasOwn(declared, 'hasDefault') ? { hasDefault: step.default !== undefined } : {}) };
3201
3209
  scope.nodes.push(node);
3202
3210
  for (const t of tails)
3203
3211
  addEdge(scope, t, id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.45.0",
3
+ "version": "6.45.2",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "618bc7abd8705bcc85c85d6414dd29be5e9c3ec0"
96
+ "gitref": "c17529ad31858ac9f6aa0858735dcbe0e5e11e17"
97
97
  }