@uipath/maestro-builder-sdk 6.23.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.
Files changed (46) hide show
  1. package/dist/api-index.md +121 -121
  2. package/dist/api-members.md +362 -362
  3. package/dist/check.js +1 -444
  4. package/dist/core/actions.d.ts +0 -404
  5. package/dist/core/actions.js +0 -111
  6. package/dist/core/form-field-type.d.ts +16 -0
  7. package/dist/core/form-field-type.js +33 -0
  8. package/dist/core/hitl-routing.d.ts +17 -3
  9. package/dist/core/stable-id.d.ts +9 -0
  10. package/dist/core/stable-id.js +16 -0
  11. package/dist/core/step-ports.d.ts +25 -1
  12. package/dist/core/step-ports.js +6 -47
  13. package/dist/core/typed-raw-node.js +0 -11
  14. package/dist/decompile.js +7 -235
  15. package/dist/flow-node-registry.js +3 -1
  16. package/dist/flow-node-type.d.ts +45 -1
  17. package/dist/flow-sdk.d.ts +2 -0
  18. package/dist/flow-sdk.js +2 -0
  19. package/dist/flow-step-exits.js +1 -1
  20. package/dist/nodes/conversation/flow/check.d.ts +17 -0
  21. package/dist/nodes/conversation/flow/check.js +54 -0
  22. package/dist/nodes/conversation/flow/factories.d.ts +58 -0
  23. package/dist/nodes/conversation/flow/factories.js +63 -0
  24. package/dist/nodes/conversation/flow/family.d.ts +3 -0
  25. package/dist/nodes/conversation/flow/family.js +8 -0
  26. package/dist/nodes/conversation/flow/index.d.ts +3 -0
  27. package/dist/nodes/conversation/flow/index.js +3 -0
  28. package/dist/nodes/conversation/flow/node-type.d.ts +14 -0
  29. package/dist/nodes/conversation/flow/node-type.js +117 -0
  30. package/dist/nodes/conversation/flow/spec.d.ts +60 -0
  31. package/dist/nodes/conversation/flow/spec.js +1 -0
  32. package/dist/nodes/hitl/flow/check.d.ts +38 -0
  33. package/dist/nodes/hitl/flow/check.js +357 -0
  34. package/dist/nodes/hitl/flow/factories.d.ts +58 -0
  35. package/dist/nodes/hitl/flow/factories.js +59 -0
  36. package/dist/nodes/hitl/flow/family.d.ts +3 -0
  37. package/dist/nodes/hitl/flow/family.js +8 -0
  38. package/dist/nodes/hitl/flow/index.d.ts +3 -0
  39. package/dist/nodes/hitl/flow/index.js +3 -0
  40. package/dist/nodes/hitl/flow/node-type.d.ts +7 -0
  41. package/dist/nodes/hitl/flow/node-type.js +601 -0
  42. package/dist/nodes/hitl/flow/spec.d.ts +265 -0
  43. package/dist/nodes/hitl/flow/spec.js +1 -0
  44. package/dist/serialize.d.ts +2 -4
  45. package/dist/serialize.js +35 -392
  46. package/package.json +2 -2
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';
@@ -90,24 +89,17 @@ const NODE_TYPE = {
90
89
  scheduledTrigger: 'core.trigger.scheduled',
91
90
  formTrigger: 'core.trigger.form',
92
91
  conversationTrigger: 'core.trigger.conversation',
93
- sendMessage: 'uipath.conversational.send-message',
94
- waitForMessage: 'uipath.conversational.wait-for-message',
95
- conversationContext: 'uipath.conversational.get-conversation-context',
96
92
  voiceTrigger: 'core.trigger.voice',
97
93
  voiceAgent: 'uipath.agent.voice',
98
94
  conversationalAgent: 'uipath.agent.conversational',
99
95
  createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
100
96
  endCall: 'uipath.conversational.voice.end-call',
101
- hitl: 'uipath.human-in-the-loop',
102
97
  // The two other spellings of the SAME human task, both resolvable on this
103
98
  // tenant. `hitl()` emits the base type unless `variant` names one of these;
104
99
  // all three share the model (`bpmn:UserTask` / `Actions.HITL`), the single
105
100
  // `completed` handle and the `output`/`status` output pair, so only the type
106
101
  // string and the form's OWNER differ. (A third, `…​.document-validation`, is
107
102
  // `AvailableOnTenant: false` — no definition resolves, so it is not here.)
108
- hitlQuickForm: 'uipath.human-in-the-loop.quick-form',
109
- hitlActionApp: 'uipath.human-in-the-loop.coded-action-app',
110
- hitlDocValidation: 'uipath.human-in-the-loop.document-validation',
111
103
  // The PLATFORM's placeholder — "a real node goes here later". Not the engine's
112
104
  // fixture-returning `kind: 'mock'` test construct, which shares only the name
113
105
  // (#125). The definition declares no `inputDefinition` and one output whose
@@ -3001,245 +2993,6 @@ function scheduleInputs(inputs, nodeId) {
3001
2993
  ...(isPreset ? {} : { timerValue: every }),
3002
2994
  };
3003
2995
  }
