@uipath/maestro-builder-sdk 6.20.4 → 6.20.6

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/flow-sdk.js CHANGED
@@ -1176,6 +1176,16 @@ class FlowBuilder extends StepList {
1176
1176
  * second variable (its default becomes the output's); a different type is a
1177
1177
  * `check` error, `OUTPUT_VAR_MISMATCH`.
1178
1178
  *
1179
+ * In the `.flow`, Studio Web sets an Output only on an End node, and neither
1180
+ * Update Variable nor an expression can touch one. So an output that the flow
1181
+ * sets or reads from inside (`{ updates }`, a `{ shared }` input, a
1182
+ * `default`, or a `v('<name>')` read) is emitted with a backing Variable,
1183
+ * `<name>Value` (`direction: 'inout'`): updates and reads go to the Variable,
1184
+ * and every End maps the output from it. That id must be free; a step,
1185
+ * input or `.var()` already called `<name>Value` is a `check` error,
1186
+ * `OUTPUT_BACKING_COLLISION`. An output only `.return({ … })` sets needs no
1187
+ * Variable: an End that does not name it maps `null`.
1188
+ *
1179
1189
  * @example
1180
1190
  * **A loop accumulator**
1181
1191
  * ```ts
@@ -54,10 +54,12 @@ export interface FlowGlobalsPlan<D extends DeclaredVariable = DeclaredVariable>
54
54
  * `@uipath/maestro-sdk` (the code that turns a `.flow` into what runs):
55
55
  * - only an `out` global is a flow OUTPUT at the root — `buildNodeOutputMappings`
56
56
  * drops an End node's mapping for any id that is not `direction: 'out'`;
57
- * - `inout` is published as an INPUT too — solution discovery puts it on the
58
- * entry point's input schema — so `inout` would widen the caller's contract;
59
- * - `{ updates }` on an `out` global is the same output mapping (`var: <id>`) as
60
- * on an `inout` one, so a loop body can read and rewrite it mid-run;
57
+ * - `inout` is not part of the caller's contract either way: `@uipath/flow-schema`
58
+ * packaging builds the entry-point input schema from `in` globals bound to the
59
+ * trigger, and the output schema from `out` globals only;
60
+ * - a loop body that reads and rewrites the value mid-run does so through the
61
+ * output's backing `inout` Variable (`flow-output-backing.ts`, #1092), because
62
+ * Studio Web neither updates nor reads an Output;
61
63
  * - the `default` on `<uipath:output>` is serialized (`collectVariablesFromNodes`)
62
64
  * and a subflow seeds its `out` globals from `defaultValue ?? null`
63
65
  * (`buildSeedTask`). The v1 CLI's `variable add --direction out
@@ -58,10 +58,12 @@ const sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
58
58
  * `@uipath/maestro-sdk` (the code that turns a `.flow` into what runs):
59
59
  * - only an `out` global is a flow OUTPUT at the root — `buildNodeOutputMappings`
60
60
  * drops an End node's mapping for any id that is not `direction: 'out'`;
61
- * - `inout` is published as an INPUT too — solution discovery puts it on the
62
- * entry point's input schema — so `inout` would widen the caller's contract;
63
- * - `{ updates }` on an `out` global is the same output mapping (`var: <id>`) as
64
- * on an `inout` one, so a loop body can read and rewrite it mid-run;
61
+ * - `inout` is not part of the caller's contract either way: `@uipath/flow-schema`
62
+ * packaging builds the entry-point input schema from `in` globals bound to the
63
+ * trigger, and the output schema from `out` globals only;
64
+ * - a loop body that reads and rewrites the value mid-run does so through the
65
+ * output's backing `inout` Variable (`flow-output-backing.ts`, #1092), because
66
+ * Studio Web neither updates nor reads an Output;
65
67
  * - the `default` on `<uipath:output>` is serialized (`collectVariablesFromNodes`)
66
68
  * and a subflow seeds its `out` globals from `defaultValue ?? null`
67
69
  * (`buildSeedTask`). The v1 CLI's `variable add --direction out
@@ -122,6 +122,26 @@ export declare function perCallDefinition(spec: {
122
122
  kind?: string;
123
123
  inputs?: any;
124
124
  } | undefined): any | undefined;
125
+ /**
126
+ * The definition version serialize emits for a typed action step that is not
127
+ * yet on the node-type registry: the author's `{ version }` when given,
128
+ * otherwise the version the step's inputs select, otherwise `undefined` (the
129
+ * pinned default). Two such families pick a version themselves:
130
+ *
131
+ * - `hitl`: `exposeError` selects `@1.2`, per-outcome routing selects `@1.1`;
132
+ * - `inlineAgent`: the newest bundled version, not the first bundled entry.
133
+ *
134
+ * The emit paths read their version here, and `check` resolves the definition
135
+ * it asks about with this same version (#1041), so the two cannot pick
136
+ * different definitions. For instance `inlineAgent({ ... })` gives the newest
137
+ * bundled version in both. A registered family (e.g. `dataFabricRead`, whose
138
+ * `resultMode` selects `core.datafabric.read@1.4`) gets the same guarantee from
139
+ * the registry's `resolveVersion`, which check and serialize both call.
140
+ */
141
+ export declare function actionVersion(spec: {
142
+ kind?: string;
143
+ inputs?: any;
144
+ } | undefined, explicit: string | undefined): string | undefined;
125
145
  /**
126
146
  * The bundled definition of `nodeType` that `makeNode` resolves: the author's
127
147
  * exact `{ version }` when given, otherwise the pinned default (the first
package/dist/serialize.js CHANGED
@@ -55,6 +55,8 @@ import { migrateToSchemaVersion } from './migrate.js';
55
55
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
56
56
  import { resolveSchemaVersion } from './schema-version.js';
57
57
  import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-variables.js';
58
+ import { outputBackingGlobal, planOutputBacking } from './flow-output-backing.js';
59
+ import { mapJsCode } from './core/js-code-spans.js';
58
60
  import { prepareCommand } from './core/cli-spelling.js';
59
61
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
60
62
  import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
@@ -3589,10 +3591,11 @@ function applyAuthorLayout(layout, scope, where) {
3589
3591
  * identically into ExpressionValue envelopes.
3590
3592
  */
3591
3593
  function renderUpdates(updates, rename) {
3592
- return Object.entries(updates).map(([variableId, val]) => {
3594
+ return Object.entries(updates).map(([name, val]) => {
3593
3595
  const expr = toExpr(val);
3594
3596
  const expression = expr.literal ? '=js:' + JSON.stringify(expr.value) : '=js:' + renameRefs(expr.js, rename);
3595
- return { variableId, expression };
3597
+ // An output is never an update target: its backing Variable is (#1092).
3598
+ return { variableId: rename.updates?.get(name) ?? name, expression };
3596
3599
  });
3597
3600
  }
3598
3601
  /**
@@ -3836,6 +3839,45 @@ export function perCallDefinition(spec) {
3836
3839
  default: return undefined;
3837
3840
  }
3838
3841
  }
3842
+ /**
3843
+ * The definition version serialize emits for a typed action step that is not
3844
+ * yet on the node-type registry: the author's `{ version }` when given,
3845
+ * otherwise the version the step's inputs select, otherwise `undefined` (the
3846
+ * pinned default). Two such families pick a version themselves:
3847
+ *
3848
+ * - `hitl`: `exposeError` selects `@1.2`, per-outcome routing selects `@1.1`;
3849
+ * - `inlineAgent`: the newest bundled version, not the first bundled entry.
3850
+ *
3851
+ * The emit paths read their version here, and `check` resolves the definition
3852
+ * it asks about with this same version (#1041), so the two cannot pick
3853
+ * different definitions. For instance `inlineAgent({ ... })` gives the newest
3854
+ * bundled version in both. A registered family (e.g. `dataFabricRead`, whose
3855
+ * `resultMode` selects `core.datafabric.read@1.4`) gets the same guarantee from
3856
+ * the registry's `resolveVersion`, which check and serialize both call.
3857
+ */
3858
+ export function actionVersion(spec, explicit) {
3859
+ if (explicit !== undefined)
3860
+ return explicit;
3861
+ // Keyed by the node type `bundledActionNodeType` already picks, so the
3862
+ // version rule adds no second dispatch on the step's kind.
3863
+ const type = bundledActionNodeType(spec);
3864
+ if (type === NODE_TYPE.hitl)
3865
+ return hitlNeededVersion(spec?.inputs);
3866
+ if (type === INLINE_AGENT_TYPE)
3867
+ return INLINE_AGENT_DEFAULT_VERSION;
3868
+ return undefined;
3869
+ }
3870
+ /**
3871
+ * The base human task version a task's inputs need when nothing is pinned:
3872
+ * `exposeError` needs 1.2 (it adds the `error` output), and a task that
3873
+ * routes per outcome needs 1.1. Only the base task reaches here: a variant
3874
+ * (quick-form, app task) has its own node type and one definition.
3875
+ */
3876
+ function hitlNeededVersion(inputs) {
3877
+ if (inputs?.exposeError)
3878
+ return '1.2';
3879
+ return hitlExitModel(inputs, undefined).shape === 'per-outcome' ? '1.1' : undefined;
3880
+ }
3839
3881
  /**
3840
3882
  * The bundled definition of `nodeType` that `makeNode` resolves: the author's
3841
3883
  * exact `{ version }` when given, otherwise the pinned default (the first
@@ -4224,7 +4266,14 @@ function emittableGlobals(built, where, kind = 'root') {
4224
4266
  if (plan.problems.length) {
4225
4267
  throw new Error(`${where}: ${plan.problems.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
4226
4268
  }
4227
- return plan;
4269
+ // An output the flow sets from inside gets an `inout` Variable to hold the
4270
+ // value; the End copies it into the Output (#1092, `flow-output-backing.ts`).
4271
+ const sharedOutputs = new Set(shared.targets.filter((t) => t.declaredAs === 'output').map((t) => t.name));
4272
+ const backing = planOutputBacking(built, plan.outputs, plan.vars, sharedOutputs);
4273
+ if (backing.problems.length) {
4274
+ throw new Error(`${where}: ${backing.problems.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
4275
+ }
4276
+ return { ...plan, backing: backing.backing, backingReads: backing.reads };
4228
4277
  }
4229
4278
  /**
4230
4279
  * The flow's `{ shared }` input plan (#924), refused here as `check` refuses it
@@ -4245,17 +4294,20 @@ function sharedInputsOf(built, where, kind) {
4245
4294
  throw new Error(`${where}: ${errors.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
4246
4295
  return plan;
4247
4296
  }
4248
- function newScope(outputs, vars = []) {
4297
+ function newScope(outputs, vars = [], backing = new Map(), backingReads = backing) {
4298
+ const typeOf = new Map(outputs.map((o) => [o.name, o.type]));
4249
4299
  return {
4250
4300
  nodes: [],
4251
4301
  edges: [],
4252
4302
  variableUpdates: {},
4253
- outputType: new Map(outputs.map((o) => [o.name, o.type])),
4303
+ outputType: typeOf,
4254
4304
  endVarType: new Map([
4255
4305
  ...outputs.map((o) => [o.name, o.type]),
4306
+ ...[...backing].map(([o, b]) => [b, typeOf.get(o)]),
4256
4307
  ...vars.map((vd) => [vd.name, vd.type]),
4257
4308
  ]),
4258
- rename: { nodeIds: new Map(), errorEnvelopes: new Map() },
4309
+ backing,
4310
+ rename: { nodeIds: new Map(), errorEnvelopes: new Map(), vars: new Map(backingReads), updates: new Map(backing) },
4259
4311
  pending: new Map(),
4260
4312
  agentSchemas: [],
4261
4313
  stepNodeId: new Map(),
@@ -4496,11 +4548,11 @@ function isTypeDesc(type) {
4496
4548
  * An End node's `outputs` map: every variable this scope exposes as a flow
4497
4549
  * output, in declaration order.
4498
4550
  *
4499
- * `explicit` holds the sources a `.return({…})` named. Everything else maps to
4500
- * ITSELF (`$vars.<name>`) — "this output is whatever the variable holds when the
4501
- * flow leaves here", which is what Studio Web wires when you add a variable to
4502
- * an End node, and the only sensible reading for a `.var(…)` counter or an
4503
- * output another branch returns.
4551
+ * `explicit` holds the sources a `.return({…})` named. Everything else gets
4552
+ * `fillInSource`: a Variable maps to ITSELF (`$vars.<name>`), "whatever it holds
4553
+ * when the flow leaves here"; an output maps from its backing Variable when a
4554
+ * step sets it, else to `null` (#1092). An End never reads `$vars.<output>`:
4555
+ * Studio Web does not declare Outputs on `$vars`.
4504
4556
  *
4505
4557
  * Emitting the full set is not cosmetic. The product's output-mapping rule warns
4506
4558
  * `MISSING_OUTPUT_MAPPING` once per (End node × unmapped output variable), and
@@ -4513,8 +4565,7 @@ function isTypeDesc(type) {
4513
4565
  function endOutputs(scope, explicit = {}) {
4514
4566
  const outs = {};
4515
4567
  for (const [name, type] of scope.endVarType) {
4516
- const source = explicit[name] ?? `=js:$vars.${name}`;
4517
- outs[name] = { type, description: '', source, var: name };
4568
+ outs[name] = { type, description: '', source: explicit[name] ?? fillInSource(scope, name), var: name };
4518
4569
  }
4519
4570
  // A `.return(…)` naming something the flow never declared is a check() error
4520
4571
  // (RETURN_UNDECLARED), but serialize must not silently swallow the mapping if
@@ -4525,6 +4576,23 @@ function endOutputs(scope, explicit = {}) {
4525
4576
  }
4526
4577
  return outs;
4527
4578
  }
4579
+ /**
4580
+ * What an End maps a variable to when no `.return()` named it here.
4581
+ *
4582
+ * - A Variable (`inout`, a backing one included) maps to itself: "whatever it
4583
+ * holds when the flow leaves here".
4584
+ * - An Output with a backing Variable maps from that Variable (#1092).
4585
+ * - Any other Output was never set on the way (only `.return()` values set
4586
+ * it, and this End returns none), so it maps to `null`. That is the value the
4587
+ * old `$vars.<output>` read gave on this path, without reading an Output,
4588
+ * which Studio Web does not allow.
4589
+ */
4590
+ function fillInSource(scope, name) {
4591
+ if (!scope.outputType.has(name))
4592
+ return `=js:$vars.${name}`;
4593
+ const backing = scope.backing.get(name);
4594
+ return backing ? `=js:$vars.${backing}` : '=js:null';
4595
+ }
4528
4596
  /**
4529
4597
  * A Terminate event has no action result, but it is still a terminal event. The
4530
4598
  * product's output-mapping validator therefore requires every flow output on it,
@@ -4547,19 +4615,23 @@ function terminateOutputs(scope) {
4547
4615
  * same name (an error in its own right — DUP_STEP).
4548
4616
  */
4549
4617
  function renameRefs(text, rename) {
4550
- if (rename.nodeIds.size === 0 && rename.errorEnvelopes.size === 0)
4618
+ if (rename.nodeIds.size === 0 && rename.errorEnvelopes.size === 0 && !rename.vars?.size)
4551
4619
  return text;
4552
4620
  // `.error` matches only with a FIELD behind it: a bare `$vars.<step>.error` is
4553
4621
  // the boolean "did this fail" (`err('<step>')`), which is truthy on every
4554
4622
  // family — the envelope object included — and must be left alone.
4555
- return text.replace(/\$vars\.([A-Za-z0-9_]+)(\.error(?=\.[A-Za-z0-9_]))?/g, (m, name, tail) => {
4623
+ // Only code is rewritten: `"$vars.summary"` in a script is text (#1092).
4624
+ return mapJsCode(text, (code) => code.replace(/\$vars\.([A-Za-z0-9_]+)(\.error(?=\.[A-Za-z0-9_]))?/g, (m, name, tail) => {
4625
+ const backing = rename.vars?.get(name);
4626
+ if (backing)
4627
+ return `$vars.${backing}${tail ?? ''}`;
4556
4628
  const to = rename.nodeIds.get(name);
4557
4629
  const nodeId = to && to.length > 0 ? to[0] : name;
4558
4630
  if (!tail)
4559
4631
  return nodeId === name ? m : `$vars.${nodeId}`;
4560
4632
  const envelope = rename.errorEnvelopes.get(name) ?? 'error';
4561
4633
  return `$vars.${nodeId}.${envelope}`;
4562
- });
4634
+ }));
4563
4635
  }
4564
4636
  /** Every step name a flow declares in its own scope (not its children's). */
4565
4637
  function stepNamesOf(steps, out = []) {
@@ -4704,7 +4776,7 @@ export function serialize(built, opts = {}) {
4704
4776
  const sidecars = opts.sidecars ?? [];
4705
4777
  const subflows = {};
4706
4778
  const rootGlobals = emittableGlobals(built, `flow "${built.id}"`);
4707
- const root = newScope(rootGlobals.outputs, rootGlobals.vars);
4779
+ const root = newScope(rootGlobals.outputs, rootGlobals.vars, rootGlobals.backing, rootGlobals.backingReads);
4708
4780
  // `input('x')` lowers eagerly to `$vars.start.output.x` — `start` being the
4709
4781
  // trigger node's default id. When the flow renames the trigger, register that
4710
4782
  // rename so the SAME pass that rewrites step-name references rewrites these
@@ -4881,7 +4953,7 @@ export function serialize(built, opts = {}) {
4881
4953
  */
4882
4954
  function emitSubflowBody(child, nodeId) {
4883
4955
  const childGlobals = emittableGlobals(child, `subflow "${nodeId}"`, 'subflow');
4884
- const scope = newScope(childGlobals.outputs, childGlobals.vars);
4956
+ const scope = newScope(childGlobals.outputs, childGlobals.vars, childGlobals.backing, childGlobals.backingReads);
4885
4957
  // Claim this copy's node ids UP FRONT, so the rename map is complete before
4886
4958
  // the first expression renders. A child's step names are free the first
4887
4959
  // time it runs; if any of them is already taken — which is what a second
@@ -4919,6 +4991,11 @@ export function serialize(built, opts = {}) {
4919
4991
  globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId: startId, ...varMeta(inp) });
4920
4992
  for (const o of childGlobals.outputs)
4921
4993
  globals.push({ id: o.name, name: o.name, direction: 'out', type: o.type, ...varMeta(o) });
4994
+ for (const o of childGlobals.outputs) {
4995
+ const b = childGlobals.backing.get(o.name);
4996
+ if (b)
4997
+ globals.push(outputBackingGlobal(o, b));
4998
+ }
4922
4999
  for (const vd of childGlobals.vars)
4923
5000
  globals.push({ id: vd.name, name: vd.name, direction: 'inout', type: vd.type, ...varMeta(vd) });
4924
5001
  resolveRejoins(scope, addEdge);
@@ -5356,7 +5433,7 @@ export function serialize(built, opts = {}) {
5356
5433
  // the node to another version as a side effect (#956). Both now live in
5357
5434
  // the sidecar and on edges; neither selects a definition. An explicit
5358
5435
  // `{ version }` (what decompile writes back) resolves exactly.
5359
- const version = step.options?.version ?? INLINE_AGENT_DEFAULT_VERSION;
5436
+ const version = actionVersion(spec, step.options?.version);
5360
5437
  // `definitions[]` holds one entry per type@version, so a flow where any
5361
5438
  // agent attaches memory keeps the handle for all of them.
5362
5439
  const withMemory = s.memory !== undefined
@@ -6278,13 +6355,12 @@ export function serialize(built, opts = {}) {
6278
6355
  throw new Error(`"${step.name}": outcomePorts: true, but the '${spec.inputs?.variant}' variant reads its outcomes `
6279
6356
  + `from its app and leaves on its single 'completed' exit. Route on out('${step.name}', 'Action') downstream.`);
6280
6357
  }
6281
- const neededVersion = hitlType !== NODE_TYPE.hitl ? undefined
6282
- : spec.inputs?.exposeError ? '1.2' : exitModel.shape === 'per-outcome' ? '1.1' : undefined;
6283
6358
  if (spec.inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
6284
6359
  throw new Error(`"${step.name}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
6285
6360
  + `Drop the pin or match it.`);
6286
6361
  }
6287
- const node = makeNode(hitlType, id, step.name, { parentId, requestedVersion: pinned ?? neededVersion });
6362
+ // The same version `check` resolves the definition at (#1041).
6363
+ const node = makeNode(hitlType, id, step.name, { parentId, requestedVersion: actionVersion(spec, pinned) });
6288
6364
  const { inputs, outputs } = hitlInputs(spec.inputs, id, built.id, step.name, scope.rename);
6289
6365
  node.inputs = inputs;
6290
6366
  // The definition's `outputDefinition` gives the two output SLOTS; the form
@@ -6881,10 +6957,14 @@ export function serialize(built, opts = {}) {
6881
6957
  for (const [name, expr] of Object.entries(step.values)) {
6882
6958
  const src = expr.literal ? '=js:' + JSON.stringify(expr.value) : '=js:' + renameRefs(expr.js, scope.rename);
6883
6959
  explicit[name] = src;
6884
- // Only the values the author actually RETURNED are written back to the
6885
- // variable. The identity mappings `endOutputs` fills in read a
6886
- // variable; a variableUpdate for one would be a no-op assignment.
6887
- updates.push({ variableId: name, expression: src });
6960
+ // The End's output mapping is what sets an Output; Studio Web writes
6961
+ // no variable update for it (`end-node-sets-output.flow`), and an
6962
+ // Output is not an update target (#1092). A returned `.var()` is a
6963
+ // Variable, so it is still written back. The identity mappings
6964
+ // `endOutputs` fills in read a variable; an update for one would be
6965
+ // a no-op assignment.
6966
+ if (!scope.outputType.has(name))
6967
+ updates.push({ variableId: name, expression: src });
6888
6968
  }
6889
6969
  emitEnd(scope, id, tails, { label: step.options?.label, parentId, requestedVersion: step.options?.version, explicit });
6890
6970
  const extra = step.options?.updates ?? {};
@@ -7035,6 +7115,11 @@ export function serialize(built, opts = {}) {
7035
7115
  globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId, ...varMeta(inp) });
7036
7116
  for (const o of rootGlobals.outputs)
7037
7117
  globals.push({ id: o.name, name: o.name, direction: 'out', type: o.type, ...varMeta(o) });
7118
+ for (const o of rootGlobals.outputs) {
7119
+ const b = rootGlobals.backing.get(o.name);
7120
+ if (b)
7121
+ globals.push(outputBackingGlobal(o, b));
7122
+ }
7038
7123
  for (const vd of rootGlobals.vars)
7039
7124
  globals.push({ id: vd.name, name: vd.name, direction: 'inout', type: vd.type, ...varMeta(vd) });
7040
7125
  for (const ep of entryPoints) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.20.4",
3
+ "version": "6.20.6",
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": "7dcc7ebc0420e1fca8be0368269c8f20ff60eff6"
95
+ "gitref": "124c0cac0f49d9974da5619817458d4d8760a91f"
96
96
  }