@uipath/maestro-builder-sdk 6.19.0 → 6.20.1

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 (41) hide show
  1. package/dist/api-index.md +107 -107
  2. package/dist/api-members.md +358 -356
  3. package/dist/check.js +181 -45
  4. package/dist/cli/commands/flow.js +3 -0
  5. package/dist/compile-cli.js +51 -0
  6. package/dist/core/actions.d.ts +13 -5
  7. package/dist/core/actions.js +1 -1
  8. package/dist/core/binding-identity.d.ts +22 -0
  9. package/dist/core/binding-identity.js +28 -0
  10. package/dist/core/context-index.d.ts +34 -0
  11. package/dist/core/context-index.js +38 -0
  12. package/dist/core/definitionless-node-types.d.ts +16 -0
  13. package/dist/core/definitionless-node-types.js +16 -0
  14. package/dist/core/form-field-type.d.ts +33 -0
  15. package/dist/core/form-field-type.js +30 -0
  16. package/dist/core/hitl-routing.d.ts +8 -0
  17. package/dist/core/hitl-routing.js +17 -1
  18. package/dist/core/process-resource-sort-order.d.ts +40 -0
  19. package/dist/core/process-resource-sort-order.js +44 -0
  20. package/dist/core/wire-expr.d.ts +27 -0
  21. package/dist/core/wire-expr.js +34 -0
  22. package/dist/decompile-cli.js +11 -1
  23. package/dist/decompile.d.ts +8 -3
  24. package/dist/decompile.js +680 -104
  25. package/dist/flow-expr-check.js +1 -1
  26. package/dist/flow-sdk.d.ts +33 -1
  27. package/dist/flow-sdk.js +21 -2
  28. package/dist/flow-step-tree.js +2 -1
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.js +1 -0
  31. package/dist/merge-cli.js +25 -8
  32. package/dist/merge-roundtrip.d.ts +81 -0
  33. package/dist/merge-roundtrip.js +404 -0
  34. package/dist/merge.d.ts +35 -6
  35. package/dist/merge.js +124 -24
  36. package/dist/node-placement.d.ts +93 -0
  37. package/dist/node-placement.js +163 -0
  38. package/dist/nodes/transform/flow/node-type.js +2 -1
  39. package/dist/registry/uip/pipeline-front-door.js +5 -3
  40. package/dist/serialize.js +112 -61
  41. package/package.json +2 -2
package/dist/serialize.js CHANGED
@@ -10,9 +10,10 @@ import { readFileSync } from 'node:fs';
10
10
  import { createHash } from 'node:crypto';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { dirname, join } from 'node:path';
13
- import { BUNDLED_DEFINITIONS } from './flow-definitions.js';
13
+ import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
14
14
  import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
15
15
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
16
+ import { contextIndexType } from './core/context-index.js';
16
17
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
17
18
  import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
18
19
  import { planContributionBindings } from './core/contribution-bindings.js';
@@ -24,7 +25,9 @@ import { connectionConnectors } from './bindings-v2.js';
24
25
  import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
25
26
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
26
27
  import { genericEventObjectRefusal } from './core/event-checks.js';
27
- import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
28
+ import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeIdOf, outcomeSlug } from './core/hitl-routing.js';
29
+ import { STICKY_NOTE_TYPE, STICKY_NOTE_TYPE_VERSION } from './core/definitionless-node-types.js';
30
+ import { placeholderPosition } from './node-placement.js';
28
31
  import { declaredExits, hasExits } from './core/step-ports.js';
29
32
  import { stopsAtEnd } from './flow-step-tree.js';
30
33
  import { assertNodeEnvelope } from './core/node-envelope.js';
@@ -55,6 +58,7 @@ import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-vari
55
58
  import { prepareCommand } from './core/cli-spelling.js';
56
59
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
57
60
  import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
61
+ import { resourceNodeSortOrder } from './core/process-resource-sort-order.js';
58
62
  import { types } from './core/expr.js';
59
63
  import { ownEntry } from './core/own-entry.js';
60
64
  import { jsonClone } from './core/json-file.js';
