@uipath/maestro-builder-sdk 6.31.7 → 6.31.9

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.
@@ -1,63 +1,301 @@
1
1
  import { nearestName } from './edit-distance.js';
2
- const PRODUCT_SPELLINGS = {
3
- text: 'text',
4
- string: 'text',
5
- String: 'text',
6
- 'System.String': 'text',
7
- 'System.Char': 'text',
8
- Char: 'text',
9
- number: 'number',
2
+ import { ownEntry } from './own-entry.js';
3
+ /**
4
+ * Each canonical type's JSON Schema, as `FIELD_TYPE_TO_JSON_SCHEMA` gives it.
5
+ * `Record<CanonicalFormFieldType, …>` makes tsc refuse a missing or extra one.
6
+ * `float` and `double` are both `number`: JSON Schema has no precision; `file`
7
+ * is `format: 'binary'` (the OpenAPI convention for an upload).
8
+ */
9
+ const CANONICAL_JSON_SCHEMA = {
10
+ string: { type: 'string' },
11
+ number: { type: 'number' },
12
+ integer: { type: 'integer' },
13
+ float: { type: 'number' },
14
+ double: { type: 'number' },
15
+ boolean: { type: 'boolean' },
16
+ date: { type: 'string', format: 'date' },
17
+ datetime: { type: 'string', format: 'date-time' },
18
+ file: { type: 'string', format: 'binary' },
19
+ object: { type: 'object' },
20
+ array: { type: 'array' },
21
+ };
22
+ /** The product's 11 canonical quick-form field types, in its own order. */
23
+ export const CANONICAL_FORM_FIELD_TYPES = Object.keys(CANONICAL_JSON_SCHEMA);
24
+ /**
25
+ * The SDK's own legacy spellings, accepted in source: `text` (what the SDK,
26
+ * flow-v1's serializer and `uip maestro flow hitl add` have always written) and
27
+ * `date-time`. The product resolves both.
28
+ */
29
+ const SDK_ALIASES = {
30
+ text: 'string',
31
+ 'date-time': 'datetime',
32
+ };
33
+ /** The SDK's legacy spellings (`text`, `date-time`), for diagnostics that list them. */
34
+ export const LEGACY_FORM_FIELD_TYPES = Object.keys(SDK_ALIASES);
35
+ /**
36
+ * The product's choice-control spellings (#1235): the data type each holds
37
+ * (`resolveFieldType`) and the control the form renders it with
38
+ * (`controlForDeclaredType`, which sets the field's `controlType` when the row
39
+ * has none). The output schema is the data type's; the choices ride on the
40
+ * field's `options`, never in the schema.
41
+ *
42
+ * Kept as written on the wire — NOT resolved to the data type, which would
43
+ * drop the control: a `singleSelect` written as `string` renders as a text box.
44
+ */
45
+ const CONTROL_TYPES = {
46
+ singleSelect: { data: 'string', control: 'dropdown' },
47
+ multiSelect: { data: 'array', control: 'multiSelect' },
48
+ enum: { data: 'string', control: 'dropdown' },
49
+ };
50
+ /** The choice-control spellings, for `check`'s messages and the contract test. */
51
+ export const CONTROL_FORM_FIELD_TYPES = Object.keys(CONTROL_TYPES);
52
+ /**
53
+ * Every form-field type the SDK accepts in source: the 11 canonical ones, the
54
+ * SDK's legacy aliases, and the three choice-control spellings.
55
+ */
56
+ export const FORM_FIELD_TYPES = [
57
+ ...CANONICAL_FORM_FIELD_TYPES, ...LEGACY_FORM_FIELD_TYPES, ...CONTROL_FORM_FIELD_TYPES,
58
+ ];
59
+ /**
60
+ * The tail of a "Valid: …" diagnostic: the canonical types, then the legacy
61
+ * spellings and the choice controls in parentheses.
62
+ *
63
+ * @example
64
+ * validFormFieldTypesText() // 'string, …, array (or the older text, date-time; or a choice control: singleSelect, …)'
65
+ */
66
+ export function validFormFieldTypesText() {
67
+ return `${CANONICAL_FORM_FIELD_TYPES.join(', ')} (or the older ${LEGACY_FORM_FIELD_TYPES.join(', ')}; `
68
+ + `or a choice control: ${CONTROL_FORM_FIELD_TYPES.join(', ')})`;
69
+ }
70
+ /**
71
+ * The data type and control a choice-control spelling names, or `undefined`
72
+ * for any other type.
73
+ *
74
+ * @example
75
+ * controlFormFieldType('singleSelect') // { data: 'string', control: 'dropdown' }
76
+ * controlFormFieldType('multiSelect') // { data: 'array', control: 'multiSelect' }
77
+ * controlFormFieldType('string') // undefined
78
+ */
79
+ export function controlFormFieldType(type) {
80
+ const entry = typeof type === 'string' ? ownEntry(CONTROL_TYPES, type) : undefined;
81
+ return entry === undefined ? undefined : { ...entry };
82
+ }
83
+ /**
84
+ * The product's other data-type spellings, each with the canonical type it
85
+ * resolves to (`resolveFieldType` in `@uipath/hitl-schema-types` 1.2.0). The
86
+ * .NET names come from forms built by RPA / agent tooling. The scope-walker
87
+ * shapes (`union`, `intersection`, `ref`, `any`) reach `field.type` when a
88
+ * canvas author binds a Flow variable; they carry no narrower shape, so they
89
+ * resolve to `string`. `T[]` and `a | b` are matched by pattern in
90
+ * `canonicalFormFieldType`.
91
+ *
92
+ * Deliberately left out: the choice-control spellings (`singleSelect`,
93
+ * `multiSelect`, `enum` — `CONTROL_TYPES` above, kept as written because the
94
+ * product infers a `controlType` from them).
95
+ */
96
+ const PRODUCT_ALIASES = {
97
+ ...SDK_ALIASES,
98
+ String: 'string',
99
+ 'System.String': 'string',
100
+ 'System.Char': 'string',
101
+ Char: 'string',
10
102
  Number: 'number',
11
- boolean: 'boolean',
103
+ Int32: 'integer',
104
+ Int64: 'integer',
105
+ 'System.Int32': 'integer',
106
+ 'System.Int64': 'integer',
107
+ 'System.Byte': 'integer',
108
+ 'System.Int16': 'integer',
109
+ Byte: 'integer',
110
+ Int16: 'integer',
111
+ UInt16: 'integer',
112
+ UInt32: 'integer',
113
+ UInt64: 'integer',
114
+ Float: 'float',
115
+ Single: 'float',
116
+ 'System.Single': 'float',
117
+ Double: 'double',
118
+ Decimal: 'double',
119
+ 'System.Double': 'double',
120
+ 'System.Decimal': 'double',
12
121
  Boolean: 'boolean',
13
122
  'System.Boolean': 'boolean',
14
- 'date-time': 'date-time',
15
- datetime: 'date-time',
16
- dateTime: 'date-time',
17
- DateTime: 'date-time',
18
- 'System.DateTime': 'date-time',
123
+ Date: 'date',
124
+ 'System.DateOnly': 'date',
125
+ DateOnly: 'date',
126
+ dateTime: 'datetime',
127
+ DateTime: 'datetime',
128
+ 'System.DateTime': 'datetime',
129
+ File: 'file',
130
+ Object: 'object',
131
+ DataTable: 'object',
132
+ QueueItem: 'object',
133
+ Array: 'array',
134
+ GenericValue: 'array',
135
+ list: 'array',
136
+ List: 'array',
137
+ union: 'string',
138
+ intersection: 'string',
139
+ ref: 'string',
140
+ any: 'string',
19
141
  };
142
+ /** The product aliases, for the contract test. */
143
+ export const FORM_FIELD_TYPE_ALIASES = PRODUCT_ALIASES;
144
+ const isCanonical = (t) => typeof t === 'string' && Object.hasOwn(CANONICAL_JSON_SCHEMA, t);
145
+ /**
146
+ * The canonical type a wire or source spelling names, or `undefined` when it
147
+ * names none the SDK models. A choice-control spelling is its own wire
148
+ * spelling, so it is `undefined` here too — its data type is
149
+ * `controlFormFieldType(t).data`; resolving it here would let a caller write
150
+ * `string` for a dropdown.
151
+ *
152
+ * @example
153
+ * canonicalFormFieldType('integer') // 'integer'
154
+ * canonicalFormFieldType('text') // 'string'
155
+ * canonicalFormFieldType('System.Int32') // 'integer'
156
+ * canonicalFormFieldType('string[]') // 'array'
157
+ * canonicalFormFieldType('string | null') // 'string'
158
+ * canonicalFormFieldType('singleSelect') // undefined
159
+ */
160
+ export function canonicalFormFieldType(type) {
161
+ if (isCanonical(type))
162
+ return type;
163
+ if (typeof type !== 'string')
164
+ return undefined;
165
+ // The package's own order: the two scope-walker patterns before the names.
166
+ if (type.endsWith('[]'))
167
+ return 'array';
168
+ if (type.includes(' | '))
169
+ return 'string';
170
+ return Object.hasOwn(PRODUCT_ALIASES, type) ? PRODUCT_ALIASES[type] : undefined;
171
+ }
20
172
  /**
21
- * The SDK's spelling of a wire form-field type, or `undefined` when the SDK
22
- * does not model that type.
173
+ * The spelling decompile writes into source for a wire field type: a canonical
174
+ * type or an SDK alias stays as written, so a canvas form (`"integer"`,
175
+ * `"date"`) and a classic-CLI form (`"text"`) both compile back to the bytes
176
+ * they came from; a product alias the SDK does not accept in source
177
+ * (`"System.Int32"`, `"string[]"`) becomes its canonical type; anything else
178
+ * stays verbatim for `check` to report.
23
179
  *
24
180
  * @example
25
- * sdkFormFieldType('string') // 'text'
26
- * sdkFormFieldType('datetime') // 'date-time'
27
- * sdkFormFieldType('integer') // undefined
181
+ * sourceFormFieldType('date') // 'date'
182
+ * sourceFormFieldType('text') // 'text'
183
+ * sourceFormFieldType('System.String') // 'string'
184
+ * sourceFormFieldType('string[]') // 'array'
185
+ * sourceFormFieldType('singleSelect') // 'singleSelect'
28
186
  */
29
- export function sdkFormFieldType(wire) {
30
- return typeof wire === 'string' && Object.hasOwn(PRODUCT_SPELLINGS, wire) ? PRODUCT_SPELLINGS[wire] : undefined;
187
+ export function sourceFormFieldType(wire) {
188
+ if (typeof wire === 'string' && FORM_FIELD_TYPES.includes(wire))
189
+ return wire;
190
+ return canonicalFormFieldType(wire) ?? String(wire);
31
191
  }
32
192
  /**
33
- * Why a form-field type is refused: a product spelling of a type the SDK
34
- * models (`"string"` is the platform's own name for text), or a type
35
- * the SDK does not model at all. `unknownWhy` is the caller's reason for the
36
- * second case; the default is a human task's.
193
+ * Why a form-field type is refused: a product alias the SDK does not accept in
194
+ * source (`"System.Int32"` is the platform's .NET name for `integer`), or a type
195
+ * the product does not recognize at all. `unknownWhy` is the caller's reason for
196
+ * the second case; the default is a human task's.
37
197
  */
38
- export function formFieldTypeWhy(type, unknownWhy = `which the platform does not recognize. Older runtimes read it as text, newer ones reject `
39
- + `the form`) {
40
- const alias = sdkFormFieldType(type);
41
- return alias !== undefined
42
- ? `which is the platform's spelling of the SDK's '${alias}' — the SDK writes '${alias}'`
198
+ export function formFieldTypeWhy(type, unknownWhy = `which is not a quick-form field type: the platform rejects the form`) {
199
+ const canonical = canonicalFormFieldType(type);
200
+ return canonical !== undefined
201
+ ? `which is the platform's alias of '${canonical}' — the SDK writes '${canonical}'`
43
202
  : unknownWhy;
44
203
  }
45
- /** Every FormFieldType, once; `Record<FormFieldType, …>` makes tsc refuse a missing or extra one. */
46
- const FORM_FIELD_TYPE_SET = { text: true, number: true, boolean: true, 'date-time': true };
47
- /** The form-field types the SDK models. */
48
- export const FORM_FIELD_TYPES = Object.keys(FORM_FIELD_TYPE_SET);
49
- /** The `suggestion` for an unknown form-field type: the nearest valid one, else `type: 'text'`. */
204
+ /** The `suggestion` for an unknown form-field type: its canonical type, the nearest valid one, else `type: 'string'`. */
50
205
  export function formFieldTypeHint(type) {
51
- const alias = sdkFormFieldType(type);
52
- if (alias !== undefined)
53
- return `type: '${alias}'`;
206
+ const canonical = canonicalFormFieldType(type);
207
+ if (canonical !== undefined)
208
+ return `type: '${canonical}'`;
54
209
  const nearest = nearestName(String(type), FORM_FIELD_TYPES);
55
- return nearest !== undefined ? `type: '${nearest}'` : `type: 'text'`;
210
+ return nearest !== undefined ? `type: '${nearest}'` : `type: 'string'`;
211
+ }
212
+ /**
213
+ * The JSON Schema of a field's value, for the output (and escalation input)
214
+ * schemas the serializers write; `undefined` for a type the SDK does not model.
215
+ *
216
+ * @example
217
+ * formFieldJsonSchema('date') // { type: 'string', format: 'date' }
218
+ * formFieldJsonSchema('text') // { type: 'string' }
219
+ * formFieldJsonSchema('multiSelect') // { type: 'array' }
220
+ */
221
+ export function formFieldJsonSchema(type) {
222
+ const canonical = canonicalFormFieldType(type) ?? controlFormFieldType(type)?.data;
223
+ const schema = canonical === undefined ? undefined : ownEntry(CANONICAL_JSON_SCHEMA, canonical);
224
+ return schema === undefined ? undefined : { ...schema };
225
+ }
226
+ /**
227
+ * A picker field's choices as the form row carries them (`options`, each
228
+ * `{ id, value, label }` — the product's `optionShape`), copied so the wire
229
+ * never aliases the author's array. `undefined` when the field has none.
230
+ * Both serializers (human task, quick-form escalation) write through this.
231
+ */
232
+ export function formFieldOptionsWire(options) {
233
+ return options === undefined ? undefined : options.map((o) => ({ id: o.id, value: o.value, label: o.label }));
234
+ }
235
+ /** Whether a value is one well-formed choice: string `id`, `value` and `label`, all three the product requires. */
236
+ function isFormFieldOption(o) {
237
+ if (o === null || typeof o !== 'object')
238
+ return false;
239
+ const r = o;
240
+ return typeof r.id === 'string' && typeof r.value === 'string' && typeof r.label === 'string';
241
+ }
242
+ /**
243
+ * A form row's `options` read back for source: the well-formed choices
244
+ * (string `id`, `value`, `label` — all three the product requires), or
245
+ * `undefined` when the row has no `options` array. A malformed entry is left
246
+ * out, not coerced: `{ id: 'o1', value: 1 }` is not rewritten to
247
+ * `value: '1', label: ''`. Decompile (human task and quick-form escalation)
248
+ * reads through this, so a canvas dropdown's choices survive an edit.
249
+ *
250
+ * @example
251
+ * liftFormFieldOptions([{ id: 'o1', value: 'a', label: 'A' }, { id: 'o2', value: 2 }])
252
+ * // [{ id: 'o1', value: 'a', label: 'A' }]
253
+ */
254
+ export function liftFormFieldOptions(raw) {
255
+ if (!Array.isArray(raw))
256
+ return undefined;
257
+ return raw.filter(isFormFieldOption).map((o) => ({ id: o.id, value: o.value, label: o.label }));
258
+ }
259
+ /**
260
+ * What is wrong with a form field's `options`, or `undefined` when nothing is.
261
+ * `check` reports each (human task and quick-form escalation):
262
+ *
263
+ * - `'malformed'`: `options` is there but is not an array of `{ id, value,
264
+ * label }` strings. Serialize would throw or write rows the platform refuses.
265
+ * - `'missing'`: a choice-control type (`singleSelect`, `multiSelect`, `enum`)
266
+ * with no choices. The form publishes an empty picker.
267
+ * - `'ignored'`: choices on a type that is not a choice control. The form
268
+ * renders a plain input and never shows them.
269
+ *
270
+ * @example
271
+ * formFieldOptionsProblem('singleSelect', undefined) // 'missing'
272
+ * formFieldOptionsProblem('singleSelect', {}) // 'malformed'
273
+ * formFieldOptionsProblem('string', [{ id: 'o1', value: 'a', label: 'A' }]) // 'ignored'
274
+ * formFieldOptionsProblem('enum', [{ id: 'o1', value: 'a', label: 'A' }]) // undefined
275
+ */
276
+ export function formFieldOptionsProblem(type, options) {
277
+ if (options !== undefined && (!Array.isArray(options) || !options.every(isFormFieldOption)))
278
+ return 'malformed';
279
+ const isControl = controlFormFieldType(type) !== undefined;
280
+ if (isControl && (options === undefined || options.length === 0))
281
+ return 'missing';
282
+ if (!isControl && options !== undefined && options.length > 0)
283
+ return 'ignored';
284
+ return undefined;
285
+ }
286
+ /**
287
+ * The reason text for a `formFieldOptionsProblem`, worded for a diagnostic
288
+ * that starts "… the field "<id>" …".
289
+ */
290
+ export function formFieldOptionsWhy(problem, type) {
291
+ switch (problem) {
292
+ case 'malformed':
293
+ return `has \`options\` that are not a list of { id, value, label } strings. Serialize cannot write them, `
294
+ + `and the platform requires all three on every choice`;
295
+ case 'missing':
296
+ return `is a ${JSON.stringify(type)} picker with no \`options\`, so the form publishes an empty picker`;
297
+ case 'ignored':
298
+ return `has \`options\` but its type ${JSON.stringify(type)} is not a choice control, so the form renders a `
299
+ + `plain input and never shows them. Use 'singleSelect' or 'enum' (a dropdown) or 'multiSelect'`;
300
+ }
56
301
  }
57
- /** JSON-Schema type per form-field type, as flow-v1's form serializer maps them (quick-form escalations and human tasks). */
58
- export const FORM_FIELD_JSON_TYPE = {
59
- text: { type: 'string' },
60
- number: { type: 'number' },
61
- boolean: { type: 'boolean' },
62
- 'date-time': { type: 'string', format: 'date-time' },
63
- };
package/dist/decompile.js CHANGED
@@ -50,7 +50,7 @@ import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
50
50
  import { contributionPort, forwardFanOut } from './core/definition-handles.js';
51
51
  import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
52
52
  import { eventFilterFromTreeLeaf } from './core/event-filters.js';
53
- import { sdkFormFieldType } from './core/form-field-type.js';
53
+ import { liftFormFieldOptions, sourceFormFieldType } from './core/form-field-type.js';
54
54
  /**
55
55
  * The trigger id of the flow currently being decompiled, so a reference through a
56
56
  * RENAMED trigger is still recognized as `input('x')` rather than falling back to
@@ -1304,8 +1304,14 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1304
1304
  ['description', rPlain(e, 'description') !== undefined ? tsLiteral(rPlain(e, 'description')) : undefined],
1305
1305
  ['fields', rows.length ? `[${rows.map((f) => opts([
1306
1306
  ['id', tsText(f.id)],
1307
- ['type', tsLiteral(sdkFormFieldType(f.type ?? 'text') ?? String(f.type))],
1307
+ ['type', tsLiteral(sourceFormFieldType(f.type ?? 'string'))],
1308
1308
  ['direction', tsLiteral(String(f.direction ?? 'output'))],
1309
+ ['options', (() => {
1310
+ const options = liftFormFieldOptions(f.options);
1311
+ return options === undefined ? undefined : `[${options.map((o) => opts([
1312
+ ['id', tsLiteral(o.id)], ['value', tsLiteral(o.value)], ['label', tsLiteral(o.label)],
1313
+ ])).join(', ')}]`;
1314
+ })()],
1309
1315
  ])).join(', ')}]` : undefined],
1310
1316
  ['app', app === undefined ? undefined : opts([
1311
1317
  ['key', tsText(app.key)],
@@ -1,6 +1,6 @@
1
1
  import { Expr } from '../../../core/expr.js';
2
2
  import { nearestName } from '../../../core/edit-distance.js';
3
- import { FORM_FIELD_TYPES, formFieldTypeHint, formFieldTypeWhy } from '../../../core/form-field-type.js';
3
+ import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText } from '../../../core/form-field-type.js';
4
4
  import { isAnswerField } from '../../../core/hitl-answers.js';
5
5
  import { hitlDerivesOutcomeHandles, outcomeIdOf } from '../../../core/hitl-routing.js';
6
6
  export const HITL_DIAGNOSTICS = {
@@ -23,6 +23,8 @@ export const HITL_DIAGNOSTICS = {
23
23
  HITL_DUP_FIELD: { severity: 'error', summary: 'a form field id declared twice' },
24
24
  HITL_FIELD_RESERVED_ID: { severity: 'error', summary: 'a form field id the result already uses' },
25
25
  HITL_FIELD_TYPE_UNKNOWN: { severity: 'error', summary: 'a form field type the platform does not recognize' },
26
+ HITL_FIELD_OPTIONS: { severity: 'error', summary: 'a picker with no choices, or choices that are not { id, value, label } strings' },
27
+ HITL_FIELD_OPTIONS_IGNORED: { severity: 'warning', summary: 'choices on a field type that does not render them' },
26
28
  HITL_FIELD_NO_VALUE: { severity: 'error', summary: 'a shown field with no value' },
27
29
  HITL_FIELD_VALUE_NOT_REF: { severity: 'error', summary: 'a shown field filled from a literal' },
28
30
  HITL_FIELD_VALUE_NOT_PATH: { severity: 'error', summary: 'a shown field filled from a computed expression' },
@@ -213,7 +215,11 @@ export function checkHitl(inputs, ctx) {
213
215
  }
214
216
  if (!FORM_FIELD_TYPES.includes(String(f?.type))) {
215
217
  at('HITL_FIELD_TYPE_UNKNOWN', `Human task "${step}" gives the form field "${id}" the type ${JSON.stringify(f?.type)}, `
216
- + `${formFieldTypeWhy(f?.type)}. Valid: ${FORM_FIELD_TYPES.join(', ')}.`, formFieldTypeHint(f?.type));
218
+ + `${formFieldTypeWhy(f?.type)}. Valid: ${validFormFieldTypesText()}.`, formFieldTypeHint(f?.type));
219
+ }
220
+ const optionsProblem = formFieldOptionsProblem(f?.type, f.options);
221
+ if (optionsProblem !== undefined) {
222
+ at(optionsProblem === 'ignored' ? 'HITL_FIELD_OPTIONS_IGNORED' : 'HITL_FIELD_OPTIONS', `Human task "${step}"'s form field "${id}" ${formFieldOptionsWhy(optionsProblem, f?.type)}.`, `options: [{ id: 'opt-1', value: 'high', label: 'High' }, { id: 'opt-2', value: 'low', label: 'Low' }]`);
217
223
  }
218
224
  if (isAnswerField(f))
219
225
  answers++; // an `output`, or an `inOut` whose reviewed value comes back
@@ -13,11 +13,10 @@
13
13
  * every edge out of an outcome-handle node to.
14
14
  */
15
15
  import { Expr, toExpr } from '../../../core/expr.js';
16
- import { FORM_FIELD_JSON_TYPE, sdkFormFieldType } from '../../../core/form-field-type.js';
16
+ import { formFieldJsonSchema, formFieldOptionsWire, liftFormFieldOptions, sourceFormFieldType } 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';
20
- import { ownEntry } from '../../../core/own-entry.js';
21
20
  import { schemaId } from '../../../core/stable-id.js';
22
21
  import { wireExprBody } from '../../../core/wire-expr.js';
23
22
  import { checkHitl, checkHitlRead } from './check.js';
@@ -87,6 +86,11 @@ function hitlInputs(inputs, nodeType, ctx) {
87
86
  if (f.required)
88
87
  base.required = true;
89
88
  }
89
+ // A picker's choices (a `singleSelect` dropdown, a `multiSelect`), as the
90
+ // canvas stores them on the row (#1235).
91
+ const options = formFieldOptionsWire(f.options);
92
+ if (options !== undefined)
93
+ base.options = options;
90
94
  return base;
91
95
  });
92
96
  const outcomes = (inputs?.outcomes ?? []).map((o, i) => {
@@ -99,7 +103,7 @@ function hitlInputs(inputs, nodeType, ctx) {
99
103
  // inOut fields land in the answers too — their reviewed value flows back.
100
104
  if (!isAnswerField(f))
101
105
  continue;
102
- properties[f.id] = { ...ownEntry(FORM_FIELD_JSON_TYPE, String(f.type)) };
106
+ properties[f.id] = { ...formFieldJsonSchema(f.type) };
103
107
  }
104
108
  const names = outcomes.map((o) => o.name).filter((n) => typeof n === 'string');
105
109
  if (names.length > 0) {
@@ -327,15 +331,18 @@ function liftHitl(node, typeName, ctx) {
327
331
  const id = String(f.id ?? '');
328
332
  const direction = String(f.direction ?? 'output');
329
333
  const value = direction === 'input' || direction === 'inOut' ? bindingExpr(f.binding) : undefined;
334
+ const options = liftFormFieldOptions(f.options);
330
335
  return {
331
336
  id,
332
337
  // serialize defaults a row's label to its id.
333
338
  ...(typeof f.label === 'string' && f.label !== id ? { label: f.label } : {}),
334
- // The product's own spelling (`"string"`, `"datetime"`, …) → the SDK's (#1143).
335
- type: sdkFormFieldType(f.type ?? 'text') ?? String(f.type),
339
+ // A type the SDK accepts stays as written, so the form compiles back to
340
+ // the same bytes; a .NET alias becomes its canonical type (#1143, #1176).
341
+ type: sourceFormFieldType(f.type ?? 'string'),
336
342
  direction,
337
343
  ...(value !== undefined ? { value } : {}),
338
344
  ...(f.required === true ? { required: true } : {}),
345
+ ...(options !== undefined ? { options } : {}),
339
346
  };
340
347
  });
341
348
  const outcomeRows = (Array.isArray(schema.outcomes) ? schema.outcomes : []);
@@ -498,6 +505,11 @@ function printHitl({ inputs }, ctx) {
498
505
  ['direction', ctx.string(f.direction)],
499
506
  ['value', f.value !== undefined ? ctx.value(f.value) : undefined],
500
507
  ['required', f.required ? 'true' : undefined],
508
+ ['options', f.options !== undefined ? `[${f.options.map((o) => obj([
509
+ ['id', ctx.string(o.id)],
510
+ ['value', ctx.string(o.value)],
511
+ ['label', ctx.string(o.label)],
512
+ ], ctx)).join(', ')}]` : undefined],
501
513
  ], ctx));
502
514
  const outcomes = h.outcomes.map((o) => (typeof o === 'string' ? ctx.string(o) : obj([
503
515
  ['name', ctx.string(o.name)],
package/dist/serialize.js CHANGED
@@ -20,7 +20,7 @@ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
20
20
  import { stepExits } from './flow-step-exits.js';
21
21
  import { resolveVersionCarried } from './core/nodes/versioned.js';
22
22
  import { indexCarriedDefinitions } from './core/carried-definitions.js';
23
- import { FORM_FIELD_JSON_TYPE } from './core/form-field-type.js';
23
+ import { formFieldJsonSchema, formFieldOptionsWire } from './core/form-field-type.js';
24
24
  import { ConnectorResolveError } from './core/connector-resolve-error.js';
25
25
  import { connectionConnectors } from './bindings-v2.js';
26
26
  import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
@@ -1362,7 +1362,7 @@ function escalationResourceJson(esc, spec) {
1362
1362
  const fields = esc.fields ?? [];
1363
1363
  const props = (dirs) => Object.fromEntries(fields
1364
1364
  .filter((f) => dirs.includes(f.direction))
1365
- .map((f) => [f.id, ownEntry(FORM_FIELD_JSON_TYPE, String(f.type)) ?? { type: 'string' }]));
1365
+ .map((f) => [f.id, formFieldJsonSchema(f.type) ?? { type: 'string' }]));
1366
1366
  channel = {
1367
1367
  ...common,
1368
1368
  type: 'actionCenterQuickForm',
@@ -5533,12 +5533,17 @@ export function serialize(built, opts = {}) {
5533
5533
  const qnode = makeNode(qdef.nodeType, qid, esc.name, { parentId, def: qdef });
5534
5534
  qnode.display = { ...qnode.display, shape: 'circle' };
5535
5535
  delete qnode.model;
5536
- const rows = (esc.fields ?? []).map((f) => ({
5537
- id: f.id,
5538
- label: f.label ?? f.id,
5539
- type: f.type,
5540
- direction: f.direction,
5541
- }));
5536
+ const rows = (esc.fields ?? []).map((f) => {
5537
+ const options = formFieldOptionsWire(f.options);
5538
+ return {
5539
+ id: f.id,
5540
+ label: f.label ?? f.id,
5541
+ type: f.type,
5542
+ direction: f.direction,
5543
+ // A picker's choices, as the human task's rows carry them (#1235).
5544
+ ...(options !== undefined ? { options } : {}),
5545
+ };
5546
+ });
5542
5547
  const qsource = fnv1aX4Uuid(`${built.id}/${step.name}/escalation/${esc.name}`);
5543
5548
  qnode.inputs = {
5544
5549
  source: qsource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.31.7",
3
+ "version": "6.31.9",
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": "99f1861467ac06fba1c816f365bf027e1e2c8e05"
96
+ "gitref": "e812e24ab6455bcc1ffeb6f806e9cd0d09cbe389"
97
97
  }