3004
- /** JSON-Schema type per form-field type, as flow-v1's form serializer maps them (quick-form escalations and human tasks). */
3005
- const FORM_FIELD_JSON_TYPE = {
3006
- text: { type: 'string' },
3007
- number: { type: 'number' },
3008
- boolean: { type: 'boolean' },
3009
- 'date-time': { type: 'string', format: 'date-time' },
3010
- };
3011
- /**
3012
- * A human task's inputs, and the outputs its form implies.
3013
- *
3014
- * Three things the author does not state and the platform needs:
3015
- *
3016
- * 1. **`type: 'quick'`** — the node type carries both an inline quick form and an
3017
- * App-backed task, selected by this field. flow-v1's serializer defaults an
3018
- * absent value to `quick`, but a reader cannot rely on that default, so the
3019
- * serializer writes it (the same "the encoding is the serializer's job" call as
3020
- * the scheduled trigger's `timerPreset`/`timerValue` split). **Only on the base
3021
- * node type**: on either suffixed variant the node TYPE is the choice, so no
3022
- * `inputs.type` is written — 165 of the 175 deployed `.quick-form` instances
3023
- * omit it, neither variant's definition declares it, and flow-v1's serializer
3024
- * DERIVES its `hitlType` from the node type rather than reading the field
3025
- * (`hitl-serialization.ts:140-146`).
3026
- * 2. **`recipient`** — who gets the task. Emitted from the definition's own
3027
- * `inputDefaults`: Email + Action Center, assigned to a group. That is what
3028
- * 203 of 233 deployed, debug-green v1 human tasks carry, unchanged.
3029
- * 3. **`schema.schemaId`** — the stable id the backend caches the form under
3030
- * (229 of those 233 flows have one). Derived from the flow and node ids so the
3031
- * emitted `.flow` is byte-stable across builds.
3032
- *
3033
- * And the outputs, which the DEFINITION cannot declare: a quick form's result keys
3034
- * are its own answer fields, so `uipath.human-in-the-loop@1.0` says only
3035
- * `output: { type: 'object' }` and the instance carries the shape — `properties`
3036
- * per answer field plus `Action` enumerated over the outcome names, and `status`
3037
- * (the platform's `=result.Action` projection) enumerated the same way. This is
3038
- * exactly what deployed v1 flows carry, and it is what lets `gen-tests` synthesize
3039
- * a replay fixture a downstream read can actually use.
3040
- */
3041
- function hitlInputs(inputs, nodeId, flowId, label, rename) {
3042
- const nodeType = hitlNodeType(inputs);
3043
- const isApp = nodeType === NODE_TYPE.hitlActionApp;
3044
- const fields = (inputs?.fields ?? []).map((f) => {
3045
- const base = {
3046
- id: f.id,
3047
- label: f.label ?? f.id,
3048
- type: f.type,
3049
- direction: f.direction,
3050
- };
3051
- if (f.direction === 'input') {
3052
- // The platform reads a field's `binding` as a variable PATH (`vars.…`),
3053
- // not as an expression — so the Expr's `$vars.x` form is rewritten here
3054
- // rather than asked of the author.
3055
- base.binding = bindingPath(f.value, rename);
3056
- }
3057
- else if (f.direction === 'inOut') {
3058
- // Pre-filled AND editable: `binding` is the canonical pre-fill (the data
3059
- // contract flow-v1's serializer reads), and the WRITE target is the field
3060
- // id itself — deliberately decoupled from the binding, so no `variable`.
3061
- base.binding = bindingPath(f.value, rename);
3062
- if (f.required)
3063
- base.required = true;
3064
- }
3065
- else {
3066
- // The answer's landing variable. The platform falls back to the field id
3067
- // when this is absent; naming it keeps the emitted flow explicit and
3068
- // matches what the designer writes.
3069
- base.variable = `vars.${f.id}`;
3070
- if (f.required)
3071
- base.required = true;
3072
- }
3073
- return base;
3074
- });
3075
- const outcomes = (inputs?.outcomes ?? []).map((o, i) => {
3076
- const name = typeof o === 'string' ? o : o?.name;
3077
- const action = typeof o === 'string' ? 'Continue' : (o?.action ?? 'Continue');
3078
- return { id: outcomeIdOf(o, i), name, type: 'string', isPrimary: i === 0, action };
3079
- });
3080
- const properties = {};
3081
- for (const f of inputs?.fields ?? []) {
3082
- // inOut fields land in the answers too — their reviewed value flows back.
3083
- if (!isAnswerField(f))
3084
- continue;
3085
- properties[f.id] = { ...ownEntry(FORM_FIELD_JSON_TYPE, String(f.type)) };
3086
- }
3087
- const names = outcomes.map((o) => o.name).filter((n) => typeof n === 'string');
3088
- if (names.length > 0) {
3089
- properties.Action = { type: 'string', enum: names, default: names[0] };
3090
- }
3091
- // The app reference and its input map, for the `action-app` variant only. The
3092
- // keys are flow-v1's own app-picker names (`name`/`key`/`folderPath`, plus the
3093
- // `appSystemName` that stays null until an app's first deployment); the
3094
- // bindings map the app's OWN parameter names to `=`-prefixed expressions,
3095
- // which is the form v1's serializer turns into `HitlTaskArguments`.
3096
- const app = inputs?.app;
3097
- const appInputBindings = {};
3098
- for (const [name, value] of Object.entries(app?.inputs ?? {})) {
3099
- appInputBindings[name] = appBindingExpr(value, rename);
3100
- }
3101
- // The author's recipient when given; the definition's own default otherwise
3102
- // (unchanged — that default is what 203 of 233 deployed v1 tasks carry).
3103
- const defaultRecipient = jsonClone(defByType.get(nodeType)?.inputDefaults?.recipient ?? {});
3104
- const r = inputs?.recipient;
3105
- const recipient = r
3106
- ? {
3107
- channels: r.channels ?? (defaultRecipient.channels ?? []),
3108
- // Preserve the default's shape: quick-form's default carries an empty
3109
- // `connections` map, the others none — keep whichever, unless the
3110
- // author supplies their own.
3111
- ...(r.connections !== undefined
3112
- ? { connections: r.connections }
3113
- : 'connections' in defaultRecipient ? { connections: defaultRecipient.connections } : {}),
3114
- assignee: {
3115
- type: r.assignee.type,
3116
- ...(r.assignee.value !== undefined ? { value: r.assignee.value } : {}),
3117
- ...(r.assignee.displayName !== undefined ? { displayName: r.assignee.displayName } : {}),
3118
- },
3119
- }
3120
- : defaultRecipient;
3121
- // Document Validation carries the station's own inputs instead of a form; the
3122
- // expressions ride as `=js:` strings, EV-wrapped by the profile migration like
3123
- // any other input (the family is not in UNWRAPPED_INPUTS).
3124
- // The station's expression fields ride as PLAIN `$vars.…` strings — the
3125
- // property panel's own placeholder shape, and what the platform validator
3126
- // reads (it string-checks them directly; an ExpressionValue envelope fails
3127
- // with "extractionResult?.trim is not a function").
3128
- const docPath = (v) => {
3129
- const e = toExpr(v);
3130
- return e.literal ? String(e.value) : renameRefs(e.js, rename);
3131
- };
3132
- const doc = inputs?.document;
3133
- const docvalInputs = nodeType === NODE_TYPE.hitlDocValidation && doc
3134
- ? {
3135
- validationRenderType: doc.render ?? 'standard',
3136
- extractionResult: docPath(doc.extractionResult),
3137
- ...(doc.storageBucket !== undefined ? { storageBucket: doc.storageBucket } : {}),
3138
- ...(doc.documentId !== undefined ? { documentId: doc.documentId } : {}),
3139
- ...(doc.taxonomy !== undefined ? { taxonomy: docPath(doc.taxonomy) } : {}),
3140
- }
3141
- : {};
3142
- // 1.2's error output, verbatim from the bundled definition — the instance
3143
- // outputs replace the definition's (see the caller), so the slot is re-added
3144
- // here when the author asked for it.
3145
- const errorOutput = inputs?.exposeError
3146
- ? DEFS['uipath.human-in-the-loop@1.2']?.outputDefinition?.error
3147
- : undefined;
3148
- return {
3149
- inputs: {
3150
- // The base node type alone needs the discriminator — see the doc block.
3151
- ...(nodeType === NODE_TYPE.hitl ? { type: 'quick' } : {}),
3152
- title: inputs?.title ?? label,
3153
- priority: inputs?.priority ?? 'Low',
3154
- ...(inputs?.labels !== undefined ? { labels: inputs.labels } : {}),
3155
- recipient,
3156
- ...docvalInputs,
3157
- ...((isApp || (nodeType === NODE_TYPE.hitlDocValidation && doc?.render === 'custom')) && app
3158
- ? {
3159
- app: {
3160
- displayName: app.name,
3161
- name: app.name,
3162
- key: app.key,
3163
- folderPath: app.folderPath,
3164
- ...(app.appSystemName !== undefined ? { appSystemName: app.appSystemName } : {}),
3165
- },
3166
- ...(Object.keys(appInputBindings).length > 0 ? { appInputBindings } : {}),
3167
- }
3168
- : {}),
3169
- // The app variant still carries a `schema` — with NO fields (the app owns
3170
- // the form) but WITH the outcomes, because the flow's own routing and its
3171
- // `Action`/`status` outputs are derived from them. Both of flow-v1's
3172
- // app-task node references write exactly this shape.
3173
- schema: { schemaId: schemaId(flowId, nodeId), fields, outcomes },
3174
- },
3175
- outputs: {
3176
- output: {
3177
- type: 'object',
3178
- description: 'Task result data',
3179
- source: '=result',
3180
- var: 'output',
3181
- ...(Object.keys(properties).length > 0 ? { properties } : {}),
3182
- },
3183
- status: {
3184
- type: 'string',
3185
- description: 'Task completion status',
3186
- source: '=result.Action',
3187
- var: 'status',
3188
- ...(names.length > 0 ? { enum: names, default: names[0] } : {}),
3189
- },
3190
- ...(errorOutput !== undefined ? { error: jsonClone(errorOutput) } : {}),
3191
- },
3192
- };
3193
- }
3194
- /**
3195
- * Which of the three human-task node types this step emits.
3196
- *
3197
- * `undefined` is deliberately the base type and not an alias for `.quick-form`:
3198
- * the default emission must not change, so a flow authored before `variant`
3199
- * existed compiles to the same bytes it always did.
3200
- */
3201
- function hitlNodeType(inputs) {
3202
- if (inputs?.variant === 'quick-form')
3203
- return NODE_TYPE.hitlQuickForm;
3204
- if (inputs?.variant === 'action-app')
3205
- return NODE_TYPE.hitlActionApp;
3206
- if (inputs?.variant === 'document-validation')
3207
- return NODE_TYPE.hitlDocValidation;
3208
- return NODE_TYPE.hitl;
3209
- }
3210
- /**
3211
- * One `appInputBindings` value: the expression sent to a deployed app's input.
3212
- *
3213
- * flow-v1 writes these `=`-prefixed and WITHOUT `js:` (`"=vars.<node>.output.<f>"`
3214
- * — `hitl-node-apptask.md`), and its serializer normalizes each to `=js:<body>`
3215
- * when it builds `HitlTaskArguments`. So the flow-level form is the bare
3216
- * `=`-prefixed path, which is what our own `$vars.` expressions become once the
3217
- * prefix is dropped. A plain string passes through as written.
3218
- */
3219
- function appBindingExpr(value, rename) {
3220
- if (!(value instanceof Expr))
3221
- return String(value ?? '');
3222
- const js = renameRefs(value.js, rename).trim();
3223
- const path = js.startsWith('$vars.') ? js.slice('$vars.'.length) : js;
3224
- return path.startsWith('=') ? path : `=${path.startsWith('vars.') ? path : `vars.${path}`}`;
3225
- }
3226
- /**
3227
- * A shown field's `binding`: the platform resolves it as a path against the
3228
- * workflow's variables (`vars.<flowInput>`, `vars.<node>.output.<field>`), so the
3229
- * `$vars.` prefix our expressions carry is dropped. A computed expression cannot
3230
- * be a path — `check` rejects one (HITL_FIELD_VALUE_NOT_PATH) before we get here.
3231
- */
3232
- function bindingPath(value, rename) {
3233
- if (!(value instanceof Expr))
3234
- return String(value ?? '');
3235
- const js = renameRefs(value.js, rename).trim();
3236
- const path = js.startsWith('$vars.') ? js.slice('$vars.'.length) : js;
3237
- // `vars.`-prefixed, which is what 526 of 528 bound fields in deployed v1 flows
3238
- // carry. flow-v1's serializer does prefix a bare identifier itself
3239
- // (`hitl-serialization.ts`), but only for `HitlTaskArguments` — the
3240
- // `__hitlInputBindings` the designer reads back stores the binding verbatim.
3241
- return path.startsWith('vars.') ? path : `vars.${path}`;
3242
- }
3243
2996
  /**
3244
2997
  * `Approve` → `approve`; a name that slugifies to nothing falls back to its index.
3245
2998
  * The same rule that names the `outcome-<id>` ports (`outcomeSlug`), so an id
@@ -3248,22 +3001,6 @@ function bindingPath(value, rename) {
3248
3001
  function outcomeId(name, index) {
3249
3002
  return outcomeSlug(name, index);
3250
3003
  }
3251
- /**
3252
- * A deterministic UUID for the form schema, derived from the flow and node ids.
3253
- *
3254
- * The platform uses `schemaId` to identify and cache a form, so it has to be
3255
- * stable across builds of the same flow — a random one would make every rebuild a
3256
- * different form and every emitted `.flow` a different file. Shaped as a v5 UUID
3257
- * (SHA-1 based, version + variant bits set) because that is what the field holds.
3258
- */
3259
- function schemaId(flowId, nodeId) {
3260
- const h = createHash('sha1').update(`uipath.human-in-the-loop:${flowId}:${nodeId}`).digest();
3261
- const b = Buffer.from(h.subarray(0, 16));
3262
- b[6] = (b[6] & 0x0f) | 0x50;
3263
- b[8] = (b[8] & 0x3f) | 0x80;
3264
- const hex = b.toString('hex');
3265
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
3266
- }
3267
3004
  /**
3268
3005
  * `serialize`, plus the extra files the flow needs on disk beside it.
3269
3006
  *
@@ -3632,12 +3369,8 @@ function describeDeclared(bindings) {
3632
3369
  */
