@uipath/maestro-builder-sdk 6.43.0 → 6.44.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.
@@ -444,6 +444,39 @@ export interface NodeOptions {
444
444
  */
445
445
  label?: string;
446
446
  }
447
+ /**
448
+ * The options of `.parallel()`: the join's {@link NodeOptions}, plus the fork
449
+ * node when the flow has one.
450
+ */
451
+ export interface ParallelOptions extends NodeOptions {
452
+ /**
453
+ * A FORK node the arms leave from: a `core.logic.merge` with one incoming
454
+ * edge and one edge per arm, which is how Studio Web draws a parallel split
455
+ * (its "Parallel" gateway). Without it the arms leave from the previous
456
+ * step's own port, one edge per arm, which the platform runs the same way.
457
+ * Name it to round-trip a flow the canvas drew with a fork node; `label` is
458
+ * the canvas text, defaulting to the name, and `version` the fork node's
459
+ * exact `core.logic.merge` version when it differs from the join's.
460
+ *
461
+ * ```ts
462
+ * .parallel('join', [(a) => a.step('t1', …), (b) => b.step('t2', …)], { fork: 'split' })
463
+ * ```
464
+ */
465
+ fork?: string | {
466
+ name: string;
467
+ label?: string;
468
+ version?: string;
469
+ };
470
+ }
471
+ /**
472
+ * The fork node a `.parallel()` asks for, normalized, or `undefined`.
473
+ * @internal
474
+ */
475
+ export declare function parallelFork(options: ParallelOptions | undefined): {
476
+ name: string;
477
+ label?: string;
478
+ version?: string;
479
+ } | undefined;
447
480
  /**
448
481
  * `.return(...)`-specific options.
449
482
  *
@@ -588,15 +621,16 @@ export type Step = {
588
621
  /**
589
622
  * A fan-out and its join. `arms` is one step list per arm — an ARRAY, because
590
623
  * arms are positional and carry no author-supplied keys — and `name` names the
591
- * Merge node the arms converge on (the only node this step creates; the fork
592
- * itself is just N edges out of one port).
624
+ * Merge node the arms converge on (the only node this step creates unless
625
+ * `options.fork` names a fork node too; otherwise the fork is just N edges
626
+ * out of one port).
593
627
  */
594
628
  | {
595
629
  kind: 'parallel';
596
630
  name: string;
597
631
  label?: string;
598
632
  arms: Step[][];
599
- options?: NodeOptions;
633
+ options?: ParallelOptions;
600
634
  }
601
635
  /**
602
636
  * A hard stop: `core.logic.terminate`, a BPMN End event carrying a
@@ -1137,11 +1171,12 @@ declare class StepList {
1137
1171
  * @param arms - One callback per parallel arm, each receiving its own
1138
1172
  * sub-builder. Two or more.
1139
1173
  * @param options - Node options; `version` selects the exact definition
1140
- * version (see {@link NodeOptions}).
1174
+ * version (see {@link NodeOptions}), and `fork` names a fork node the arms
1175
+ * leave from (see {@link ParallelOptions}).
1141
1176
  * @returns This builder, positioned after the Merge — so what you chain next
1142
1177
  * runs once, when every arm has finished.
1143
1178
  */
1144
- parallel(name: string, arms: ((b: StepList) => void)[], options?: NodeOptions): this;
1179
+ parallel(name: string, arms: ((b: StepList) => void)[], options?: ParallelOptions): this;
1145
1180
  /**
1146
1181
  * Iterate `collection`; `bodyFn` receives a sub-builder for the loop body.
1147
1182
  *
package/dist/flow-sdk.js CHANGED
@@ -264,6 +264,18 @@ export function conversationTrigger() {
264
264
  export function voiceTrigger() {
265
265
  return builtinTriggerSpec({ kind: 'voice' });
266
266
  }
267
+ /**
268
+ * The fork node a `.parallel()` asks for, normalized, or `undefined`.
269
+ * @internal
270
+ */
271
+ export function parallelFork(options) {
272
+ const fork = options?.fork;
273
+ if (fork === undefined)
274
+ return undefined;
275
+ return typeof fork === 'string'
276
+ ? { name: fork }
277
+ : { name: fork.name, ...(fork.label !== undefined ? { label: fork.label } : {}), ...(fork.version !== undefined ? { version: fork.version } : {}) };
278
+ }
267
279
  /**
268
280
  * A node's layout at a canvas position — the short form of a {@link NodeLayout}
269
281
  * for the `nodes` map of {@link FlowBuilder.layout}.
@@ -764,7 +776,8 @@ class StepList {
764
776
  * @param arms - One callback per parallel arm, each receiving its own
765
777
  * sub-builder. Two or more.
766
778
  * @param options - Node options; `version` selects the exact definition
767
- * version (see {@link NodeOptions}).
779
+ * version (see {@link NodeOptions}), and `fork` names a fork node the arms
780
+ * leave from (see {@link ParallelOptions}).
768
781
  * @returns This builder, positioned after the Merge — so what you chain next
769
782
  * runs once, when every arm has finished.
770
783
  */
