@uipath/maestro-builder-sdk 6.24.0 → 6.25.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.
package/dist/serialize.js CHANGED
@@ -7,7 +7,6 @@
7
7
  * the platform emits, so the result passes `uip maestro flow validate`.
8
8
  */
9
9
  import { readFileSync } from 'node:fs';
10
- import { createHash } from 'node:crypto';
11
10
  import { fileURLToPath } from 'node:url';
12
11
  import { dirname, join } from 'node:path';
13
12
  import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
@@ -21,19 +20,19 @@ import { declaredErrorPortRefusal, definitionHasErrorPort, errorPortRefusal } fr
21
20
  import { FLOW_NODE_TYPES } from './flow-node-registry.js';
22
21
  import { stepExits } from './flow-step-exits.js';
23
22
  import { resolveVersion } from './core/nodes/versioned.js';
24
- import { isAnswerField } from './core/hitl-answers.js';
23
+ import { FORM_FIELD_JSON_TYPE } from './core/form-field-type.js';
25
24
  import { connectionConnectors } from './bindings-v2.js';
26
25
  import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
27
26
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
28
27
  import { genericEventObjectRefusal } from './core/event-checks.js';
29
- import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeIdOf, outcomeSlug } from './core/hitl-routing.js';
28
+ import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
30
29
  import { STICKY_NOTE_TYPE, STICKY_NOTE_TYPE_VERSION } from './core/definitionless-node-types.js';
31
30
  import { placeholderPosition } from './node-placement.js';
32
31
  import { hasExits } from './core/step-ports.js';
33
32
  import { stopsAtEnd } from './flow-step-tree.js';
34
33
  import { assertNodeEnvelope } from './core/node-envelope.js';
35
34
  import { buildConfiguration } from './config.js';
36
- import { fnv1aX4Uuid, stableId } from './core/stable-id.js';
35
+ import { fnv1aX4Uuid, schemaId, stableId } from './core/stable-id.js';
37
36
  import { readEventFilter, eventFilterProblem, eventFilterJmes, eventFilterTreeLeaf, jmesString, jmesStringProblem } from './core/event-filters.js';
38
37
  import { FLOW_EXPR_DIALECT } from './core/connector-checks.js';
39
38
  import { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors, multipartBodyFieldName, transportHttpMethod } from './core/connector-inputs.js';
@@ -95,16 +94,12 @@ const NODE_TYPE = {
95
94
  conversationalAgent: 'uipath.agent.conversational',
96
95
  createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
97
96
  endCall: 'uipath.conversational.voice.end-call',
98
- hitl: 'uipath.human-in-the-loop',
99
97
  // The two other spellings of the SAME human task, both resolvable on this
100
98
  // tenant. `hitl()` emits the base type unless `variant` names one of these;
101
99
  // all three share the model (`bpmn:UserTask` / `Actions.HITL`), the single
102
100
  // `completed` handle and the `output`/`status` output pair, so only the type
103
101
  // string and the form's OWNER differ. (A third, `…​.document-validation`, is
104
102
  // `AvailableOnTenant: false` — no definition resolves, so it is not here.)
105
- hitlQuickForm: 'uipath.human-in-the-loop.quick-form',
106
- hitlActionApp: 'uipath.human-in-the-loop.coded-action-app',
107
- hitlDocValidation: 'uipath.human-in-the-loop.document-validation',
108
103
  // The PLATFORM's placeholder — "a real node goes here later". Not the engine's
109
104
  // fixture-returning `kind: 'mock'` test construct, which shares only the name
110
105
  // (#125). The definition declares no `inputDefinition` and one output whose
@@ -2998,245 +2993,6 @@ function scheduleInputs(inputs, nodeId) {
2998
2993
  ...(isPreset ? {} : { timerValue: every }),
2999
2994
  };
3000
2995
  }