3633
3370
  export function bundledActionNodeType(spec) {
3634
3371
  switch (spec?.kind) {
3635
- case 'hitl': return hitlNodeType(spec.inputs);
3636
3372
  case 'inlineAgent': return INLINE_AGENT_TYPE;
3637
3373
  case 'subflow':
3638
- case 'sendMessage':
3639
- case 'waitForMessage':
3640
- case 'conversationContext':
3641
3374
  case 'createOutgoingCall':
3642
3375
  case 'endCall':
3643
3376
  case 'voiceAgent':
@@ -3692,10 +3425,8 @@ export function perCallDefinition(spec) {
3692
3425
  * The definition version serialize emits for a typed action step that is not
3693
3426
  * yet on the node-type registry: the author's `{ version }` when given,
3694
3427
  * otherwise the version the step's inputs select, otherwise `undefined` (the
3695
- * pinned default). Two such families pick a version themselves:
3696
- *
3697
- * - `hitl`: `exposeError` selects `@1.2`, per-outcome routing selects `@1.1`;
3698
- * - `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.
3699
3430
  *
3700
3431
  * The emit paths read their version here, and `check` resolves the definition
3701
3432
  * it asks about with this same version (#1041), so the two cannot pick
@@ -3710,23 +3441,10 @@ export function actionVersion(spec, explicit) {
3710
3441
  // Keyed by the node type `bundledActionNodeType` already picks, so the
3711
3442
  // version rule adds no second dispatch on the step's kind.
3712
3443
  const type = bundledActionNodeType(spec);
3713
- if (type === NODE_TYPE.hitl)
3714
- return hitlNeededVersion(spec?.inputs);
3715
3444
  if (type === INLINE_AGENT_TYPE)
3716
3445
  return INLINE_AGENT_DEFAULT_VERSION;
3717
3446
  return undefined;
3718
3447
  }
3719
- /**
3720
- * The base human task version a task's inputs need when nothing is pinned:
3721
- * `exposeError` needs 1.2 (it adds the `error` output), and a task that
3722
- * routes per outcome needs 1.1. Only the base task reaches here: a variant
3723
- * (quick-form, app task) has its own node type and one definition.
3724
- */
3725
- function hitlNeededVersion(inputs) {
3726
- if (inputs?.exposeError)
3727
- return '1.2';
3728
- return hitlExitModel(inputs, undefined).shape === 'per-outcome' ? '1.1' : undefined;
3729
- }
3730
3448
  /**
3731
3449
  * The bundled definition of `nodeType` that `makeNode` resolves: the author's
3732
3450
  * exact `{ version }` when given, otherwise the pinned default (the first
@@ -4978,14 +4696,6 @@ export function serialize(built, opts = {}) {
4978
4696
  function successPort(type) {
4979
4697
  if (type === NODE_TYPE.loop)
4980
4698
  return 'success';
4981
- // Every human-task definition declares one static source handle, `completed`.
4982
- // It is the real exit only where the product draws it: action-app,
4983
- // document-validation, and a task with no outcomes. A base or quick-form task
4984
- // with outcomes leaves on `outcome-<id>` instead (`hitlExitModel`), and never
4985
- // reaches this function for its tail.
4986
- if (type === NODE_TYPE.hitl || type === NODE_TYPE.hitlQuickForm || type === NODE_TYPE.hitlActionApp
4987
- || type === NODE_TYPE.hitlDocValidation)
4988
- return 'completed';
4989
4699
  // The inline agent's forward handle is `success`, not `output` — its definition
4990
4700
  // declares `success` (with `isDefaultForType`) + `error` on the right, and 29 of
4991
4701
  // 29 deployed instances wire the outgoing edge to `success`. The PUBLISHED agent
@@ -5055,7 +4765,12 @@ export function serialize(built, opts = {}) {
5055
4765
  delete node.model;
5056
4766
  node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
5057
4767
  if (contribution.outputs) {
5058
- 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) };
5059
4774
  }
5060
4775
  // A bundled registered type returns no definition; its bindings still close
5061
4776
  // against the bundled one (#942), counting what it bound through the context
@@ -5063,8 +4778,17 @@ export function serialize(built, opts = {}) {
5063
4778
  const def = o.def ?? bundledDefinition(o.typeName, o.requestedVersion);
5064
4779
  hoistContributionBindings(def, [...(o.bound ?? []), ...(contribution.bindings ?? [])], o.where);
5065
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);
5066
4787
  for (const t of o.tails)
5067
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
+ }
5068
4792
  const port = contribution.port ?? contributionPort(def);
5069
4793
  if (port === undefined) {
5070
4794
  // Only templated exits: no main path leaves this node. Remember it so a
@@ -5091,7 +4815,10 @@ export function serialize(built, opts = {}) {
5091
4815
  scope.rename.errorEnvelopes.set(step.name, impl.errorEnvelope);
5092
4816
  const ctx = {
5093
4817
  step: step.name,
4818
+ nodeId: id,
4819
+ flowId: built.id,
5094
4820
  version: impl.version,
4821
+ pinnedVersion: step.options?.version,
5095
4822
  render: (value) => renderValue(value, scope.rename),
5096
4823
  renameRefs: (js) => renameRefs(js, scope.rename),
5097
4824
  stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
@@ -5137,12 +4864,21 @@ export function serialize(built, opts = {}) {
5137
4864
  }
5138
4865
  declaredErrorPorts.set(id, { port, typeName: impl.typeName, version: impl.version });
5139
4866
  }
5140
- if (contribution.port !== undefined && !declaresForwardPort(def ?? bundledDefinition(impl.typeName, impl.version), contribution.port)) {
5141
- throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} leaves by port "${contribution.port}", `
5142
- + '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.');
5143
4878
  }