@@ -206,6 +210,9 @@ function processResourceLocation(spec) {
206
210
  * `icon: 'rpa'`, the `bpmn:ServiceTask` / `Orchestrator.StartJob` / `v2` model,
207
211
  * `section: 'Published'` (42/45), `debug.runtime: 'bpmnEngine'`, and the
208
212
  * `<bindings.name>` / `<bindings.folderPath>` context placeholders.
213
+ * `sortOrder` is not one of those constants any more: flow-core derives it from
214
+ * the category, and renumbered after 07-29, so it comes from
215
+ * `resourceNodeSortOrder` (#1045).
209
216
  *
210
217
  * Two fields are derived from the author's call rather than from the tenant, and
211
218
  * that limit is real:
@@ -236,7 +243,7 @@ function rpaWorkflowDef(spec) {
236
243
  runtimeConstraints: { exclude: ['api-function'] },
237
244
  description: loc.description,
238
245
  tags: loc.tags,
239
- sortOrder: 530,
246
+ sortOrder: resourceNodeSortOrder('rpa-workflow'),
240
247
  supportsErrorHandling: true,
241
248
  display: { label: spec.name, icon: 'rpa' },
242
249
  handleConfiguration: [
@@ -316,7 +323,7 @@ const API_WORKFLOW_PREFIX = 'uipath.core.api-workflow.';
316
323
  * AGREE on, not invented: 42 `uipath.core.api-workflow.*` instances across 42
317
324
  * dated `skill-flow-api-workflow` artifacts (2026-05-18 → 07-29), cross-checked
318
325
  * against `registry get` for today's workflow. Every one is unanimous —
319
- * `category: 'api-workflow'`, `sortOrder: 510`, `supportsErrorHandling`,
326
+ * `category: 'api-workflow'`, `sortOrder: 510` (now 640, see `resourceNodeSortOrder`, #1045), `supportsErrorHandling`,
320
327
  * `icon: 'api'`, the `bpmn:ServiceTask` / `Orchestrator.ExecuteApiWorkflowAsync` /
321
328
  * `v2` model, `section: 'Published'` (37/42; the 5 without are the in-solution
322
329
  * shape, a bare-GUID resourceKey — rpa's 3/45 split again), `resourceSubType:
@@ -354,7 +361,7 @@ function apiWorkflowDef(spec) {
354
361
  runtimeConstraints: { exclude: ['api-function'] },
355
362
  description: `(${spec.folderPath})`,
356
363
  tags: [],
357
- sortOrder: 510,
364
+ sortOrder: resourceNodeSortOrder('api-workflow'),
358
365
  supportsErrorHandling: true,
359
366
  display: { label: spec.name, icon: 'api' },
360
367
  handleConfiguration: [
@@ -435,7 +442,7 @@ function publishedFunctionDef(spec) {
435
442
  category: 'function',
436
443
  description: `(${spec.folderPath})`,
437
444
  tags: [],
438
- sortOrder: 512,
445
+ sortOrder: resourceNodeSortOrder('function'),
439
446
  supportsErrorHandling: true,
440
447
  display: { label: spec.name, icon: 'function' },
441
448
  handleConfiguration: [
@@ -513,7 +520,8 @@ function publishedFunctionDef(spec) {
513
520
  *
514
521
  * - `uip maestro flow registry get uipath.core.agentic-process.<key>` for ALL
515
522
  * THREE agentic processes on the tenant (2026-07-29): `category:
516
- * 'agentic-process'`, `sortOrder: 507`, `supportsErrorHandling`, `icon:
523
+ * 'agentic-process'`, `sortOrder: 507` (now 610 for bpmn, 600 flow, 620 case:
524
+ * `resourceNodeSortOrder`, #1045), `supportsErrorHandling`, `icon:
517
525
  * 'agentic-process'`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
518
526
  * `Orchestrator.StartAgenticProcess` / `v2` model, `section: 'Published'`,
519
527
  * `resourceSubType: 'ProcessOrchestration'`, `orchestratorType:
@@ -556,7 +564,7 @@ function agenticProcessDef(spec) {
556
564
  runtimeConstraints: { exclude: ['api-function'] },
557
565
  description: loc.description,
558
566
  tags: loc.tags,
559
- sortOrder: 507,
567
+ sortOrder: resourceNodeSortOrder(form.category),
560
568
  supportsErrorHandling: true,
561
569
  display: { label: spec.name, icon: form.icon },
562
570
  handleConfiguration: [
@@ -637,7 +645,8 @@ const INLINE_AGENT_TYPE = 'uipath.agent.autonomous';
637
645
  * checkable rather than asserted:
638
646
  *
639
647
  * - `uip maestro flow registry get uipath.core.agent.<key>` for ALL NINE agents on
640
- * the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505`,
648
+ * the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505` (510 since flow-core's
649
+ * renumber: the 54 runs from 09-01 below carry 510; `resourceNodeSortOrder`, #1045),
641
650
  * `supportsErrorHandling: true`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
642
651
  * `Orchestrator.StartAgentJob` / `v2` model, `section: 'Published'`,
643
652
  * `resourceSubType: 'Agent'`, `orchestratorType: 'agent'`,
@@ -691,7 +700,7 @@ function agentDef(spec) {
691
700
  runtimeConstraints: { exclude: ['api-function'] },
692
701
  description: loc.description,
693
702
  tags: loc.tags,
694
- sortOrder: 505,
703
+ sortOrder: resourceNodeSortOrder('agent'),
695
704
  supportsErrorHandling: true,
696
705
  display: {
697
706
  label: spec.name,
@@ -836,21 +845,6 @@ function contextIndexDef(spec) {
836
845
  },
837
846
  };
838
847
  }
839
- /**
840
- * The node type for a context index: `…context.index.<slug>.<uuid>`.
841
- *
842
- * The slug is the index's display name lower-cased with every run of
843
- * non-alphanumerics collapsed to a single `-`. Measured against the platform's own
844
- * type for "Billing Dispute SOP Index" → `billing-dispute-sop-index`, and pinned
845
- * by a test — because a derived identifier that disagrees with the registry's is a
846
- * node type the tenant has never heard of.
847
- */
848
- function contextIndexSlug(name) {
849
- return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
850
- }
851
- function contextIndexType(spec) {
852
- return `uipath.agent.resource.context.index.${contextIndexSlug(spec.name)}.${spec.id}`;
853
- }
854
848
  /**
855
849
  * Render an index's retrieval query into the platform's MODE-TAGGED object.
856
850
  *
@@ -2958,9 +2952,14 @@ function connectionResourceBindings(spec, existing) {
2958
2952
  * create, or the author's `returns` for a create-and-wait. Without a schema a
2959
2953
  * downstream read is unresolvable.
2960
2954
  */
2961
- function queueItemDef(spec, stepName, ids) {
2955
+ function queueItemDef(spec, stepName, ids, version) {
2962
2956
  const nodeType = spec.wait ? NODE_TYPE.queueCreateAndWait : NODE_TYPE.queueCreate;
2963
- const base = defByType.get(nodeType);
2957
+ // The template at the AUTHOR'S version, when the bundle has it. Starting from
2958
+ // the default and handing it to makeNode with the pin refused every other
2959
+ // bundled version — the bug #988 fixed for inline agents (#1128). An
2960
+ // unbundled pin falls back to the default, and makeNode's exact-version
2961
+ // refusal names what is bundled.
2962
+ const base = (version !== undefined ? DEFS[`${nodeType}@${version}`] : undefined) ?? defByType.get(nodeType);
2964
2963
  if (!base)
2965
2964
  throw new Error(`no bundled definition for "${nodeType}"`);
2966
2965
  const def = jsonClone(base);
@@ -3199,7 +3198,7 @@ function hitlInputs(inputs, nodeId, flowId, label, rename) {
3199
3198
  const outcomes = (inputs?.outcomes ?? []).map((o, i) => {
3200
3199
  const name = typeof o === 'string' ? o : o?.name;
3201
3200
  const action = typeof o === 'string' ? 'Continue' : (o?.action ?? 'Continue');
3202
- return { id: outcomeId(name, i), name, type: 'string', isPrimary: i === 0, action };
3201
+ return { id: outcomeIdOf(o, i), name, type: 'string', isPrimary: i === 0, action };
3203
3202
  });
3204
3203
  const properties = {};
3205
3204
  for (const f of inputs?.fields ?? []) {
@@ -3475,8 +3474,6 @@ const NO_RENAME = { nodeIds: new Map(), errorEnvelopes: new Map() };
3475
3474
  const ERROR_ENVELOPE_VAR = {
3476
3475
  [NODE_TYPE.httpV2]: 'output',
3477
3476
  };
3478
- /** The `typeVersion` Studio Web stamps on a sticky note. The type has no manifest to take it from. */
3479
- const STICKY_NOTE_TYPE_VERSION = '1.0.0';
3480
3477
  /**
3481
3478
  * Append the flow's `.stickyNotes()` as real `stickyNote` nodes.
3482
3479
  *
@@ -3527,7 +3524,7 @@ function emitStickyNotes(built, scope, claimNoteId) {
3527
3524
  // `flow validate`, manifest or not (#869).
3528
3525
  scope.nodes.push({
3529
3526
  id: emittedId,
3530
- type: 'stickyNote',
3527
+ type: STICKY_NOTE_TYPE,
3531
3528
  typeVersion: STICKY_NOTE_TYPE_VERSION,
3532
3529
  ui: {
3533
3530
  position: { x: note.x, y: note.y },
@@ -3849,11 +3846,6 @@ export function resolveContributionDefinition(ref, requestedVersion, where) {
3849
3846
  }
3850
3847
  throw new Error(`${where}: unknown DefinitionReference source "${ref.source}".`);
3851
3848
  }
3852
- /**
3853
- * The forward port of a contribution-lowered node, derived from its
3854
- * definition's handles: the source handle marked `isDefaultForType`, else the
3855
- * first non-error source handle, else the conventional `output`.
3856
- */
3857
3849
  /**
3858
3850
  * Whether `def` declares `port` as a forward source handle: an exact id, or a
3859
3851
  * templated one (`outcome-{item.id}` declares every `outcome-<slug>`). `error` is
@@ -3873,10 +3865,17 @@ function declaresForwardPort(def, port) {
3873
3865
  return port.length > head.length + tail.length && port.startsWith(head) && port.endsWith(tail);
3874
3866
  });
3875
3867
  }
3868
+ /**
3869
+ * Forward port: the first concrete source handle (`isDefaultForType` wins), else `output`;
3870
+ * `undefined` when every forward source handle is templated (`case-{item.id}`).
3871
+ */
3876
3872
  function contributionPort(def) {
3877
3873
  const handles = (def?.handleConfiguration ?? []).flatMap((g) => g?.handles ?? []);
3878
3874
  const sources = handles.filter((h) => h?.type === 'source' && h?.id !== 'error');
3879
- return sources.find((h) => h?.isDefaultForType)?.id ?? sources[0]?.id ?? 'output';
3875
+ const concrete = sources.filter((h) => typeof h?.id === 'string' && !h.id.includes('{'));
3876
+ if (concrete.length === 0 && sources.length > 0)
3877
+ return undefined;
3878
+ return concrete.find((h) => h?.isDefaultForType)?.id ?? concrete[0]?.id ?? 'output';
3880
3879
  }
3881
3880
  /**
3882
3881
  * Render a switch case value as a JS literal for the case expression.
@@ -4726,6 +4725,8 @@ export function serialize(built, opts = {}) {
4726
4725
  * `.stepToRef` rejoins now do come through here, #938).
4727
4726
  */
4728
4727
  const hitlNodes = new Map();
4728
+ /** Step name → node id of a step whose every forward exit is templated (see `contributionPort`). */
4729
+ const routerNodes = new Map();
4729
4730
  /**
4730
4731
  * Human tasks with an outcome whose path stops at an End while another
4731
4732
  * outcome continues — read by `.parallel`, whose join would wait forever for
@@ -4757,8 +4758,16 @@ export function serialize(built, opts = {}) {
4757
4758
  if (o.requestedVersion !== undefined && def?.version !== o.requestedVersion) {
4758
4759
  const exact = o.def ? undefined : DEFS[`${type}@${o.requestedVersion}`];
4759
4760
  if (!exact) {
4761
+ // Name every version the SDK could have resolved: every bundled
4762
+ // version of the type, else (a type built per resource, a connector)
4763
+ // the definition in hand. Naming only the default sent the reader
4764
+ // looking for a version that was shipped all along (#1128:
4765
+ // quick-form bundles 1.0 AND 1.1, and the message said 1.0).
4766
+ const bundled = bundledVersions(type);
4767
+ const resolvable = bundled.length > 0 ? bundled
4768
+ : [def?.version].filter((v) => typeof v === 'string');
4760
4769
  throw new Error(`"${label ?? id}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
4761
- + `${type}@${def?.version ?? '<none>'}. Versions are exact — omit { version } to use `
4770
+ + `${type}@${resolvable.join(', ') || '<none>'}. Versions are exact — omit { version } to use `
4762
4771
  + `the pinned default, or request a version this SDK ships.`);
4763
4772
  }
4764
4773
  def = exact;
@@ -4771,7 +4780,7 @@ export function serialize(built, opts = {}) {
4771
4780
  id,
4772
4781
  type,
4773
4782
  typeVersion: def?.version ?? '1.0.0',
4774
- ui: { position: { x: 256 + col * 288, y: 144 + (o.parentId ? 160 : 0) }, size: { width: 96, height: 96 }, collapsed: false },
4783
+ ui: { position: placeholderPosition(col, Boolean(o.parentId)), size: { width: 96, height: 96 }, collapsed: false },
4775
4784
  display: { label: label ?? def?.display?.label ?? id, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
4776
4785
  };
4777
4786
  col++;
@@ -5075,7 +5084,14 @@ export function serialize(built, opts = {}) {
5075
5084
  scope.nodes.push(node);
5076
5085
  for (const t of o.tails)
5077
5086
  addEdge(scope, t, id);
5078
- return [{ node: id, port: contribution.port ?? contributionPort(def) }];
5087
+ const port = contribution.port ?? contributionPort(def);
5088
+ if (port === undefined) {
5089
+ // Only templated exits: no main path leaves this node. Remember it so a
5090
+ // following `.stepToList` / side-exit `.stepToRef` can still attach.
5091
+ routerNodes.set(o.stepName, id);
5092
+ return [];
5093
+ }
5094
+ return [{ node: id, port }];
5079
5095
  }
5080
5096
  /**
5081
5097
  * Lower a step whose kind is a registered node type (flow-node-registry.ts):
@@ -5413,6 +5429,13 @@ export function serialize(built, opts = {}) {
5413
5429
  const contexts = s.context === undefined ? []
5414
5430
  : Array.isArray(s.context) ? s.context : [s.context];
5415
5431
  for (const cx of contexts) {
5432
+ // `check`'s AGENT_CONTEXT_NO_ID, again for `--no-check`: a blank id builds
5433
+ // the type `…context.index.<slug>.`, which no tenant serves, and nothing
5434
+ // after this point would say so.
5435
+ if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
5436
+ throw Object.assign(new Error(`inline agent "${step.name}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
5437
+ + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
5438
+ }
5416
5439
  const cdef = contextIndexDef({ name: cx.name, id: cx.id });
5417
5440
  // `<step>Context`, deduped by `uid` for a second index on the same agent.
5418
5441
  // Not slugged from the index NAME: an id is the author's handle on the node
@@ -5876,7 +5899,7 @@ export function serialize(built, opts = {}) {
5876
5899
  const { ids, entries } = queueItemBindings(s, resourceBindings);
5877
5900
  for (const b of entries)
5878
5901
  resourceBindings.set(b.id, b);
5879
- const def = queueItemDef(s, step.name, ids);
5902
+ const def = queueItemDef(s, step.name, ids, step.options?.version);
5880
5903
  const id = stepUid(scope, step.name);
5881
5904
  // The result is read as `$vars.<step>.output`, like every other family: the
5882
5905
  // node's own output block maps the runtime `response` onto the variable
@@ -6359,8 +6382,13 @@ export function serialize(built, opts = {}) {
6359
6382
  * from a non-default port is a side exit. Only the seed differs from an ordinary
6360
6383
  * list — the same node, a different port.
6361
6384
  */
6362
- function emitPortList(step, source, tails, scope, parentId, breakTarget) {
6385
+ function emitPortList(step, source, tails, scope, parentId, loopExits) {
6363
6386
  const handler = step.body;
6387
+ // A node whose exits are all templated hands on no tail; its port lists
6388
+ // leave from the node itself.
6389
+ const router = tails.length === 0 ? routerNodes.get(source) : undefined;
6390
+ if (router !== undefined)
6391
+ tails = [{ node: router, port: step.port }];
6364
6392
  // `emitAction` returns one tail for every action kind — the node's own
6365
6393
  // success port — except a fan-out human task, which returns one per outcome
6366
6394
  // from the SAME node. Tails from two nodes mean a new action shape reached
@@ -6379,7 +6407,7 @@ export function serialize(built, opts = {}) {
6379
6407
  refuseErrorEdgeWithoutPort(nodeId, source);
6380
6408
  node.inputs = { ...(node.inputs ?? {}), errorHandlingEnabled: true };
6381
6409
  }
6382
- const handlerTails = emit(handler, [{ node: nodeId, port: step.port }], scope, parentId, breakTarget);
6410
+ const handlerTails = emit(handler, [{ node: nodeId, port: step.port }], scope, parentId, loopExits);
6383
6411
  // A body that runs out of steps ends the run on an End of its own (no
6384
6412
  // merge-back is inferred). That End maps the outputs like any other, so a
6385
6413
  // body that set them with `{ updates }` still answers (#742).
@@ -6387,13 +6415,13 @@ export function serialize(built, opts = {}) {
6387
6415
  const endId = uid(scope, `${source}${step.port === 'error' ? 'Error' : step.port.charAt(0).toUpperCase() + step.port.slice(1)}End`);
6388
6416
  emitEnd(scope, endId, handlerTails, { parentId });
6389
6417
  }
6390
- return tails;
6418
+ return router !== undefined ? [] : tails;
6391
6419
  }
6392
6420
  function emit(steps, incoming, scope, parentId,
6393
- // The enclosing container's break handle, when inside a loop/do-while body.
6394
- // Branch/switch/parallel arms and error handlers INHERIT it; a nested
6395
- // container replaces it for its own body.
6396
- breakTarget) {
6421
+ // The enclosing container's break and continue handles, when inside a
6422
+ // loop/do-while body. Branch/switch/parallel arms and error handlers INHERIT
6423
+ // them; a nested container replaces them for its own body.
6424
+ loopExits) {
6397
6425
  let tails = incoming;
6398
6426
  // The action a port-scoped edge leaves. `.stepToList('error', …)` and
6399
6427
  // `.stepToRef('error', …)` are list MEMBERS that follow the step they attach
@@ -6437,6 +6465,14 @@ export function serialize(built, opts = {}) {
6437
6465
  node.display = { ...(node.display ?? {}), label };
6438
6466
  };
6439
6467
  for (const [stepIndex, step] of steps.entries()) {
6468
+ // After a node whose exits are all templated, only its port lists may
6469
+ // follow: nothing continues from it, so a step here would hang off no edge.
6470
+ const router = lastAction !== undefined && tails.length === 0 ? routerNodes.get(lastAction) : undefined;
6471
+ if (router !== undefined && step.kind !== 'stepToList' && step.kind !== 'stepToRef') {
6472
+ throw new Error(`step "${lastAction}": its definition declares only templated exits (such as case-{item.id}), so no `
6473
+ + `path continues from it and ${'name' in step ? `"${step.name}"` : `.${step.kind}()`} after it would `
6474
+ + `have no incoming edge. Route each exit with .stepToList('<port>', …), e.g. .stepToList('case-<id>', …).`);
6475
+ }
6440
6476
  if (step.kind === 'action') {
6441
6477
  tails = emitAction(step, tails, scope, parentId);
6442
6478
  applyUpdates(step);
@@ -6465,7 +6501,7 @@ export function serialize(built, opts = {}) {
6465
6501
  }
6466
6502
  }
6467
6503
  else if (step.kind === 'stepToList') {
6468
- tails = emitPortList(step, lastAction ?? '?', tails, scope, parentId, breakTarget);
6504
+ tails = emitPortList(step, lastAction ?? '?', tails, scope, parentId, loopExits);
6469
6505
  }
6470
6506
  else if (step.kind === 'stepSwitch') {
6471
6507
  // The action, then EVERY exit it declares as an arm — no tacit tail.
@@ -6510,7 +6546,7 @@ export function serialize(built, opts = {}) {
6510
6546
  // from all of them. (`.stepToList` instead gives an arm that runs out
6511
6547
  // of steps an End of its own; `emitEnd` maps the outputs on it,
6512
6548
  // flow-builder-sdk#742.)
6513
- const caseTails = emit(c.body, [{ node: nodeId, port: exit.port }], scope, parentId, breakTarget);
6549
+ const caseTails = emit(c.body, [{ node: nodeId, port: exit.port }], scope, parentId, loopExits);
6514
6550
  if (caseTails.length === 0 && stopsAtEnd(c.body))
6515
6551
  stopped.push(String(c.value));
6516
6552
  armTails.push(...(hitlNodes.has(nodeId)
@@ -6555,8 +6591,8 @@ export function serialize(built, opts = {}) {
6555
6591
  scope.nodes.push(node);
6556
6592
  for (const t of tails)
6557
6593
  addEdge(scope, t, id);
6558
- const thenTails = emit(step.then, [{ node: id, port: 'true' }], scope, parentId, breakTarget);
6559
- const elseTails = emit(step.otherwise, [{ node: id, port: 'false' }], scope, parentId, breakTarget);
6594
+ const thenTails = emit(step.then, [{ node: id, port: 'true' }], scope, parentId, loopExits);
6595
+ const elseTails = emit(step.otherwise, [{ node: id, port: 'false' }], scope, parentId, loopExits);
6560
6596
  tails = [...thenTails, ...elseTails];
6561
6597
  }
6562
6598
  else if (step.kind === 'switch') {
@@ -6571,8 +6607,11 @@ export function serialize(built, opts = {}) {
6571
6607
  // the runtime — so no `=js:` prefix, same as a decision's `expression`.
6572
6608
  const id = stepUid(scope, step.name);
6573
6609
  const node = makeNode(NODE_TYPE.switch, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: step.options?.version });
6610
+ // `id` is the author's when given (a decompiled switch keeps the
6611
+ // canvas's `default-1`, …: merge keeps the original node, so the edges
6612
+ // must leave from ITS handles), else positional (#1130).
6574
6613
  const cases = step.cases.map((c, i) => ({
6575
- id: `case_${i + 1}`,
6614
+ id: c.id ?? `case_${i + 1}`,
6576
6615
  label: c.label ?? `Case ${caseLiteral(c.value)}`,
6577
6616
  expression: `${renameRefs(step.on.js, scope.rename)} === ${caseLiteral(c.value)}`,
6578
6617
  }));
@@ -6585,9 +6624,9 @@ export function serialize(built, opts = {}) {
6585
6624
  // merge node; the convergence point is found by graph search).
6586
6625
  const armTails = [];
6587
6626
  for (const [i, c] of step.cases.entries())
6588
- armTails.push(...emit(c.body, [{ node: id, port: `case-${cases[i].id}` }], scope, parentId, breakTarget));
6627
+ armTails.push(...emit(c.body, [{ node: id, port: `case-${cases[i].id}` }], scope, parentId, loopExits));
6589
6628
  if (step.default !== undefined)
6590
- armTails.push(...emit(step.default, [{ node: id, port: 'default' }], scope, parentId));
6629
+ armTails.push(...emit(step.default, [{ node: id, port: 'default' }], scope, parentId, loopExits));
6591
6630
  tails = armTails;
6592
6631
  }
6593
6632
  else if (step.kind === 'parallel') {
@@ -6620,7 +6659,7 @@ export function serialize(built, opts = {}) {
6620
6659
  const armTails = [];
6621
6660
  const endsBefore = hitlEndExits.length;
6622
6661
  for (const arm of step.arms) {
6623
- const t = emit(arm, tails, scope, parentId, breakTarget);
6662
+ const t = emit(arm, tails, scope, parentId, loopExits);
6624
6663
  // An arm hands the join ONE edge, or several that are alternatives —
6625
6664
  // `.branch` / `.switch` / `.stepSwitch` arms, a fan-out task's
6626
6665
  // outcomes — of which only one fires. Refused when any of them is a
@@ -6705,7 +6744,7 @@ export function serialize(built, opts = {}) {
6705
6744
  addEdge(scope, t, id);
6706
6745
  const bodyPort = v24 ? 'start' : 'output';
6707
6746
  const backPort = v24 ? 'continue' : 'loopBack';
6708
- const bodyTails = emit(step.body, [{ node: id, port: bodyPort }], scope, id, v24 ? { node: id, port: 'break' } : undefined);
6747
+ const bodyTails = emit(step.body, [{ node: id, port: bodyPort }], scope, id, v24 ? { node: id, break: 'break', continue: backPort } : { node: id, continue: backPort });
6709
6748
  for (const bt of bodyTails)
6710
6749
  addEdge(scope, bt, id, backPort);
6711
6750
  applyUpdates(step);
@@ -6724,18 +6763,29 @@ export function serialize(built, opts = {}) {
6724
6763
  scope.nodes.push(node);
6725
6764
  for (const t of tails)
6726
6765
  addEdge(scope, t, id);
6727
- const bodyTails = emit(step.body, [{ node: id, port: 'start' }], scope, id, { node: id, port: 'break' });
6766
+ const bodyTails = emit(step.body, [{ node: id, port: 'start' }], scope, id, { node: id, break: 'break', continue: 'continue' });
6728
6767
  for (const bt of bodyTails)
6729
6768
  addEdge(scope, bt, id, 'continue');
6730
6769
  applyUpdates(step);
6731
6770
  tails = [{ node: id, port: 'success' }];
6732
6771
  }
6733
6772
  else if (step.kind === 'break') {
6734
- if (!breakTarget) {
6773
+ if (!loopExits?.break) {
6735
6774
  throw new Error('.break() has no enclosing .loop()/.doWhile() body to exit — it is only valid inside a container.');
6736
6775
  }
6737
6776
  for (const t of tails)
6738
- addEdge(scope, t, breakTarget.node, breakTarget.port);
6777
+ addEdge(scope, t, loopExits.node, loopExits.break);
6778
+ tails = [];
6779
+ }
6780
+ else if (step.kind === 'continue') {
6781
+ // The same edge a body's last step gets, from wherever this path has got
6782
+ // to — so a handler or an arm can go on to the next iteration instead of
6783
+ // ending the run on an End of its own (#1129).
6784
+ if (!loopExits) {
6785
+ throw new Error('.continue() has no enclosing .loop()/.doWhile() body to go back to — it is only valid inside a container.');
6786
+ }
6787
+ for (const t of tails)
6788
+ addEdge(scope, t, loopExits.node, loopExits.continue);
6739
6789
  tails = [];
6740
6790
  }
6741
6791
  else if (step.kind === 'terminate') {
@@ -6773,7 +6823,8 @@ export function serialize(built, opts = {}) {
6773
6823
  else {
6774
6824
  // Any other port is a SIDE EXIT from the step just emitted. The main
6775
6825
  // path is untouched, so `tails` survives and chaining continues.
6776
- const from = tails[0];
6826
+ const router = tails.length === 0 && lastAction !== undefined ? routerNodes.get(lastAction) : undefined;
6827
+ const from = tails[0] ?? (router !== undefined ? { node: router, port: step.port } : undefined);
6777
6828
  if (!from)
6778
6829
  throw new Error(`.stepToRef('${step.port}', '${step.target}') has no step to leave from.`);
6779
6830
  refuseRewire('.stepToRef', step.port, lastAction ?? '?', tails);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.19.0",
3
+ "version": "6.20.1",
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",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "75a7db3acd31187aec1aa0d4e7df67ec11b7985a"
95
+ "gitref": "5440c25e8e833aced459eb5a0ae9109ac3c973ae"
96
96
  }