3001
- /** JSON-Schema type per form-field type, as flow-v1's form serializer maps them (quick-form escalations and human tasks). */
3002
- const FORM_FIELD_JSON_TYPE = {
3003
- text: { type: 'string' },
3004
- number: { type: 'number' },
3005
- boolean: { type: 'boolean' },
3006
- 'date-time': { type: 'string', format: 'date-time' },
3007
- };
3008
- /**
3009
- * A human task's inputs, and the outputs its form implies.
3010
- *
3011
- * Three things the author does not state and the platform needs:
3012
- *
3013
- * 1. **`type: 'quick'`** — the node type carries both an inline quick form and an
3014
- * App-backed task, selected by this field. flow-v1's serializer defaults an
3015
- * absent value to `quick`, but a reader cannot rely on that default, so the
3016
- * serializer writes it (the same "the encoding is the serializer's job" call as
3017
- * the scheduled trigger's `timerPreset`/`timerValue` split). **Only on the base
3018
- * node type**: on either suffixed variant the node TYPE is the choice, so no
3019
- * `inputs.type` is written — 165 of the 175 deployed `.quick-form` instances
3020
- * omit it, neither variant's definition declares it, and flow-v1's serializer
3021
- * DERIVES its `hitlType` from the node type rather than reading the field
3022
- * (`hitl-serialization.ts:140-146`).
3023
- * 2. **`recipient`** — who gets the task. Emitted from the definition's own
3024
- * `inputDefaults`: Email + Action Center, assigned to a group. That is what
3025
- * 203 of 233 deployed, debug-green v1 human tasks carry, unchanged.
3026
- * 3. **`schema.schemaId`** — the stable id the backend caches the form under
3027
- * (229 of those 233 flows have one). Derived from the flow and node ids so the
3028
- * emitted `.flow` is byte-stable across builds.
3029
- *
3030
- * And the outputs, which the DEFINITION cannot declare: a quick form's result keys
3031
- * are its own answer fields, so `uipath.human-in-the-loop@1.0` says only
3032
- * `output: { type: 'object' }` and the instance carries the shape — `properties`
3033
- * per answer field plus `Action` enumerated over the outcome names, and `status`
3034
- * (the platform's `=result.Action` projection) enumerated the same way. This is
3035
- * exactly what deployed v1 flows carry, and it is what lets `gen-tests` synthesize
3036
- * a replay fixture a downstream read can actually use.
3037
- */
3038
- function hitlInputs(inputs, nodeId, flowId, label, rename) {
3039
- const nodeType = hitlNodeType(inputs);
3040
- const isApp = nodeType === NODE_TYPE.hitlActionApp;
3041
- const fields = (inputs?.fields ?? []).map((f) => {
3042
- const base = {
3043
- id: f.id,
3044
- label: f.label ?? f.id,
3045
- type: f.type,
3046
- direction: f.direction,
3047
- };
3048
- if (f.direction === 'input') {
3049
- // The platform reads a field's `binding` as a variable PATH (`vars.…`),
3050
- // not as an expression — so the Expr's `$vars.x` form is rewritten here
3051
- // rather than asked of the author.
3052
- base.binding = bindingPath(f.value, rename);
3053
- }
3054
- else if (f.direction === 'inOut') {
3055
- // Pre-filled AND editable: `binding` is the canonical pre-fill (the data
3056
- // contract flow-v1's serializer reads), and the WRITE target is the field
3057
- // id itself — deliberately decoupled from the binding, so no `variable`.
3058
- base.binding = bindingPath(f.value, rename);
3059
- if (f.required)
3060
- base.required = true;
3061
- }
3062
- else {
3063
- // The answer's landing variable. The platform falls back to the field id
3064
- // when this is absent; naming it keeps the emitted flow explicit and
3065
- // matches what the designer writes.
3066
- base.variable = `vars.${f.id}`;
3067
- if (f.required)
3068
- base.required = true;
3069
- }
3070
- return base;
3071
- });
3072
- const outcomes = (inputs?.outcomes ?? []).map((o, i) => {
3073
- const name = typeof o === 'string' ? o : o?.name;
3074
- const action = typeof o === 'string' ? 'Continue' : (o?.action ?? 'Continue');
3075
- return { id: outcomeIdOf(o, i), name, type: 'string', isPrimary: i === 0, action };
3076
- });
3077
- const properties = {};
3078
- for (const f of inputs?.fields ?? []) {
3079
- // inOut fields land in the answers too — their reviewed value flows back.
3080
- if (!isAnswerField(f))
3081
- continue;
3082
- properties[f.id] = { ...ownEntry(FORM_FIELD_JSON_TYPE, String(f.type)) };
3083
- }
3084
- const names = outcomes.map((o) => o.name).filter((n) => typeof n === 'string');
3085
- if (names.length > 0) {
3086
- properties.Action = { type: 'string', enum: names, default: names[0] };
3087
- }
3088
- // The app reference and its input map, for the `action-app` variant only. The
3089
- // keys are flow-v1's own app-picker names (`name`/`key`/`folderPath`, plus the
3090
- // `appSystemName` that stays null until an app's first deployment); the
3091
- // bindings map the app's OWN parameter names to `=`-prefixed expressions,
3092
- // which is the form v1's serializer turns into `HitlTaskArguments`.
3093
- const app = inputs?.app;
3094
- const appInputBindings = {};
3095
- for (const [name, value] of Object.entries(app?.inputs ?? {})) {
3096
- appInputBindings[name] = appBindingExpr(value, rename);
3097
- }
3098
- // The author's recipient when given; the definition's own default otherwise
3099
- // (unchanged — that default is what 203 of 233 deployed v1 tasks carry).
3100
- const defaultRecipient = jsonClone(defByType.get(nodeType)?.inputDefaults?.recipient ?? {});
3101
- const r = inputs?.recipient;
3102
- const recipient = r
3103
- ? {
3104
- channels: r.channels ?? (defaultRecipient.channels ?? []),
3105
- // Preserve the default's shape: quick-form's default carries an empty
3106
- // `connections` map, the others none — keep whichever, unless the
3107
- // author supplies their own.
3108
- ...(r.connections !== undefined
3109
- ? { connections: r.connections }
3110
- : 'connections' in defaultRecipient ? { connections: defaultRecipient.connections } : {}),
3111
- assignee: {
3112
- type: r.assignee.type,
3113
- ...(r.assignee.value !== undefined ? { value: r.assignee.value } : {}),
3114
- ...(r.assignee.displayName !== undefined ? { displayName: r.assignee.displayName } : {}),
3115
- },
3116
- }
3117
- : defaultRecipient;
3118
- // Document Validation carries the station's own inputs instead of a form; the
3119
- // expressions ride as `=js:` strings, EV-wrapped by the profile migration like
3120
- // any other input (the family is not in UNWRAPPED_INPUTS).
3121
- // The station's expression fields ride as PLAIN `$vars.…` strings — the
3122
- // property panel's own placeholder shape, and what the platform validator
3123
- // reads (it string-checks them directly; an ExpressionValue envelope fails
3124
- // with "extractionResult?.trim is not a function").
3125
- const docPath = (v) => {
3126
- const e = toExpr(v);
3127
- return e.literal ? String(e.value) : renameRefs(e.js, rename);
3128
- };
3129
- const doc = inputs?.document;
3130
- const docvalInputs = nodeType === NODE_TYPE.hitlDocValidation && doc
3131
- ? {
3132
- validationRenderType: doc.render ?? 'standard',
3133
- extractionResult: docPath(doc.extractionResult),
3134
- ...(doc.storageBucket !== undefined ? { storageBucket: doc.storageBucket } : {}),
3135
- ...(doc.documentId !== undefined ? { documentId: doc.documentId } : {}),
3136
- ...(doc.taxonomy !== undefined ? { taxonomy: docPath(doc.taxonomy) } : {}),
3137
- }
3138
- : {};
3139
- // 1.2's error output, verbatim from the bundled definition — the instance
3140
- // outputs replace the definition's (see the caller), so the slot is re-added
3141
- // here when the author asked for it.
3142
- const errorOutput = inputs?.exposeError
3143
- ? DEFS['uipath.human-in-the-loop@1.2']?.outputDefinition?.error
3144
- : undefined;
3145
- return {
3146
- inputs: {
3147
- // The base node type alone needs the discriminator — see the doc block.
3148
- ...(nodeType === NODE_TYPE.hitl ? { type: 'quick' } : {}),
3149
- title: inputs?.title ?? label,
3150
- priority: inputs?.priority ?? 'Low',
3151
- ...(inputs?.labels !== undefined ? { labels: inputs.labels } : {}),
3152
- recipient,
3153
- ...docvalInputs,
3154
- ...((isApp || (nodeType === NODE_TYPE.hitlDocValidation && doc?.render === 'custom')) && app
3155
- ? {
3156
- app: {
3157
- displayName: app.name,
3158
- name: app.name,
3159
- key: app.key,
3160
- folderPath: app.folderPath,
3161
- ...(app.appSystemName !== undefined ? { appSystemName: app.appSystemName } : {}),
3162
- },
3163
- ...(Object.keys(appInputBindings).length > 0 ? { appInputBindings } : {}),
3164
- }
3165
- : {}),
3166
- // The app variant still carries a `schema` — with NO fields (the app owns
3167
- // the form) but WITH the outcomes, because the flow's own routing and its
3168
- // `Action`/`status` outputs are derived from them. Both of flow-v1's
3169
- // app-task node references write exactly this shape.
3170
- schema: { schemaId: schemaId(flowId, nodeId), fields, outcomes },
3171
- },
3172
- outputs: {
3173
- output: {
3174
- type: 'object',
3175
- description: 'Task result data',
3176
- source: '=result',
3177
- var: 'output',
3178
- ...(Object.keys(properties).length > 0 ? { properties } : {}),
3179
- },
3180
- status: {
3181
- type: 'string',
3182
- description: 'Task completion status',
3183
- source: '=result.Action',
3184
- var: 'status',
3185
- ...(names.length > 0 ? { enum: names, default: names[0] } : {}),
3186
- },
3187
- ...(errorOutput !== undefined ? { error: jsonClone(errorOutput) } : {}),
3188
- },
3189
- };
3190
- }
3191
- /**
3192
- * Which of the three human-task node types this step emits.
3193
- *
3194
- * `undefined` is deliberately the base type and not an alias for `.quick-form`:
3195
- * the default emission must not change, so a flow authored before `variant`
3196
- * existed compiles to the same bytes it always did.
3197
- */
3198
- function hitlNodeType(inputs) {
3199
- if (inputs?.variant === 'quick-form')
3200
- return NODE_TYPE.hitlQuickForm;
3201
- if (inputs?.variant === 'action-app')
3202
- return NODE_TYPE.hitlActionApp;
3203
- if (inputs?.variant === 'document-validation')
3204
- return NODE_TYPE.hitlDocValidation;
3205
- return NODE_TYPE.hitl;
3206
- }
3207
- /**
3208
- * One `appInputBindings` value: the expression sent to a deployed app's input.
3209
- *
3210
- * flow-v1 writes these `=`-prefixed and WITHOUT `js:` (`"=vars.<node>.output.<f>"`
3211
- * — `hitl-node-apptask.md`), and its serializer normalizes each to `=js:<body>`
3212
- * when it builds `HitlTaskArguments`. So the flow-level form is the bare
3213
- * `=`-prefixed path, which is what our own `$vars.` expressions become once the
3214
- * prefix is dropped. A plain string passes through as written.
3215
- */
3216
- function appBindingExpr(value, rename) {
3217
- if (!(value instanceof Expr))
3218
- return String(value ?? '');
3219
- const js = renameRefs(value.js, rename).trim();
3220
- const path = js.startsWith('$vars.') ? js.slice('$vars.'.length) : js;
3221
- return path.startsWith('=') ? path : `=${path.startsWith('vars.') ? path : `vars.${path}`}`;
3222
- }
3223
- /**
3224
- * A shown field's `binding`: the platform resolves it as a path against the
3225
- * workflow's variables (`vars.<flowInput>`, `vars.<node>.output.<field>`), so the
3226
- * `$vars.` prefix our expressions carry is dropped. A computed expression cannot
3227
- * be a path — `check` rejects one (HITL_FIELD_VALUE_NOT_PATH) before we get here.
3228
- */
3229
- function bindingPath(value, rename) {
3230
- if (!(value instanceof Expr))
3231
- return String(value ?? '');
3232
- const js = renameRefs(value.js, rename).trim();
3233
- const path = js.startsWith('$vars.') ? js.slice('$vars.'.length) : js;
3234
- // `vars.`-prefixed, which is what 526 of 528 bound fields in deployed v1 flows
3235
- // carry. flow-v1's serializer does prefix a bare identifier itself
3236
- // (`hitl-serialization.ts`), but only for `HitlTaskArguments` — the
3237
- // `__hitlInputBindings` the designer reads back stores the binding verbatim.
3238
- return path.startsWith('vars.') ? path : `vars.${path}`;
3239
- }
3240
2996
  /**
3241
2997
  * `Approve` → `approve`; a name that slugifies to nothing falls back to its index.
3242
2998
  * The same rule that names the `outcome-<id>` ports (`outcomeSlug`), so an id
@@ -3245,22 +3001,6 @@ function bindingPath(value, rename) {
3245
3001
  function outcomeId(name, index) {
3246
3002
  return outcomeSlug(name, index);
3247
3003
  }
3248
- /**
3249
- * A deterministic UUID for the form schema, derived from the flow and node ids.
3250
- *
3251
- * The platform uses `schemaId` to identify and cache a form, so it has to be
3252
- * stable across builds of the same flow — a random one would make every rebuild a
3253
- * different form and every emitted `.flow` a different file. Shaped as a v5 UUID
3254
- * (SHA-1 based, version + variant bits set) because that is what the field holds.
3255
- */
3256
- function schemaId(flowId, nodeId) {
3257
- const h = createHash('sha1').update(`uipath.human-in-the-loop:${flowId}:${nodeId}`).digest();
3258
- const b = Buffer.from(h.subarray(0, 16));
3259
- b[6] = (b[6] & 0x0f) | 0x50;
3260
- b[8] = (b[8] & 0x3f) | 0x80;
3261
- const hex = b.toString('hex');
3262
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3263
- }
3264
3004
  /**
3265
3005
  * `serialize`, plus the extra files the flow needs on disk beside it.
3266
3006
  *
@@ -3629,7 +3369,6 @@ function describeDeclared(bindings) {
3629
3369
  */