5144
4879
  return placeStepNode({
5145
4880
  where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: impl.version, def, contribution, bound, tails, scope, parentId,
4881
+ trackOutcomes: true,
5146
4882
  });
5147
4883
  }
5148
4884
  function emitAction(step, tails, scope, parentId) {
@@ -6020,48 +5756,6 @@ export function serialize(built, opts = {}) {
6020
5756
  addEdge(scope, t, id);
6021
5757
  return [{ node: id, port: successPort(NODE_TYPE.voiceAgent) }];
6022
5758
  }
6023
- if (spec.kind === 'sendMessage' || spec.kind === 'waitForMessage' || spec.kind === 'conversationContext') {
6024
- // One emit path for the three CONVERSATIONAL steps: they differ in their
6025
- // definition and in which fields they carry. Each field is written under
6026
- // the node's own name, and the definition's `inputDefaults` supply the
6027
- // fixed ones the platform's schema requires (`role: 'assistant'` and
6028
- // `mimeType: 'text/markdown'` on send, `from: 'User'` on wait) — the
6029
- // author never writes a value the enum only allows one of.
6030
- const type = spec.kind === 'sendMessage' ? NODE_TYPE.sendMessage
6031
- : spec.kind === 'waitForMessage' ? NODE_TYPE.waitForMessage
6032
- : NODE_TYPE.conversationContext;
6033
- const id = stepUid(scope, step.name);
6034
- const node = makeNode(type, id, step.name, { parentId, requestedVersion: step.options?.version });
6035
- // The service model stays on the DEFINITION only, as it does for the AI
6036
- // patterns and the published families: these are `bpmn:ServiceTask` /
6037
- // `bpmn:IntermediateCatchEvent` nodes whose serviceType the platform
6038
- // resolves from `definitions[]`.
6039
- delete node.model;
6040
- const defaults = jsonClone(defByType.get(type)?.inputDefaults ?? {});
6041
- node.inputs = spec.kind === 'sendMessage'
6042
- ? {
6043
- ...defaults,
6044
- conversationId: renderValue(toExpr(spec.inputs.conversationId), scope.rename),
6045
- exchangeId: renderValue(toExpr(spec.inputs.exchangeId), scope.rename),
6046
- content: renderValue(toExpr(spec.inputs.content), scope.rename),
6047
- ...(spec.inputs.endExchange !== undefined ? { endExchange: spec.inputs.endExchange } : {}),
6048
- }
6049
- : spec.kind === 'waitForMessage'
6050
- ? {
6051
- ...defaults,
6052
- conversationId: renderValue(toExpr(spec.inputs.conversationId), scope.rename),
6053
- ...(spec.inputs.numExchanges !== undefined ? { numExchanges: spec.inputs.numExchanges } : {}),
6054
- }
6055
- : {
6056
- ...defaults,
6057
- conversationId: renderValue(toExpr(spec.inputs.conversationId), scope.rename),
6058
- ...(spec.inputs.exchangeLimit !== undefined ? { exchangeLimit: spec.inputs.exchangeLimit } : {}),
6059
- };
6060
- scope.nodes.push(node);
6061
- for (const t of tails)
6062
- addEdge(scope, t, id);
6063
- return [{ node: id, port: successPort(type) }];
6064
- }
6065
5759
  if (spec.kind === 'ixpExtract') {
6066
5760
  const s = spec.inputs;
6067
5761
  const def = perCallDefinition(spec); // ixpExtractDef: throws on an unparseable project
@@ -6130,57 +5824,6 @@ export function serialize(built, opts = {}) {
6130
5824
  addEdge(scope, t, id);
6131
5825
  return [{ node: id, port: successPort(rc.nodeType) }];
6132
5826
  }
6133
- if (spec.kind === 'hitl') {
6134
- const id = stepUid(scope, step.name);
6135
- const hitlType = hitlNodeType(spec.inputs);
6136
- // How the task leaves its node — `hitlExitModel` (core/hitl-routing.ts)
6137
- // has the product rule and what `.step()` means after each shape.
6138
- //
6139
- // Only the BASE node has definition versions to select: routing per
6140
- // outcome picks 1.1 and `exposeError` picks 1.2 (which adds the `error`
6141
- // output). quick-form draws the same per-outcome handles on the one
6142
- // definition it has, so it routes per outcome without moving. An explicit
6143
- // pin wins over the inferred 1.1 — `flow validate` accepts `outcome-<id>`
6144
- // edges on base@1.0 — but not over `exposeError`, whose output only 1.2
6145
- // declares. A zero- or one-outcome task stays on the pinned default.
6146
- const pinned = step.options?.version;
6147
- const exitModel = hitlExitModel(spec.inputs, pinned);
6148
- if (hitlType !== NODE_TYPE.hitl && spec.inputs?.exposeError === true) {
6149
- throw new Error(`"${step.name}": exposeError needs the base human task's 1.2 definition, which adds the `
6150
- + `error output — the '${spec.inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
6151
- }
6152
- if (spec.inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(hitlType)) {
6153
- throw new Error(`"${step.name}": outcomePorts: true, but the '${spec.inputs?.variant}' variant reads its outcomes `
6154
- + `from its app and leaves on its single 'completed' exit. Route on out('${step.name}', 'Action') downstream.`);
6155
- }
6156
- if (spec.inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
6157
- throw new Error(`"${step.name}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
6158
- + `Drop the pin or match it.`);
6159
- }
6160
- // The same version `check` resolves the definition at (#1041).
6161
- const node = makeNode(hitlType, id, step.name, { parentId, requestedVersion: actionVersion(spec, pinned) });
6162
- const { inputs, outputs } = hitlInputs(spec.inputs, id, built.id, step.name, scope.rename);
6163
- node.inputs = inputs;
6164
- // The definition's `outputDefinition` gives the two output SLOTS; the form
6165
- // gives their shape (see hitlInputs), so the instance's version wins.
6166
- node.outputs = outputs;
6167
- scope.nodes.push(node);
6168
- if (OUTCOME_HANDLE_NODE_TYPES.has(hitlType))
6169
- hitlNodes.set(id, node);
6170
- for (const t of tails)
6171
- addEdge(scope, t, id);
6172
- if (exitModel.shape === 'per-outcome') {
6173
- // The PRIMARY (first) outcome continues the main path; the author routes
6174
- // the rest with .stepToList('outcome-<id>', …) or as .stepSwitch arms.
6175
- return [{ node: id, port: exitModel.exits[0]?.port ?? `outcome-${outcomeSlug(undefined, 0)}` }];
6176
- }
6177
- if (exitModel.shape === 'every-outcome') {
6178
- // Fan-out: every outcome continues to the next step, each on its own
6179
- // handle. End outcomes are marked, and `emit` ends them.
6180
- return exitModel.exits.map((e) => ({ node: id, port: e.port, ...(e.endsProcess ? { endsRun: true } : {}) }));
6181
- }
6182
- return [{ node: id, port: successPort(hitlType) }];
6183
- }
6184
5827
  // A custom FlowAction subclass carries no `kind`; it compiles through its
6185
5828
  // own contribution (design §4). Anything else without a recognized kind is
6186
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.23.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": "4e81a94f4009b646e93353cd0043286f725a4507"
95
+ "gitref": "119258f247ce3ff7e3b3b3faf21ab2e2b13eca5a"
96
96
  }