@@ -0,0 +1,4 @@
1
+ export declare function stepDeclaredVariables(spec: {
2
+ kind?: unknown;
3
+ inputs?: unknown;
4
+ } | undefined, pinnedVersion?: string): string[];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The flow variables a step DECLARES by running (`FlowNodeType.declares`): a
3
+ * quick form's asked fields land in their `variable` (`vars.<Step>_<field>` as
4
+ * the designer names it), and later steps read `$vars.<name>`. Resolved at the
5
+ * version the step resolves to, the way `stepExits` does, so `check` and
6
+ * `flow-expr-check` agree on what a step publishes (#1309).
7
+ */
8
+ import { resolveVersion } from './core/nodes/versioned.js';
9
+ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
10
+ export function stepDeclaredVariables(spec, pinnedVersion) {
11
+ const entry = typeof spec?.kind === 'string' ? FLOW_NODE_TYPES.byKind(spec.kind, spec) : undefined;
12
+ if (!entry)
13
+ return [];
14
+ let impl;
15
+ try {
16
+ impl = resolveVersion(entry.type, spec, pinnedVersion, 'declares');
17
+ }
18
+ catch {
19
+ impl = resolveVersion(entry.type, spec, undefined, 'declares');
20
+ }
21
+ return impl.declares?.(spec, pinnedVersion) ?? [];
22
+ }
@@ -3,28 +3,45 @@ import type { LoopOptions, Step } from './flow-sdk.js';
3
3
  export declare const LOOP_NODE_TYPE = "core.logic.loop";
4
4
  /** The `core.logic.loop` version `.loop()` emits unless `{ version }` pins another. */
5
5
  export declare const LOOP_DEFAULT_VERSION = "2.4";
6
+ /**
7
+ * The definition a loop at `version` compiles against: the bundled one, else
8
+ * the one the flow carries (`carried`, a `.definitions()` lookup), else
9
+ * `undefined`. `serialize` and `check` both resolve through this, so they agree
10
+ * on a carried `core.logic.loop@1.0`'s handles and inputs (#1311).
11
+ */
12
+ export declare function resolveLoopDefinition(version: string, carried?: (type: string, version: string) => unknown): unknown;
6
13
  /** The version `.loop()` emits for these options. */
7
14
  export declare function loopVersion(options: LoopOptions | undefined): string;
8
15
  /**
9
16
  * True when this loop version's body runs from the inner `start` handle and goes
10
17
  * back through `continue` (2.x); false for the legacy `output`/`loopBack` shape.
11
- * A version with no bundled definition counts as 2.x; its lookup fails later.
18
+ *
19
+ * The answer is read off the definition's target handles. `definition` is the
20
+ * one the loop compiles against: the bundled one for `version` by default, or
21
+ * the one the flow carries (`.definitions([...])`) when the SDK bundles none —
22
+ * a designer-saved `core.logic.loop@1.0` keeps its `output`/`loopBack` handles
23
+ * only because its carried definition says so (#1311). A version with no
24
+ * definition at all counts as 2.x; its lookup fails later.
12
25
  */
13
- export declare function loopHasInnerHandles(version: string): boolean;
26
+ export declare function loopHasInnerHandles(version: string, definition?: unknown): boolean;
14
27
  /**
15
28
  * The features this loop uses that its version's definition cannot express, in
16
29
  * a fixed order (`parallel`, `completionCondition`, `breakEnabled`, `break()`).
17
30
  * Empty when all are supported, or when no definition is bundled for the version
18
31
  * (an unknown version is reported elsewhere, by the definition lookup).
19
32
  */
20
- export declare function unsupportedLoopFeatures(options: LoopOptions | undefined, usesBreak: boolean): string[];
33
+ export declare function unsupportedLoopFeatures(options: LoopOptions | undefined, usesBreak: boolean, definition?: unknown): string[];
34
+ /** The inputs a loop definition declares (`collection`, `parallel`, `completionCondition`, `breakEnabled`, …). */
35
+ export declare function loopDeclaredInputs(definition: unknown): Record<string, unknown>;
21
36
  /**
22
37
  * The refusal for a loop whose options or body need a definition the requested
23
38
  * version does not have, or `undefined` when there is nothing to refuse.
24
39
  * `serialize` throws it; `check` reports it as `LOOP_VERSION_UNSUPPORTED` with
25
- * the same text, so the author reads one message in both places.
40
+ * the same text, so the author reads one message in both places. `definition`
41
+ * is the one the loop compiles against (bundled, else carried), as for
42
+ * `loopHasInnerHandles`.
26
43
  */
27
- export declare function loopVersionRefusal(name: string, options: LoopOptions | undefined, usesBreak: boolean): string | undefined;
44
+ export declare function loopVersionRefusal(name: string, options: LoopOptions | undefined, usesBreak: boolean, definition?: unknown): string | undefined;
28
45
  /**
29
46
  * The warning for `parallel: true` on a 1.0.0 loop, or `undefined`. The 1.0.0
30
47
  * definition declares the input, so the loop compiles and a canvas 1.0.0 loop
@@ -32,7 +49,7 @@ export declare function loopVersionRefusal(name: string, options: LoopOptions |
32
49
  * confirmed, so a new loop that needs that should use the default version.
33
50
  * `check` reports it as `LOOP_PARALLEL_LEGACY`.
34
51
  */
35
- export declare function loopParallelWarning(name: string, options: LoopOptions | undefined): string | undefined;
52
+ export declare function loopParallelWarning(name: string, options: LoopOptions | undefined, definition?: unknown): string | undefined;
36
53
  /**
37
54
  * True when a container body (or an arm/handler inside it, but not a NESTED
38
55
  * container's body) breaks: a nested `.loop()`/`.doWhile()` owns its own
@@ -11,6 +11,15 @@ import { BUNDLED_DEFINITIONS } from './flow-definitions.js';
11
11
  export const LOOP_NODE_TYPE = 'core.logic.loop';
12
12
  /** The `core.logic.loop` version `.loop()` emits unless `{ version }` pins another. */
13
13
  export const LOOP_DEFAULT_VERSION = '2.4';
14
+ /**
15
+ * The definition a loop at `version` compiles against: the bundled one, else
16
+ * the one the flow carries (`carried`, a `.definitions()` lookup), else
17
+ * `undefined`. `serialize` and `check` both resolve through this, so they agree
18
+ * on a carried `core.logic.loop@1.0`'s handles and inputs (#1311).
19
+ */
20
+ export function resolveLoopDefinition(version, carried) {
21
+ return loopDefinition(version) ?? carried?.(LOOP_NODE_TYPE, version);
22
+ }
14
23
  /** The version `.loop()` emits for these options. */
15
24
  export function loopVersion(options) {
16
25
  return options?.version ?? LOOP_DEFAULT_VERSION;
@@ -18,10 +27,16 @@ export function loopVersion(options) {
18
27
  /**
19
28
  * True when this loop version's body runs from the inner `start` handle and goes
20
29
  * back through `continue` (2.x); false for the legacy `output`/`loopBack` shape.
21
- * A version with no bundled definition counts as 2.x; its lookup fails later.
30
+ *
31
+ * The answer is read off the definition's target handles. `definition` is the
32
+ * one the loop compiles against: the bundled one for `version` by default, or
33
+ * the one the flow carries (`.definitions([...])`) when the SDK bundles none —
34
+ * a designer-saved `core.logic.loop@1.0` keeps its `output`/`loopBack` handles
35
+ * only because its carried definition says so (#1311). A version with no
36
+ * definition at all counts as 2.x; its lookup fails later.
22
37
  */
23
- export function loopHasInnerHandles(version) {
24
- return loopDefinition(version) ? targetHandleIds(version).has('continue') : true;
38
+ export function loopHasInnerHandles(version, definition = loopDefinition(version)) {
39
+ return definition ? targetHandleIdsOf(definition).has('continue') : true;
25
40
  }
26
41
  /** The loop options whose support depends on the version, in message order. */
27
42
  const VERSIONED_OPTIONS = ['parallel', 'completionCondition', 'breakEnabled'];
@@ -31,24 +46,29 @@ const VERSIONED_OPTIONS = ['parallel', 'completionCondition', 'breakEnabled'];
31
46
  * Empty when all are supported, or when no definition is bundled for the version
32
47
  * (an unknown version is reported elsewhere, by the definition lookup).
33
48
  */
34
- export function unsupportedLoopFeatures(options, usesBreak) {
35
- const def = loopDefinition(loopVersion(options));
36
- if (!def)
49
+ export function unsupportedLoopFeatures(options, usesBreak, definition = loopDefinition(loopVersion(options))) {
50
+ if (!definition)
37
51
  return [];
38
- const inputs = def.inputDefinition?.properties ?? {};
52
+ const inputs = loopDeclaredInputs(definition);
39
53
  const out = VERSIONED_OPTIONS.filter((k) => options?.[k] !== undefined && !(k in inputs));
40
- if (usesBreak && !targetHandleIds(loopVersion(options)).has('break'))
54
+ if (usesBreak && !targetHandleIdsOf(definition).has('break'))
41
55
  out.push('break()');
42
56
  return out;
43
57
  }
58
+ /** The inputs a loop definition declares (`collection`, `parallel`, `completionCondition`, `breakEnabled`, …). */
59
+ export function loopDeclaredInputs(definition) {
60
+ return definition?.inputDefinition?.properties ?? {};
61
+ }
44
62
  /**
45
63
  * The refusal for a loop whose options or body need a definition the requested
46
64
  * version does not have, or `undefined` when there is nothing to refuse.
47
65
  * `serialize` throws it; `check` reports it as `LOOP_VERSION_UNSUPPORTED` with
48
- * the same text, so the author reads one message in both places.
66
+ * the same text, so the author reads one message in both places. `definition`
67
+ * is the one the loop compiles against (bundled, else carried), as for
68
+ * `loopHasInnerHandles`.
49
69
  */
50
- export function loopVersionRefusal(name, options, usesBreak) {
51
- const missing = unsupportedLoopFeatures(options, usesBreak);
70
+ export function loopVersionRefusal(name, options, usesBreak, definition = loopDefinition(loopVersion(options))) {
71
+ const missing = unsupportedLoopFeatures(options, usesBreak, definition);
52
72
  if (missing.length === 0)
53
73
  return undefined;
54
74
  const version = loopVersion(options);
@@ -63,8 +83,8 @@ export function loopVersionRefusal(name, options, usesBreak) {
63
83
  * confirmed, so a new loop that needs that should use the default version.
64
84
  * `check` reports it as `LOOP_PARALLEL_LEGACY`.
65
85
  */
66
- export function loopParallelWarning(name, options) {
67
- if (options?.parallel !== true || loopHasInnerHandles(loopVersion(options)))
86
+ export function loopParallelWarning(name, options, definition = loopDefinition(loopVersion(options))) {
87
+ if (options?.parallel !== true || loopHasInnerHandles(loopVersion(options), definition))
68
88
  return undefined;
69
89
  return `loop "${name}": parallel: true on { version: '${loopVersion(options)}' } is written to the node, but `
70
90
  + `that version is not confirmed to run iterations in parallel, so the loop may run them one by one. `
@@ -86,9 +106,11 @@ export function bodyUsesBreak(steps) {
86
106
  function loopDefinition(version) {
87
107
  return BUNDLED_DEFINITIONS[`${LOOP_NODE_TYPE}@${version}`];
88
108
  }
89
- function targetHandleIds(version) {
109
+ /** The ids of a definition's target handles (`input`, `loopBack`, `continue`, `break`, …). */
110
+ function targetHandleIdsOf(definition) {
90
111
  const ids = new Set();
91
- for (const group of loopDefinition(version)?.handleConfiguration ?? []) {
112
+ const groups = definition?.handleConfiguration ?? [];
113
+ for (const group of groups) {
92
114
  for (const h of group.handles ?? [])
93
115
  if (h.type === 'target')
94
116
  ids.add(h.id);
@@ -30,6 +30,7 @@ export const HITL_DIAGNOSTICS = {
30
30
  HITL_FIELD_VALUE_NOT_REF: { severity: 'error', summary: 'a shown field filled from a literal' },
31
31
  HITL_FIELD_VALUE_NOT_PATH: { severity: 'error', summary: 'a shown field filled from a computed expression' },
32
32
  HITL_FIELD_DIRECTION: { severity: 'error', summary: 'a field direction that is not input, output or inOut' },
33
+ HITL_FIELD_VARIABLE: { severity: 'error', summary: "an asked field's variable that is not one identifier, or sits on a shown field" },
33
34
  HITL_NO_OUTCOMES: { severity: 'error', summary: 'no outcomes, so the task never completes' },
34
35
  HITL_OUTCOME_BAD_ID: { severity: 'error', summary: 'an outcome id that is not a non-empty string' },
35
36
  HITL_DUP_OUTCOME_ID: { severity: 'error', summary: 'two outcomes on one id' },
@@ -225,6 +226,20 @@ export function checkHitl(inputs, ctx) {
225
226
  for (const problem of formFieldSettingProblems(f)) {
226
227
  at('HITL_FIELD_SETTING_INVALID', `Human task "${step}" gives the form field "${id}" ${problem}. The platform refuses the form.`);
227
228
  }
229
+ // The landing variable is read as `$vars.<variable>`, so it has to be one
230
+ // identifier — not a path, not a `vars.` prefix — and only an asked field
231
+ // has one: a shown field's row is written without it (#1309).
232
+ const variable = f?.variable;
233
+ if (variable !== undefined) {
234
+ if (f?.direction !== 'output') {
235
+ at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" sets \`variable\` on a ${f?.direction === 'inOut' ? 'pre-filled' : 'shown'} field. `
236
+ + `Only an asked field (direction 'output') lands its answer in a variable; the SDK writes the row without one.`, 'Drop `variable`, or make the field direction: \'output\'.');
237
+ }
238
+ else if (typeof variable !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(variable)) {
239
+ at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" has variable ${JSON.stringify(variable)}, which is not one identifier. `
240
+ + `It is read as $vars.<variable>, so give the name only — no \`vars.\` prefix, no dots.`, typeof variable === 'string' && variable.startsWith('vars.') ? `variable: '${variable.slice(5)}'` : `variable: '${step}_${id}'`);
241
+ }
242
+ }
228
243
  if (isAnswerField(f))
229
244
  answers++; // an `output`, or an `inOut` whose reviewed value comes back
230
245
  if (f?.direction === 'input' || f?.direction === 'inOut') {
@@ -13,7 +13,7 @@
13
13
  * every edge out of an outcome-handle node to.
14
14
  */
15
15
  import { Expr, toExpr } from '../../../core/expr.js';
16
- import { formFieldJsonSchema, formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, formFieldSettingsWire, liftFormFieldSettings, sourceFormFieldType, wireFormFieldType, } from '../../../core/form-field-type.js';
16
+ import { formFieldJsonSchema, formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, formFieldSettingsWire, formFieldVariableName, liftFormFieldSettings, sourceFormFieldType, wireFormFieldType, } from '../../../core/form-field-type.js';
17
17
  import { isAnswerField } from '../../../core/hitl-answers.js';
18
18
  import { OUTCOME_HANDLE_NODE_TYPES, hitlExitModel, outcomeIdOf, outcomeSlug } from '../../../core/hitl-routing.js';
19
19
  import { jsonClone } from '../../../core/json-clone.js';
@@ -82,8 +82,9 @@ function hitlInputs(inputs, nodeType, ctx) {
82
82
  else {
83
83
  // The answer's landing variable. The platform falls back to the field id
84
84
  // when this is absent; naming it keeps the emitted flow explicit and
85
- // matches what the designer writes.
86
- base.variable = `vars.${f.id}`;
85
+ // matches what the designer writes — `vars.<Step>_<field>` when the author
86
+ // (or the canvas) named one, else the field's own id (#1309).
87
+ base.variable = `vars.${f.variable ?? f.id}`;
87
88
  const required = f.required;
88
89
  if (typeof required === 'boolean')
89
90
  base.required = required;
@@ -331,6 +332,11 @@ function liftHitl(node, typeName, ctx) {
331
332
  const id = String(f.id ?? '');
332
333
  const direction = String(f.direction ?? 'output');
333
334
  const value = direction === 'input' || direction === 'inOut' ? bindingExpr(f.binding) : undefined;
335
+ // An asked field's landing variable comes back when it is not the field's
336
+ // own id — the designer's `vars.<Step>_<field>` (#1309); `formFieldRowLoss`
337
+ // reports any other spelling.
338
+ const landing = direction === 'output' ? formFieldVariableName(f.variable) : undefined;
339
+ const variable = landing !== undefined && landing !== id ? landing : undefined;
334
340
  // Every row key the source cannot carry is reported, never dropped
335
341
  // silently: a warning, an error under --strict (#1250).
336
342
  const loss = formFieldRowLoss(f, 'human task');
@@ -346,6 +352,7 @@ function liftHitl(node, typeName, ctx) {
346
352
  direction,
347
353
  ...(value !== undefined ? { value } : {}),
348
354
  ...(typeof f.required === 'boolean' && direction !== 'input' ? { required: f.required } : {}),
355
+ ...(variable !== undefined ? { variable } : {}),
349
356
  ...liftFormFieldSettings(f),
350
357
  };
351
358
  });
@@ -509,6 +516,7 @@ function printHitl({ inputs }, ctx) {
509
516
  ['direction', ctx.string(f.direction)],
510
517
  ['value', f.value !== undefined ? ctx.value(f.value) : undefined],
511
518
  ['required', f.required !== undefined ? String(f.required) : undefined],
519
+ ['variable', f.variable !== undefined ? ctx.string(f.variable) : undefined],
512
520
  ...formFieldSettingsSource(f, ctx.value),
513
521
  ], ctx));
514
522
  const outcomes = h.outcomes.map((o) => (typeof o === 'string' ? ctx.string(o) : obj([
@@ -596,6 +604,13 @@ function hitlAt(typeName, version, factory) {
596
604
  .filter((f) => f?.direction === 'input')
597
605
  .map((f) => ({ value: f.value })),
598
606
  output: ({ inputs }) => ({ kind: 'reader', read: (read, report) => checkHitlRead(read, inputs, report) }),
607
+ // An asked field's answer lands in its `variable` (`vars.<Step>_<field>` as
608
+ // the designer names it), else in `vars.<id>` — serialize writes one or the
609
+ // other on every asked row — and later steps read it as `$vars.<name>`: the
610
+ // task declares that variable (#1309).
611
+ declares: ({ inputs }) => (inputs?.fields ?? [])
612
+ .map((f) => (f?.direction === 'output' ? (f.variable ?? f.id) : undefined))
613
+ .filter((v) => typeof v === 'string' && v !== ''),
599
614
  exits: hitlExits,
600
615
  // The base node and quick-form draw one `outcome-<id>` handle per outcome,
601
616
  // whatever the version; their definitions declare only `completed`.
@@ -722,6 +722,22 @@ function liftProcess(node, ctx, prefix, kind, form) {
722
722
  inputs[k] = legacyAgentTemplateInput(v);
723
723
  }
724
724
  const schema = def?.outputDefinition?.output?.schema?.properties;
725
+ const returns = schema && Object.keys(schema).length > 0
726
+ ? Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')]))
727
+ : undefined;
728
+ // The designer writes the process's output schema on the definition whether
729
+ // or not the step waits for it. `agenticProcess()` refuses `returns` beside
730
+ // `completion: 'fire-and-forget'` (a dispatch publishes no output, so nothing
731
+ // could read one), so the schema is not carried into source — said, not
732
+ // dropped silently (#1312). Merge keeps the original's definition row, so a
733
+ // no-edit round trip loses nothing in the file.
734
+ if (fireAndForget && returns !== undefined) {
735
+ const id = String(node.id ?? '');
736
+ ctx.lossy(`process step ${JSON.stringify(id)} is fire-and-forget, so the ${Object.keys(returns).length} output `
737
+ + `field(s) its definition declares (${Object.keys(returns).join(', ')}) are not written as \`returns\`: `
738
+ + `a fire-and-forget dispatch publishes no output, and agenticProcess() refuses the pair. Compile writes the `
739
+ + `definition without that schema. To read the process result, set completion: 'wait' and declare \`returns\`.`);
740
+ }
725
741
  const spec = {
726
742
  key,
727
743
  ...(name !== undefined ? { name } : {}),
@@ -729,8 +745,7 @@ function liftProcess(node, ctx, prefix, kind, form) {
729
745
  ...(!inSolution && folderPath !== undefined ? { folderPath } : {}),
730
746
  ...(inSolution && projectId !== undefined ? { projectId } : {}),
731
747
  ...(Object.keys(inputs).length ? { inputs: Object.fromEntries(Object.entries(inputs).map(([k, v]) => [k, lifted(v, ctx)])) } : {}),
732
- ...(schema && Object.keys(schema).length > 0
733
- ? { returns: Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')])) } : {}),
748
+ ...(returns !== undefined && !fireAndForget ? { returns } : {}),
734
749
  ...(form !== undefined ? { form } : {}),
735
750
  ...(fireAndForget ? { completion: 'fire-and-forget' } : {}),
736
751
  };
package/dist/serialize.js CHANGED
@@ -8,8 +8,8 @@
8
8
  */
9
9
  import ixpDefinition from './core/ixp-definition.json' with { type: 'json' };
10
10
  import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
11
- import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
12
- import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameSegmentsOf } from './flow-sdk.js';
11
+ import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopDeclaredInputs, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
12
+ import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameSegmentsOf, parallelFork } from './flow-sdk.js';
13
13
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
14
14
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
15
15
  import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
@@ -1656,6 +1656,12 @@ function stepNamesOf(steps, out = [], terminates) {
1656
1656
  // End id is claimed as `end`); `stepToRef` has no `name` at all (it is one edge).
1657
1657
  if ('name' in s && s.kind !== 'return')
1658
1658
  out.push(s.name);
1659
+ // A `.parallel` with a fork node claims that node's name too (#1310).
1660
+ if (s.kind === 'parallel') {
1661
+ const fork = parallelFork(s.options);
1662
+ if (fork)
1663
+ out.push(fork.name);
1664
+ }
1659
1665
  // How many `.terminate`s hold each name, so a clash gets the right remedy (#1239).
1660
1666
  if (s.kind === 'terminate' && terminates)
1661
1667
  terminates.set(s.name, (terminates.get(s.name) ?? 0) + 1);
@@ -1705,6 +1711,11 @@ function namedNodesOf(steps, out = { steps: [], ends: [] }, seenEnds = new Set()
1705
1711
  }
1706
1712
  else if ('name' in s)
1707
1713
  out.steps.push(s.name);
1714
+ if (s.kind === 'parallel') {
1715
+ const fork = parallelFork(s.options);
1716
+ if (fork)
1717
+ out.steps.push(fork.name);
1718
+ }
1708
1719
  if (s.kind === 'stepToList')
1709
1720
  namedNodesOf(s.body, out, seenEnds);
1710
1721
  else if (s.kind === 'branch') {
@@ -3227,6 +3238,29 @@ export function serialize(built, opts = {}) {
3227
3238
  // one, hang the run with no incident. check reports both
3228
3239
  // (HITL_FAN_OUT_INTO_JOIN, HITL_END_OUTCOME_IN_PARALLEL); this is the
3229
3240
  // compile-side half, and it fails closed.
3241
+ // The fork, when the author names one (`{ fork }`, #1310): a merge node
3242
+ // with one incoming edge and one edge per arm — the gateway Studio Web
3243
+ // draws for a parallel split. The arms then leave from it instead of
3244
+ // from the step before.
3245
+ const fork = parallelFork(step.options);
3246
+ if (fork) {
3247
+ // The gateway waits for a token on EVERY incoming edge, so two tails
3248
+ // that are alternatives — a `.branch()`'s or `.switch()`'s arms, a
3249
+ // fan-out task's outcomes — must not both enter it: only one fires
3250
+ // and the arms never start. Fails closed, as the join does.
3251
+ if (tails.length > 1) {
3252
+ throw new Error(`.parallel("${step.name}"): its fork "${fork.name}" would receive ${tails.length} alternative edges `
3253
+ + `(${tails.map((t) => `${t.node}@${t.port}`).join(', ')}), and only one of them fires while the gateway `
3254
+ + `waits for all of them. Add a step before the .parallel so the paths meet there first, or drop { fork }.`);
3255
+ }
3256
+ const forkId = stepUid(scope, fork.name);
3257
+ const forkNode = makeNode(NODE_TYPE.merge, forkId, fork.label ?? fork.name, { parentId, requestedVersion: fork.version ?? step.options?.version });
3258
+ forkNode.inputs = {};
3259
+ scope.nodes.push(forkNode);
3260
+ for (const t of tails)
3261
+ addEdge(scope, t, forkId);
3262
+ tails = [{ node: forkId, port: successPort(NODE_TYPE.merge) }];
3263
+ }
3230
3264
  const armTails = [];
3231
3265
  const endsBefore = hitlEndExits.length;
3232
3266
  for (const arm of step.arms) {
@@ -3292,23 +3326,30 @@ export function serialize(built, opts = {}) {
3292
3326
  // An option or a .break() the pinned version's definition cannot express
3293
3327
  // is a contradiction the author must resolve, not a silent drop; `check`
3294
3328
  // reports the same refusal from the same helper (#1186).
3295
- const refusal = loopVersionRefusal(step.name, o, usesBreak);
3329
+ const version = loopVersion(o);
3330
+ // The definition the loop compiles against decides its handles AND its
3331
+ // inputs: a carried `core.logic.loop@1.0` (a designer-saved file's own)
3332
+ // keeps its `output`/`loopBack` pair, where the bundled lookup alone
3333
+ // would have written 2.4's `start`/`continue` onto a 1.0 node (#1311),
3334
+ // and an option is written when that definition declares the input
3335
+ // (the carried 1.0 declares `completionCondition`; the bundled 1.0.0
3336
+ // does not) and refused when it does not.
3337
+ const loopDef = definitionFor(NODE_TYPE.loop, version);
3338
+ const refusal = loopVersionRefusal(step.name, o, usesBreak, loopDef);
3296
3339
  if (refusal)
3297
3340
  throw new Error(refusal);
3298
- const version = loopVersion(o);
3299
- const v24 = loopHasInnerHandles(version);
3341
+ const v24 = loopHasInnerHandles(version, loopDef);
3342
+ const declared = loopDeclaredInputs(loopDef);
3300
3343
  const id = stepUid(scope, step.name);
3301
3344
  const node = makeNode(NODE_TYPE.loop, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: version });
3302
- node.inputs = v24
3303
- ? {
3304
- collection: renderValue(step.collection, scope.rename),
3305
- parallel: o.parallel ?? false,
3306
- ...(o.completionCondition !== undefined
3307
- ? { completionCondition: renameRefs(o.completionCondition.js, scope.rename) }
3308
- : {}),
3309
- breakEnabled: (o.breakEnabled ?? false) || usesBreak,
3310
- }
3311
- : { collection: renderValue(step.collection, scope.rename), parallel: o.parallel ?? false };
3345
+ node.inputs = {
3346
+ collection: renderValue(step.collection, scope.rename),
3347
+ parallel: o.parallel ?? false,
3348
+ ...(o.completionCondition !== undefined && (v24 || 'completionCondition' in declared)
3349
+ ? { completionCondition: renameRefs(o.completionCondition.js, scope.rename) }
3350
+ : {}),
3351
+ ...(v24 || 'breakEnabled' in declared ? { breakEnabled: (o.breakEnabled ?? false) || usesBreak } : {}),
3352
+ };
3312
3353
  scope.nodes.push(node);
3313
3354
  for (const t of tails)
3314
3355
  addEdge(scope, t, id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.43.0",
3
+ "version": "6.44.0",
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": "572ec05cde3b8e5f4e1c102ed2cfa3f0baaa95b0"
96
+ "gitref": "7075376040d8eb546745a5457c894dbc168448c8"
97
97
  }