3630
3370
  export function bundledActionNodeType(spec) {
3631
3371
  switch (spec?.kind) {
3632
- case 'hitl': return hitlNodeType(spec.inputs);
3633
3372
  case 'inlineAgent': return INLINE_AGENT_TYPE;
3634
3373
  case 'subflow':
3635
3374
  case 'createOutgoingCall':
@@ -3686,10 +3425,8 @@ export function perCallDefinition(spec) {
3686
3425
  * The definition version serialize emits for a typed action step that is not
3687
3426
  * yet on the node-type registry: the author's `{ version }` when given,
3688
3427
  * otherwise the version the step's inputs select, otherwise `undefined` (the
3689
- * pinned default). Two such families pick a version themselves:
3690
- *
3691
- * - `hitl`: `exposeError` selects `@1.2`, per-outcome routing selects `@1.1`;
3692
- * - `inlineAgent`: the newest bundled version, not the first bundled entry.
3428
+ * pinned default). One such family picks a version itself: `inlineAgent`
3429
+ * takes the newest bundled version, not the first bundled entry.
3693
3430
  *
3694
3431
  * The emit paths read their version here, and `check` resolves the definition
3695
3432
  * it asks about with this same version (#1041), so the two cannot pick
@@ -3704,23 +3441,10 @@ export function actionVersion(spec, explicit) {
3704
3441
  // Keyed by the node type `bundledActionNodeType` already picks, so the
3705
3442
  // version rule adds no second dispatch on the step's kind.
3706
3443
  const type = bundledActionNodeType(spec);
3707
- if (type === NODE_TYPE.hitl)
3708
- return hitlNeededVersion(spec?.inputs);
3709
3444
  if (type === INLINE_AGENT_TYPE)
3710
3445
  return INLINE_AGENT_DEFAULT_VERSION;
3711
3446
  return undefined;
3712
3447
  }
3713
- /**
3714
- * The base human task version a task's inputs need when nothing is pinned:
3715
- * `exposeError` needs 1.2 (it adds the `error` output), and a task that
3716
- * routes per outcome needs 1.1. Only the base task reaches here: a variant
3717
- * (quick-form, app task) has its own node type and one definition.
3718
- */
3719
- function hitlNeededVersion(inputs) {
3720
- if (inputs?.exposeError)
3721
- return '1.2';
3722
- return hitlExitModel(inputs, undefined).shape === 'per-outcome' ? '1.1' : undefined;
3723
- }
3724
3448
  /**
3725
3449
  * The bundled definition of `nodeType` that `makeNode` resolves: the author's
3726
3450
  * exact `{ version }` when given, otherwise the pinned default (the first
@@ -4972,14 +4696,6 @@ export function serialize(built, opts = {}) {
4972
4696
  function successPort(type) {
4973
4697
  if (type === NODE_TYPE.loop)
4974
4698
  return 'success';
4975
- // Every human-task definition declares one static source handle, `completed`.
4976
- // It is the real exit only where the product draws it: action-app,
4977
- // document-validation, and a task with no outcomes. A base or quick-form task
4978
- // with outcomes leaves on `outcome-<id>` instead (`hitlExitModel`), and never
4979
- // reaches this function for its tail.
4980
- if (type === NODE_TYPE.hitl || type === NODE_TYPE.hitlQuickForm || type === NODE_TYPE.hitlActionApp
4981
- || type === NODE_TYPE.hitlDocValidation)
4982
- return 'completed';
4983
4699
  // The inline agent's forward handle is `success`, not `output` — its definition
4984
4700
  // declares `success` (with `isDefaultForType`) + `error` on the right, and 29 of
4985
4701
  // 29 deployed instances wire the outgoing edge to `success`. The PUBLISHED agent
@@ -5049,7 +4765,12 @@ export function serialize(built, opts = {}) {
5049
4765
  delete node.model;
5050
4766
  node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
5051
4767
  if (contribution.outputs) {
5052
- node.outputs = { ...(node.outputs ?? {}), ...subNodeId(jsonClone(contribution.outputs), id) };
4768
+ // Replaced outputs are the instance's own, written verbatim: a human task's
4769
+ // come from its form, where a literal `{nodeId}` in an outcome name or a
4770
+ // field id is the author's text, not a template.
4771
+ node.outputs = contribution.replaceOutputs
4772
+ ? jsonClone(contribution.outputs)
4773
+ : { ...(node.outputs ?? {}), ...subNodeId(jsonClone(contribution.outputs), id) };
5053
4774
  }
5054
4775
  // A bundled registered type returns no definition; its bindings still close
5055
4776
  // against the bundled one (#942), counting what it bound through the context
@@ -5057,8 +4778,17 @@ export function serialize(built, opts = {}) {
5057
4778
  const def = o.def ?? bundledDefinition(o.typeName, o.requestedVersion);
5058
4779
  hoistContributionBindings(def, [...(o.bound ?? []), ...(contribution.bindings ?? [])], o.where);
5059
4780
  scope.nodes.push(node);
4781
+ // A typed human task whose exits the product derives from its outcomes: every
4782
+ // edge out of it is held to that rule (`hitlExitViolation`, in addEdge), and
4783
+ // the step list treats its tails as outcome edges. A rawNode of the type is
4784
+ // not one: it routes by the port lists its author wrote.
4785
+ if (o.trackOutcomes && OUTCOME_HANDLE_NODE_TYPES.has(o.typeName))
4786
+ hitlNodes.set(id, node);
5060
4787
  for (const t of o.tails)
5061
4788
  addEdge(scope, t, id);
4789
+ if (contribution.fanOut) {
4790
+ return contribution.fanOut.map((f) => ({ node: id, port: f.port, ...(f.endsRun ? { endsRun: true } : {}) }));
4791
+ }
5062
4792
  const port = contribution.port ?? contributionPort(def);
5063
4793
  if (port === undefined) {
5064
4794
  // Only templated exits: no main path leaves this node. Remember it so a
@@ -5085,7 +4815,10 @@ export function serialize(built, opts = {}) {
5085
4815
  scope.rename.errorEnvelopes.set(step.name, impl.errorEnvelope);
5086
4816
  const ctx = {
5087
4817
  step: step.name,
4818
+ nodeId: id,
4819
+ flowId: built.id,
5088
4820
  version: impl.version,
4821
+ pinnedVersion: step.options?.version,
5089
4822
  render: (value) => renderValue(value, scope.rename),
5090
4823
  renameRefs: (js) => renameRefs(js, scope.rename),
5091
4824
  stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
@@ -5131,12 +4864,21 @@ export function serialize(built, opts = {}) {
5131
4864
  }
5132
4865
  declaredErrorPorts.set(id, { port, typeName: impl.typeName, version: impl.version });
5133
4866
  }
5134
- if (contribution.port !== undefined && !declaresForwardPort(def ?? bundledDefinition(impl.typeName, impl.version), contribution.port)) {
5135
- throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} leaves by port "${contribution.port}", `
5136
- + 'which its definition declares no source handle for.');
4867
+ // A forward port is a handle the definition declares, one of the type's
4868
+ // author-named exits, or one the product derives (`derivesPort`: a human
4869
+ // task's `outcome-<id>` handles, where the definition lists only `completed`).
4870
+ const exits = impl.exits?.(spec, step.options?.version);
4871
+ const exitPorts = new Set(exits && hasExits(exits) ? exits.map((e) => e.port) : []);
4872
+ for (const p of [...(contribution.port !== undefined ? [contribution.port] : []), ...(contribution.fanOut ?? []).map((f) => f.port)]) {
4873
+ if (exitPorts.has(p) || impl.derivesPort?.(spec, p)
4874
+ || declaresForwardPort(def ?? bundledDefinition(impl.typeName, impl.version), p))
4875
+ continue;
4876
+ throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} leaves by port "${p}", `
4877
+ + 'which its definition declares no source handle for, and the type neither lists as an exit nor derives.');
5137
4878
  }
5138
4879
  return placeStepNode({
5139
4880
  where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: impl.version, def, contribution, bound, tails, scope, parentId,
4881
+ trackOutcomes: true,
5140
4882
  });
5141
4883
  }
5142
4884
  function emitAction(step, tails, scope, parentId) {
@@ -6082,57 +5824,6 @@ export function serialize(built, opts = {}) {
6082
5824
  addEdge(scope, t, id);
6083
5825
  return [{ node: id, port: successPort(rc.nodeType) }];
6084
5826
  }
6085
- if (spec.kind === 'hitl') {
6086
- const id = stepUid(scope, step.name);
6087
- const hitlType = hitlNodeType(spec.inputs);
6088
- // How the task leaves its node — `hitlExitModel` (core/hitl-routing.ts)
6089
- // has the product rule and what `.step()` means after each shape.
6090
- //
6091
- // Only the BASE node has definition versions to select: routing per
6092
- // outcome picks 1.1 and `exposeError` picks 1.2 (which adds the `error`
6093
- // output). quick-form draws the same per-outcome handles on the one
6094
- // definition it has, so it routes per outcome without moving. An explicit
6095
- // pin wins over the inferred 1.1 — `flow validate` accepts `outcome-<id>`
6096
- // edges on base@1.0 — but not over `exposeError`, whose output only 1.2
6097
- // declares. A zero- or one-outcome task stays on the pinned default.
6098
- const pinned = step.options?.version;
6099
- const exitModel = hitlExitModel(spec.inputs, pinned);
6100
- if (hitlType !== NODE_TYPE.hitl && spec.inputs?.exposeError === true) {
6101
- throw new Error(`"${step.name}": exposeError needs the base human task's 1.2 definition, which adds the `
6102
- + `error output — the '${spec.inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
6103
- }
6104
- if (spec.inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(hitlType)) {
6105
- throw new Error(`"${step.name}": outcomePorts: true, but the '${spec.inputs?.variant}' variant reads its outcomes `
6106
- + `from its app and leaves on its single 'completed' exit. Route on out('${step.name}', 'Action') downstream.`);
6107
- }
6108
- if (spec.inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
6109
- throw new Error(`"${step.name}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
6110
- + `Drop the pin or match it.`);
6111
- }
6112
- // The same version `check` resolves the definition at (#1041).
6113
- const node = makeNode(hitlType, id, step.name, { parentId, requestedVersion: actionVersion(spec, pinned) });
6114
- const { inputs, outputs } = hitlInputs(spec.inputs, id, built.id, step.name, scope.rename);
6115
- node.inputs = inputs;
6116
- // The definition's `outputDefinition` gives the two output SLOTS; the form
6117
- // gives their shape (see hitlInputs), so the instance's version wins.
6118
- node.outputs = outputs;
6119
- scope.nodes.push(node);
6120
- if (OUTCOME_HANDLE_NODE_TYPES.has(hitlType))
6121
- hitlNodes.set(id, node);
6122
- for (const t of tails)
6123
- addEdge(scope, t, id);
6124
- if (exitModel.shape === 'per-outcome') {
6125
- // The PRIMARY (first) outcome continues the main path; the author routes
6126
- // the rest with .stepToList('outcome-<id>', …) or as .stepSwitch arms.
6127
- return [{ node: id, port: exitModel.exits[0]?.port ?? `outcome-${outcomeSlug(undefined, 0)}` }];
6128
- }
6129
- if (exitModel.shape === 'every-outcome') {
6130
- // Fan-out: every outcome continues to the next step, each on its own
6131
- // handle. End outcomes are marked, and `emit` ends them.
6132
- return exitModel.exits.map((e) => ({ node: id, port: e.port, ...(e.endsProcess ? { endsRun: true } : {}) }));
6133
- }
6134
- return [{ node: id, port: successPort(hitlType) }];
6135
- }
6136
5827
  // A custom FlowAction subclass carries no `kind`; it compiles through its
6137
5828
  // own contribution (design §4). Anything else without a recognized kind is
6138
5829
  // an authoring error — it must NOT fall through into the script branch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.24.0",
3
+ "version": "6.25.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",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "a3e8314b01bff6cf76316765840e31bd2e3860e5"
95
+ "gitref": "119258f247ce3ff7e3b3b3faf21ab2e2b13eca5a"
96
96
  }