@uipath/maestro-builder-sdk 6.33.2 → 6.35.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/api-index.md +103 -97
- package/dist/api-members.md +403 -383
- package/dist/case/case-sdk.d.ts +52 -1
- package/dist/case/case-sdk.js +37 -2
- package/dist/case/check.js +30 -0
- package/dist/case/compile-cli.js +6 -1
- package/dist/case/decompile.js +50 -3
- package/dist/case/format-profile.d.ts +1 -1
- package/dist/case/format-profile.js +7 -3
- package/dist/case/serialize.js +93 -9
- package/dist/check.js +6 -2
- package/dist/core/actions.d.ts +103 -18
- package/dist/core/form-field-type.d.ts +77 -29
- package/dist/core/form-field-type.js +264 -46
- package/dist/decompile.js +41 -12
- package/dist/flow-node-type.d.ts +6 -0
- package/dist/nodes/hitl/flow/check.js +8 -4
- package/dist/nodes/hitl/flow/node-type.js +20 -20
- package/dist/schema-version.js +6 -1
- package/dist/serialize.js +7 -4
- package/package.json +2 -2
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* agent quick-form escalations), decompile, and the serializers' output
|
|
27
27
|
* schemas.
|
|
28
28
|
*/
|
|
29
|
-
import type { CanonicalFormFieldType, ControlFormFieldType,
|
|
29
|
+
import type { CanonicalFormFieldType, ControlFormFieldType, FormFieldControlType, FormFieldSettings } from './actions.js';
|
|
30
30
|
/** The product's 11 canonical quick-form field types, in its own order. */
|
|
31
31
|
export declare const CANONICAL_FORM_FIELD_TYPES: CanonicalFormFieldType[];
|
|
32
32
|
/** The SDK's legacy spellings (`text`, `date-time`), for diagnostics that list them. */
|
|
@@ -117,48 +117,50 @@ export declare function formFieldJsonSchema(type: unknown): {
|
|
|
117
117
|
format?: string;
|
|
118
118
|
} | undefined;
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
121
|
-
* `
|
|
122
|
-
*
|
|
123
|
-
*
|
|
120
|
+
* Every key of a quick-form field row, in the platform's own order
|
|
121
|
+
* (`@uipath/hitl-schema-types` 1.2.0 `fieldSchemaFor`). The contract test
|
|
122
|
+
* compares this list with the real package, so a key added upstream fails
|
|
123
|
+
* there until the SDK decides how to carry it.
|
|
124
124
|
*/
|
|
125
|
-
export declare
|
|
125
|
+
export declare const FORM_FIELD_ROW_KEYS: readonly ["id", "type", "label", "direction", "required", "variable", "binding", "value", "rules", "requiredMessage", "colSpan", "controlType", "options", "defaultValue", "description"];
|
|
126
|
+
/** The platform's controls (`controlType`), in its own order. */
|
|
127
|
+
export declare const FORM_FIELD_CONTROL_TYPES: readonly FormFieldControlType[];
|
|
126
128
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* out, not coerced: `{ id: 'o1', value: 1 }` is not rewritten to
|
|
131
|
-
* `value: '1', label: ''`. Decompile (human task and quick-form escalation)
|
|
132
|
-
* reads through this, so a canvas dropdown's choices survive an edit.
|
|
129
|
+
* Each settings key a field sets to a value the platform's schema refuses, as
|
|
130
|
+
* `key: value (what it must be)`. Empty when every setting is valid. `check`
|
|
131
|
+
* reports these: a form with one fails the platform's validation.
|
|
133
132
|
*
|
|
134
133
|
* @example
|
|
135
|
-
*
|
|
136
|
-
* // [{ id: 'o1', value: 'a', label: 'A' }]
|
|
134
|
+
* formFieldSettingProblems({ colSpan: 13 }) // ['colSpan: 13 (must be an integer from 1 to 12 …)']
|
|
137
135
|
*/
|
|
138
|
-
export declare function
|
|
136
|
+
export declare function formFieldSettingProblems(f: unknown): string[];
|
|
137
|
+
/** The `FormFieldSettings` keys, in the platform's row order. */
|
|
138
|
+
export declare const FORM_FIELD_SETTINGS_KEYS: (keyof FormFieldSettings)[];
|
|
139
139
|
/**
|
|
140
|
-
* What is wrong with a form field's
|
|
141
|
-
* `check` reports each (human task and quick-form escalation)
|
|
140
|
+
* What is wrong with a form field's choices, or `undefined` when nothing is.
|
|
141
|
+
* `check` reports each (human task and quick-form escalation). A field is a
|
|
142
|
+
* picker when its type is a choice-control spelling (`singleSelect`,
|
|
143
|
+
* `multiSelect`, `enum`) or its `controlType` is a picker control (`dropdown`,
|
|
144
|
+
* `radioGroup`, `multiSelect`, `checkboxGroup`).
|
|
142
145
|
*
|
|
143
|
-
* - `'
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
146
|
+
* - `'missing'`: a picker with no choices. The form publishes an empty picker.
|
|
147
|
+
* - `'ignored'`: choices on a field that is not a picker. The form renders a
|
|
148
|
+
* plain input and never shows them.
|
|
149
|
+
*
|
|
150
|
+
* `options` that do not have the platform's shape are reported by
|
|
151
|
+
* `formFieldSettingProblems` instead, so they get no second diagnostic here.
|
|
149
152
|
*
|
|
150
153
|
* @example
|
|
151
|
-
* formFieldOptionsProblem('singleSelect'
|
|
152
|
-
* formFieldOptionsProblem('
|
|
153
|
-
* formFieldOptionsProblem('string', [{ id: 'o1', value: 'a', label: 'A' }]) //
|
|
154
|
-
* formFieldOptionsProblem('enum', [{ id: 'o1', value: 'a', label: 'A' }]) // undefined
|
|
154
|
+
* formFieldOptionsProblem({ type: 'singleSelect' }) // 'missing'
|
|
155
|
+
* formFieldOptionsProblem({ type: 'string', options: [{ id: 'o1', value: 'a', label: 'A' }] }) // 'ignored'
|
|
156
|
+
* formFieldOptionsProblem({ type: 'string', controlType: 'dropdown', options: [{ id: 'o1', value: 'a', label: 'A' }] }) // undefined
|
|
155
157
|
*/
|
|
156
|
-
export declare function formFieldOptionsProblem(
|
|
158
|
+
export declare function formFieldOptionsProblem(f: unknown): 'missing' | 'ignored' | undefined;
|
|
157
159
|
/**
|
|
158
160
|
* The reason text for a `formFieldOptionsProblem`, worded for a diagnostic
|
|
159
161
|
* that starts "… the field "<id>" …".
|
|
160
162
|
*/
|
|
161
|
-
export declare function formFieldOptionsWhy(problem: '
|
|
163
|
+
export declare function formFieldOptionsWhy(problem: 'missing' | 'ignored', type: unknown): string;
|
|
162
164
|
/**
|
|
163
165
|
* The spelling a form field's type is written in on the wire: the product's
|
|
164
166
|
* canonical one for the SDK's legacy spellings (`text` is written `string`,
|
|
@@ -173,3 +175,49 @@ export declare function formFieldOptionsWhy(problem: 'malformed' | 'missing' | '
|
|
|
173
175
|
* wireFormFieldType('number') // 'number'
|
|
174
176
|
*/
|
|
175
177
|
export declare function wireFormFieldType(type: unknown): unknown;
|
|
178
|
+
/**
|
|
179
|
+
* A field's settings as the form row carries them: each key the author set,
|
|
180
|
+
* in the platform's row order, copied so the wire never aliases the author's
|
|
181
|
+
* objects. Both serializers (human task, quick-form escalation) append this
|
|
182
|
+
* after the row's own keys.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* formFieldSettingsWire({ colSpan: 6, controlType: 'dropdown' }) // { colSpan: 6, controlType: 'dropdown' }
|
|
186
|
+
*/
|
|
187
|
+
export declare function formFieldSettingsWire(f: FormFieldSettings): Record<string, unknown>;
|
|
188
|
+
/**
|
|
189
|
+
* A form row's settings read back for source: each settings key whose value
|
|
190
|
+
* has the platform's shape, copied, without any key the platform's schema does
|
|
191
|
+
* not define. A key with any other value is left out here; both cases are
|
|
192
|
+
* reported by `formFieldRowLoss`.
|
|
193
|
+
*/
|
|
194
|
+
export declare function liftFormFieldSettings(row: Record<string, unknown>): FormFieldSettings;
|
|
195
|
+
/**
|
|
196
|
+
* A field's settings as source options, in the platform's row order, for the
|
|
197
|
+
* decompilers' printers. `value` prints one data value (`'high'`, `6`,
|
|
198
|
+
* `[{ id: 'o1', … }]`).
|
|
199
|
+
*/
|
|
200
|
+
export declare function formFieldSettingsSource(settings: FormFieldSettings, value: (v: unknown) => string): Array<[string, string | undefined]>;
|
|
201
|
+
/**
|
|
202
|
+
* Where a form row lives, which decides which of its binding keys the SDK
|
|
203
|
+
* derives: a human task writes `binding` for a shown or pre-filled field and
|
|
204
|
+
* `variable: 'vars.<id>'` for an asked one; a quick-form escalation writes
|
|
205
|
+
* neither (the agent fills the form).
|
|
206
|
+
*/
|
|
207
|
+
export type FormRowSite = 'human task' | 'escalation';
|
|
208
|
+
/**
|
|
209
|
+
* The keys of a form row the SDK cannot carry into source, each with why.
|
|
210
|
+
* Empty when decompile → compile writes the row back as it was. The lifts
|
|
211
|
+
* report a non-empty list as a warning (an error under `--strict`), so no key
|
|
212
|
+
* is dropped silently.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* formFieldRowLoss({ id: 'a', label: 'A', type: 'string', direction: 'output', colSpan: 13 }, 'human task')
|
|
216
|
+
* // ['colSpan: 13 (not the platform\'s shape for it)']
|
|
217
|
+
*/
|
|
218
|
+
export declare function formFieldRowLoss(row: Record<string, unknown>, site: FormRowSite): string[];
|
|
219
|
+
/**
|
|
220
|
+
* The warning (or `--strict` error) for a row `formFieldRowLoss` reported.
|
|
221
|
+
* `where` names the form, e.g. `human task "review"`.
|
|
222
|
+
*/
|
|
223
|
+
export declare function formFieldRowLossMessage(where: string, fieldId: string, loss: readonly string[]): string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { jsonClone } from './json-clone.js';
|
|
2
|
+
import { isRecord } from './node-envelope.js';
|
|
1
3
|
import { nearestName } from './edit-distance.js';
|
|
2
4
|
import { ownEntry } from './own-entry.js';
|
|
3
5
|
/**
|
|
@@ -228,60 +230,129 @@ export function formFieldJsonSchema(type) {
|
|
|
228
230
|
/**
|
|
229
231
|
* A picker field's choices as the form row carries them (`options`, each
|
|
230
232
|
* `{ id, value, label }` — the product's `optionShape`), copied so the wire
|
|
231
|
-
* never aliases the author's array.
|
|
232
|
-
* Both serializers (human task, quick-form escalation) write through this.
|
|
233
|
+
* never aliases the author's array.
|
|
233
234
|
*/
|
|
234
|
-
|
|
235
|
-
return options
|
|
235
|
+
function formFieldOptionsWire(options) {
|
|
236
|
+
return options.map((o) => ({ id: o.id, value: o.value, label: o.label }));
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
// ─── The whole field row (#1250) ─────────────────────────────────────────────
|
|
239
|
+
/**
|
|
240
|
+
* Every key of a quick-form field row, in the platform's own order
|
|
241
|
+
* (`@uipath/hitl-schema-types` 1.2.0 `fieldSchemaFor`). The contract test
|
|
242
|
+
* compares this list with the real package, so a key added upstream fails
|
|
243
|
+
* there until the SDK decides how to carry it.
|
|
244
|
+
*/
|
|
245
|
+
export const FORM_FIELD_ROW_KEYS = [
|
|
246
|
+
'id', 'type', 'label', 'direction', 'required', 'variable', 'binding', 'value',
|
|
247
|
+
'rules', 'requiredMessage', 'colSpan', 'controlType', 'options', 'defaultValue', 'description',
|
|
248
|
+
];
|
|
249
|
+
/** The platform's controls (`controlType`), in its own order. */
|
|
250
|
+
export const FORM_FIELD_CONTROL_TYPES = [
|
|
251
|
+
'text', 'textarea', 'numberInput', 'datePicker', 'dateAndTimePicker', 'checkbox',
|
|
252
|
+
'dropdown', 'radioGroup', 'multiSelect', 'checkboxGroup', 'fileUpload',
|
|
253
|
+
];
|
|
254
|
+
const isString = (v) => typeof v === 'string';
|
|
255
|
+
/*
|
|
256
|
+
* The shapes below check only the keys the platform defines. Its `optionShape`,
|
|
257
|
+
* `fieldRuleSchema` and `fieldConditionSchema` are plain `z.object`s, which
|
|
258
|
+
* STRIP an unknown key rather than refuse it, so an extra key is not a reason
|
|
259
|
+
* for `check` to fail. Decompile drops one (`stripSetting`) and reports it
|
|
260
|
+
* (`formFieldRowLoss`), because the SDK's source types have no place for it.
|
|
261
|
+
*/
|
|
262
|
+
const OPTION_KEYS = ['id', 'value', 'label'];
|
|
263
|
+
const RULE_KEYS = ['id', 'conditions', 'operator', 'effects'];
|
|
264
|
+
const CONDITION_KEYS = ['when', 'is', 'isNot', 'in', 'notIn', 'matches', 'custom'];
|
|
265
|
+
function isOptions(v) {
|
|
266
|
+
return Array.isArray(v) && v.every((o) => isRecord(o) && isString(o.id) && isString(o.value) && isString(o.label));
|
|
267
|
+
}
|
|
268
|
+
/** `fieldConditionSchema`: `when` plus optional `is`/`isNot`/`in`/`notIn`/`matches`/`custom`. */
|
|
269
|
+
function isCondition(v) {
|
|
270
|
+
return isRecord(v)
|
|
271
|
+
&& isString(v.when)
|
|
272
|
+
&& (v.in === undefined || Array.isArray(v.in)) && (v.notIn === undefined || Array.isArray(v.notIn))
|
|
273
|
+
&& (v.matches === undefined || isString(v.matches)) && (v.custom === undefined || isString(v.custom));
|
|
243
274
|
}
|
|
275
|
+
/** `fieldRuleSchema`: `{ id, conditions, operator?, effects }`. */
|
|
276
|
+
function isRules(v) {
|
|
277
|
+
return Array.isArray(v) && v.every((r) => isRecord(r)
|
|
278
|
+
&& isString(r.id) && Array.isArray(r.conditions) && r.conditions.every(isCondition)
|
|
279
|
+
&& (r.operator === undefined || r.operator === 'AND' || r.operator === 'OR') && isRecord(r.effects));
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The settings keys (`FormFieldSettings`), in the platform's row order, each
|
|
283
|
+
* with the shape the platform's schema accepts. `Record<keyof …>` makes tsc
|
|
284
|
+
* refuse a settings key without an entry here.
|
|
285
|
+
*/
|
|
286
|
+
const SETTINGS = {
|
|
287
|
+
rules: isRules,
|
|
288
|
+
requiredMessage: isString,
|
|
289
|
+
colSpan: (v) => Number.isInteger(v) && v >= 1 && v <= 12,
|
|
290
|
+
controlType: (v) => FORM_FIELD_CONTROL_TYPES.includes(v),
|
|
291
|
+
options: isOptions,
|
|
292
|
+
defaultValue: (v) => isString(v) || (Array.isArray(v) && v.every(isString)),
|
|
293
|
+
description: isString,
|
|
294
|
+
};
|
|
295
|
+
/** Whether `value` has the platform's shape for the settings key `key`; false for any other key. */
|
|
296
|
+
const validSetting = (key, value) => ownEntry(SETTINGS, key)?.(value) === true;
|
|
297
|
+
/** What each settings key must be, for `check`'s message. */
|
|
298
|
+
const SETTINGS_SHAPE = {
|
|
299
|
+
rules: "a list of { id, conditions: [{ when, … }], operator?: 'AND' | 'OR', effects: { … } }",
|
|
300
|
+
requiredMessage: 'a string',
|
|
301
|
+
colSpan: 'an integer from 1 to 12 (the form\'s 12-column grid)',
|
|
302
|
+
controlType: `one of ${FORM_FIELD_CONTROL_TYPES.join(', ')}`,
|
|
303
|
+
options: 'a list of { id, value, label }, all strings',
|
|
304
|
+
defaultValue: 'a string, or a list of strings for a multi-select',
|
|
305
|
+
description: 'a string',
|
|
306
|
+
};
|
|
244
307
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* out, not coerced: `{ id: 'o1', value: 1 }` is not rewritten to
|
|
249
|
-
* `value: '1', label: ''`. Decompile (human task and quick-form escalation)
|
|
250
|
-
* reads through this, so a canvas dropdown's choices survive an edit.
|
|
308
|
+
* Each settings key a field sets to a value the platform's schema refuses, as
|
|
309
|
+
* `key: value (what it must be)`. Empty when every setting is valid. `check`
|
|
310
|
+
* reports these: a form with one fails the platform's validation.
|
|
251
311
|
*
|
|
252
312
|
* @example
|
|
253
|
-
*
|
|
254
|
-
* // [{ id: 'o1', value: 'a', label: 'A' }]
|
|
313
|
+
* formFieldSettingProblems({ colSpan: 13 }) // ['colSpan: 13 (must be an integer from 1 to 12 …)']
|
|
255
314
|
*/
|
|
256
|
-
export function
|
|
257
|
-
if (!
|
|
258
|
-
return
|
|
259
|
-
return
|
|
315
|
+
export function formFieldSettingProblems(f) {
|
|
316
|
+
if (!isRecord(f))
|
|
317
|
+
return [];
|
|
318
|
+
return FORM_FIELD_SETTINGS_KEYS
|
|
319
|
+
.filter((key) => f[key] !== undefined && !validSetting(key, f[key]))
|
|
320
|
+
.map((key) => `${key}: ${JSON.stringify(f[key])} (must be ${ownEntry(SETTINGS_SHAPE, key)})`);
|
|
260
321
|
}
|
|
322
|
+
/** The `FormFieldSettings` keys, in the platform's row order. */
|
|
323
|
+
export const FORM_FIELD_SETTINGS_KEYS = Object.keys(SETTINGS);
|
|
324
|
+
/** The `controlType`s that render a field's `options` as choices. */
|
|
325
|
+
const PICKER_CONTROL_TYPES = ['dropdown', 'radioGroup', 'multiSelect', 'checkboxGroup'];
|
|
261
326
|
/**
|
|
262
|
-
* What is wrong with a form field's
|
|
263
|
-
* `check` reports each (human task and quick-form escalation)
|
|
327
|
+
* What is wrong with a form field's choices, or `undefined` when nothing is.
|
|
328
|
+
* `check` reports each (human task and quick-form escalation). A field is a
|
|
329
|
+
* picker when its type is a choice-control spelling (`singleSelect`,
|
|
330
|
+
* `multiSelect`, `enum`) or its `controlType` is a picker control (`dropdown`,
|
|
331
|
+
* `radioGroup`, `multiSelect`, `checkboxGroup`).
|
|
332
|
+
*
|
|
333
|
+
* - `'missing'`: a picker with no choices. The form publishes an empty picker.
|
|
334
|
+
* - `'ignored'`: choices on a field that is not a picker. The form renders a
|
|
335
|
+
* plain input and never shows them.
|
|
264
336
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* - `'missing'`: a choice-control type (`singleSelect`, `multiSelect`, `enum`)
|
|
268
|
-
* with no choices. The form publishes an empty picker.
|
|
269
|
-
* - `'ignored'`: choices on a type that is not a choice control. The form
|
|
270
|
-
* renders a plain input and never shows them.
|
|
337
|
+
* `options` that do not have the platform's shape are reported by
|
|
338
|
+
* `formFieldSettingProblems` instead, so they get no second diagnostic here.
|
|
271
339
|
*
|
|
272
340
|
* @example
|
|
273
|
-
* formFieldOptionsProblem('singleSelect'
|
|
274
|
-
* formFieldOptionsProblem('
|
|
275
|
-
* formFieldOptionsProblem('string', [{ id: 'o1', value: 'a', label: 'A' }]) //
|
|
276
|
-
* formFieldOptionsProblem('enum', [{ id: 'o1', value: 'a', label: 'A' }]) // undefined
|
|
341
|
+
* formFieldOptionsProblem({ type: 'singleSelect' }) // 'missing'
|
|
342
|
+
* formFieldOptionsProblem({ type: 'string', options: [{ id: 'o1', value: 'a', label: 'A' }] }) // 'ignored'
|
|
343
|
+
* formFieldOptionsProblem({ type: 'string', controlType: 'dropdown', options: [{ id: 'o1', value: 'a', label: 'A' }] }) // undefined
|
|
277
344
|
*/
|
|
278
|
-
export function formFieldOptionsProblem(
|
|
279
|
-
if (
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
|
|
345
|
+
export function formFieldOptionsProblem(f) {
|
|
346
|
+
if (!isRecord(f))
|
|
347
|
+
return undefined;
|
|
348
|
+
if (f.options !== undefined && !validSetting('options', f.options))
|
|
349
|
+
return undefined;
|
|
350
|
+
const picker = controlFormFieldType(f.type) !== undefined
|
|
351
|
+
|| (typeof f.controlType === 'string' && PICKER_CONTROL_TYPES.includes(f.controlType));
|
|
352
|
+
const count = Array.isArray(f.options) ? f.options.length : 0;
|
|
353
|
+
if (picker && count === 0)
|
|
283
354
|
return 'missing';
|
|
284
|
-
if (!
|
|
355
|
+
if (!picker && count > 0)
|
|
285
356
|
return 'ignored';
|
|
286
357
|
return undefined;
|
|
287
358
|
}
|
|
@@ -291,14 +362,12 @@ export function formFieldOptionsProblem(type, options) {
|
|
|
291
362
|
*/
|
|
292
363
|
export function formFieldOptionsWhy(problem, type) {
|
|
293
364
|
switch (problem) {
|
|
294
|
-
case 'malformed':
|
|
295
|
-
return `has \`options\` that are not a list of { id, value, label } strings. Serialize cannot write them, `
|
|
296
|
-
+ `and the platform requires all three on every choice`;
|
|
297
365
|
case 'missing':
|
|
298
|
-
return `is a
|
|
366
|
+
return `is a picker with no \`options\`, so the form publishes an empty picker`;
|
|
299
367
|
case 'ignored':
|
|
300
|
-
return `has \`options\` but its type ${JSON.stringify(type)} is not a choice control
|
|
301
|
-
+
|
|
368
|
+
return `has \`options\` but its type ${JSON.stringify(type)} is not a choice control and it sets no picker `
|
|
369
|
+
+ `\`controlType\`, so the form renders a plain input and never shows them. Use 'singleSelect' or 'enum' `
|
|
370
|
+
+ `(a dropdown) or 'multiSelect', or set controlType: 'dropdown'`;
|
|
302
371
|
}
|
|
303
372
|
}
|
|
304
373
|
/**
|
|
@@ -319,3 +388,152 @@ export function wireFormFieldType(type) {
|
|
|
319
388
|
? SDK_ALIASES[type]
|
|
320
389
|
: type;
|
|
321
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* A field's settings as the form row carries them: each key the author set,
|
|
393
|
+
* in the platform's row order, copied so the wire never aliases the author's
|
|
394
|
+
* objects. Both serializers (human task, quick-form escalation) append this
|
|
395
|
+
* after the row's own keys.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* formFieldSettingsWire({ colSpan: 6, controlType: 'dropdown' }) // { colSpan: 6, controlType: 'dropdown' }
|
|
399
|
+
*/
|
|
400
|
+
export function formFieldSettingsWire(f) {
|
|
401
|
+
const wire = {};
|
|
402
|
+
for (const key of FORM_FIELD_SETTINGS_KEYS) {
|
|
403
|
+
const v = f[key];
|
|
404
|
+
if (v === undefined)
|
|
405
|
+
continue;
|
|
406
|
+
wire[key] = key === 'options' ? formFieldOptionsWire(v) : jsonClone(v);
|
|
407
|
+
}
|
|
408
|
+
return wire;
|
|
409
|
+
}
|
|
410
|
+
/** `o` with only `keys`, in `o`'s own order. */
|
|
411
|
+
const pick = (o, keys) => Object.fromEntries(Object.entries(o).filter(([k]) => keys.includes(k)));
|
|
412
|
+
/** The keys of `o` outside `keys`, each as `<at>.<key>`. */
|
|
413
|
+
const extraKeys = (o, keys, at) => Object.keys(o).filter((k) => !keys.includes(k)).map((k) => `${at}.${k}`);
|
|
414
|
+
/**
|
|
415
|
+
* A valid `options` or `rules` value without the keys the platform's schema
|
|
416
|
+
* does not define (it strips them too); any other setting as given.
|
|
417
|
+
*/
|
|
418
|
+
function stripSetting(key, v) {
|
|
419
|
+
if (key === 'options')
|
|
420
|
+
return v.map((o) => pick(o, OPTION_KEYS));
|
|
421
|
+
if (key === 'rules') {
|
|
422
|
+
return v.map((r) => ({
|
|
423
|
+
...pick(r, RULE_KEYS),
|
|
424
|
+
conditions: r.conditions.map((c) => pick(c, CONDITION_KEYS)),
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
return v;
|
|
428
|
+
}
|
|
429
|
+
/** The keys `stripSetting` drops from a valid `options` or `rules` value, e.g. `options[0].color`. */
|
|
430
|
+
function settingExtraKeys(key, v) {
|
|
431
|
+
if (key === 'options') {
|
|
432
|
+
return v.flatMap((o, i) => extraKeys(o, OPTION_KEYS, `options[${i}]`));
|
|
433
|
+
}
|
|
434
|
+
if (key === 'rules') {
|
|
435
|
+
return v.flatMap((r, i) => [
|
|
436
|
+
...extraKeys(r, RULE_KEYS, `rules[${i}]`),
|
|
437
|
+
...r.conditions
|
|
438
|
+
.flatMap((c, j) => extraKeys(c, CONDITION_KEYS, `rules[${i}].conditions[${j}]`)),
|
|
439
|
+
]);
|
|
440
|
+
}
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* A form row's settings read back for source: each settings key whose value
|
|
445
|
+
* has the platform's shape, copied, without any key the platform's schema does
|
|
446
|
+
* not define. A key with any other value is left out here; both cases are
|
|
447
|
+
* reported by `formFieldRowLoss`.
|
|
448
|
+
*/
|
|
449
|
+
export function liftFormFieldSettings(row) {
|
|
450
|
+
const settings = {};
|
|
451
|
+
for (const key of FORM_FIELD_SETTINGS_KEYS) {
|
|
452
|
+
if (row[key] !== undefined && validSetting(key, row[key]))
|
|
453
|
+
settings[key] = jsonClone(stripSetting(key, row[key]));
|
|
454
|
+
}
|
|
455
|
+
return settings;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* A field's settings as source options, in the platform's row order, for the
|
|
459
|
+
* decompilers' printers. `value` prints one data value (`'high'`, `6`,
|
|
460
|
+
* `[{ id: 'o1', … }]`).
|
|
461
|
+
*/
|
|
462
|
+
export function formFieldSettingsSource(settings, value) {
|
|
463
|
+
return FORM_FIELD_SETTINGS_KEYS.map((key) => [key, settings[key] === undefined ? undefined : value(settings[key])]);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* The keys of a form row the SDK cannot carry into source, each with why.
|
|
467
|
+
* Empty when decompile → compile writes the row back as it was. The lifts
|
|
468
|
+
* report a non-empty list as a warning (an error under `--strict`), so no key
|
|
469
|
+
* is dropped silently.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* formFieldRowLoss({ id: 'a', label: 'A', type: 'string', direction: 'output', colSpan: 13 }, 'human task')
|
|
473
|
+
* // ['colSpan: 13 (not the platform\'s shape for it)']
|
|
474
|
+
*/
|
|
475
|
+
export function formFieldRowLoss(row, site) {
|
|
476
|
+
const loss = [];
|
|
477
|
+
const id = String(row.id ?? '');
|
|
478
|
+
const direction = row.direction;
|
|
479
|
+
const say = (key, why) => {
|
|
480
|
+
loss.push(`${key}: ${JSON.stringify(row[key])} (${why})`);
|
|
481
|
+
};
|
|
482
|
+
for (const key of Object.keys(row)) {
|
|
483
|
+
if (row[key] === undefined)
|
|
484
|
+
continue;
|
|
485
|
+
if (!FORM_FIELD_ROW_KEYS.includes(key)) {
|
|
486
|
+
say(key, 'not a key of the platform\'s field schema');
|
|
487
|
+
}
|
|
488
|
+
else if (Object.hasOwn(SETTINGS, key)) {
|
|
489
|
+
if (!validSetting(key, row[key]))
|
|
490
|
+
say(key, 'not the platform\'s shape for it');
|
|
491
|
+
else {
|
|
492
|
+
const extra = settingExtraKeys(key, row[key]);
|
|
493
|
+
if (extra.length > 0)
|
|
494
|
+
say(key, `${extra.join(', ')}: not in the platform's schema, which drops it too`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else if (key === 'label') {
|
|
498
|
+
if (!isString(row.label))
|
|
499
|
+
say(key, 'not a string');
|
|
500
|
+
}
|
|
501
|
+
else if (key === 'value') {
|
|
502
|
+
say(key, 'the form editor\'s structured binding; a .flow stores it as `binding`, which is all the SDK reads');
|
|
503
|
+
}
|
|
504
|
+
else if (key === 'required') {
|
|
505
|
+
// `required: false` is the default: compile omits it, and the form behaves the same.
|
|
506
|
+
if (typeof row.required !== 'boolean')
|
|
507
|
+
say(key, 'not a boolean');
|
|
508
|
+
else if (direction === 'input' && row.required)
|
|
509
|
+
say(key, 'on a shown field, which the SDK does not model');
|
|
510
|
+
}
|
|
511
|
+
else if (key === 'binding') {
|
|
512
|
+
if (site === 'escalation')
|
|
513
|
+
say(key, 'on an escalation form, which the SDK writes without bindings');
|
|
514
|
+
else if (direction === 'output')
|
|
515
|
+
say(key, 'on an asked field, which the SDK writes without one');
|
|
516
|
+
else if (!isString(row.binding))
|
|
517
|
+
say(key, 'not a string');
|
|
518
|
+
}
|
|
519
|
+
else if (key === 'variable') {
|
|
520
|
+
if (site === 'escalation')
|
|
521
|
+
say(key, 'on an escalation form, which the SDK writes without variables');
|
|
522
|
+
else if (direction !== 'output')
|
|
523
|
+
say(key, 'on a shown or pre-filled field, which the SDK writes without one');
|
|
524
|
+
else if (row.variable !== `vars.${id}`)
|
|
525
|
+
say(key, `the SDK writes 'vars.${id}', the field's own id`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return loss;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* The warning (or `--strict` error) for a row `formFieldRowLoss` reported.
|
|
532
|
+
* `where` names the form, e.g. `human task "review"`.
|
|
533
|
+
*/
|
|
534
|
+
export function formFieldRowLossMessage(where, fieldId, loss) {
|
|
535
|
+
return `${where} form field ${JSON.stringify(fieldId)} has ${loss.length === 1 ? 'a key' : 'keys'} the SDK cannot `
|
|
536
|
+
+ `write back: ${loss.join('; ')}. Compile writes the field without ${loss.length === 1 ? 'it' : 'them'}, so `
|
|
537
|
+
+ `merging an edit of this node loses ${loss.length === 1 ? 'it' : 'them'}. Edit this field in the canvas or `
|
|
538
|
+
+ `the .flow JSON instead.`;
|
|
539
|
+
}
|
package/dist/decompile.js
CHANGED
|
@@ -51,7 +51,7 @@ import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
|
|
|
51
51
|
import { contributionPort, forwardFanOut } from './core/definition-handles.js';
|
|
52
52
|
import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
|
|
53
53
|
import { eventFilterFromTreeLeaf } from './core/event-filters.js';
|
|
54
|
-
import {
|
|
54
|
+
import { formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, liftFormFieldSettings, sourceFormFieldType, } from './core/form-field-type.js';
|
|
55
55
|
/**
|
|
56
56
|
* The trigger id of the flow currently being decompiled, so a reference through a
|
|
57
57
|
* RENAMED trigger is still recognized as `input('x')` rather than falling back to
|
|
@@ -280,6 +280,19 @@ function opts(entries) {
|
|
|
280
280
|
const parts = entries.filter(([, v]) => v !== undefined).map(([k, v]) => `${key(k)}: ${v}`);
|
|
281
281
|
return parts.length ? `{ ${parts.join(', ')} }` : '';
|
|
282
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Plain JSON data as source, in the `{ k: v }` / `[a, b]` spelling the
|
|
285
|
+
* printers use (a form field's `options`, `rules`, `defaultValue`).
|
|
286
|
+
*/
|
|
287
|
+
function dataSource(v) {
|
|
288
|
+
if (Array.isArray(v))
|
|
289
|
+
return `[${v.map(dataSource).join(', ')}]`;
|
|
290
|
+
if (v !== null && typeof v === 'object') {
|
|
291
|
+
const parts = Object.entries(v).filter(([, x]) => x !== undefined).map(([k, x]) => `${key(k)}: ${dataSource(x)}`);
|
|
292
|
+
return parts.length ? `{ ${parts.join(', ')} }` : '{}';
|
|
293
|
+
}
|
|
294
|
+
return tsLiteral(v);
|
|
295
|
+
}
|
|
283
296
|
/** A collector for indented source lines. */
|
|
284
297
|
class Emit {
|
|
285
298
|
indentUnit;
|
|
@@ -618,6 +631,11 @@ function liftRegistered(node, impl = registeredImpl(node), graph) {
|
|
|
618
631
|
// when the file carries none (a hand-trimmed or SDK-written file).
|
|
619
632
|
definition: () => runDefs.get(`${node.type}@${version}`) ?? BUNDLED_DEFINITIONS[`${node.type}@${version}`] ?? runDefs.get(node.type),
|
|
620
633
|
warn: warnOnce,
|
|
634
|
+
lossy: (message) => {
|
|
635
|
+
if (runStrict)
|
|
636
|
+
throw new Error(`decompile: ${message}`);
|
|
637
|
+
warnOnce(message);
|
|
638
|
+
},
|
|
621
639
|
outPorts: () => (graph?.outEdges(node.id) ?? []).map((e) => e.sourcePort),
|
|
622
640
|
convergedFanOut: () => graph?.convergedFanOut(node.id) === true,
|
|
623
641
|
};
|
|
@@ -1343,17 +1361,28 @@ function emitAgentCluster(node, imp, inputNames, graph) {
|
|
|
1343
1361
|
['variant', isQuickForm ? tsLiteral('quick-form') : undefined],
|
|
1344
1362
|
['name', tsLiteral(rPlain(e, 'name') ?? '')],
|
|
1345
1363
|
['description', rPlain(e, 'description') !== undefined ? tsLiteral(rPlain(e, 'description')) : undefined],
|
|
1346
|
-
['fields', rows.length ? `[${rows.map((f) =>
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1364
|
+
['fields', rows.length ? `[${rows.map((f) => {
|
|
1365
|
+
const id = String(f.id ?? '');
|
|
1366
|
+
const direction = String(f.direction ?? 'output');
|
|
1367
|
+
// Every row key the source cannot carry is reported, never dropped
|
|
1368
|
+
// silently: a warning, an error under --strict (#1250).
|
|
1369
|
+
const loss = formFieldRowLoss(f, 'escalation');
|
|
1370
|
+
if (loss.length > 0) {
|
|
1371
|
+
const message = formFieldRowLossMessage(`quick-form escalation ${JSON.stringify(rPlain(e, 'name') ?? e.id)} on agent ${JSON.stringify(node.id)}`, id, loss);
|
|
1372
|
+
if (runStrict)
|
|
1373
|
+
throw new Error(`decompile: ${message}`);
|
|
1374
|
+
warnOnce(message);
|
|
1375
|
+
}
|
|
1376
|
+
return opts([
|
|
1377
|
+
['id', tsText(f.id)],
|
|
1378
|
+
// serialize defaults a row's label to its id.
|
|
1379
|
+
['label', typeof f.label === 'string' && f.label !== id ? tsLiteral(f.label) : undefined],
|
|
1380
|
+
['type', tsLiteral(sourceFormFieldType(f.type ?? 'string'))],
|
|
1381
|
+
['direction', tsLiteral(direction)],
|
|
1382
|
+
['required', typeof f.required === 'boolean' && direction !== 'input' ? String(f.required) : undefined],
|
|
1383
|
+
...formFieldSettingsSource(liftFormFieldSettings(f), dataSource),
|
|
1384
|
+
]);
|
|
1385
|
+
}).join(', ')}]` : undefined],
|
|
1357
1386
|
['app', app === undefined ? undefined : opts([
|
|
1358
1387
|
['key', tsText(app.key)],
|
|
1359
1388
|
['name', tsText(app.name)],
|
package/dist/flow-node-type.d.ts
CHANGED
|
@@ -162,6 +162,12 @@ export interface FlowDeserializeContext {
|
|
|
162
162
|
definition(): unknown;
|
|
163
163
|
/** Report something the lift could not keep. Decompile prints it once. */
|
|
164
164
|
warn(message: string): void;
|
|
165
|
+
/**
|
|
166
|
+
* Report something the source cannot carry, so compile would drop it: a
|
|
167
|
+
* warning, or under `--strict` (`bestEffort: false`) an error that stops
|
|
168
|
+
* decompile.
|
|
169
|
+
*/
|
|
170
|
+
lossy(message: string): void;
|
|
165
171
|
/** The source ports this node's out-edges leave from, in edge order (an empty list outside a graph). */
|
|
166
172
|
outPorts(): readonly string[];
|
|
167
173
|
/**
|
|
@@ -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, formFieldOptionsProblem, formFieldOptionsWhy, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText } from '../../../core/form-field-type.js';
|
|
3
|
+
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, 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,8 +23,9 @@ 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
|
|
27
|
-
HITL_FIELD_OPTIONS_IGNORED: { severity: 'warning', summary: 'choices on a field
|
|
26
|
+
HITL_FIELD_OPTIONS: { severity: 'error', summary: 'a picker with no choices' },
|
|
27
|
+
HITL_FIELD_OPTIONS_IGNORED: { severity: 'warning', summary: 'choices on a field that does not render them' },
|
|
28
|
+
HITL_FIELD_SETTING_INVALID: { severity: 'error', summary: 'a form field setting (controlType, colSpan, …) the platform refuses' },
|
|
28
29
|
HITL_FIELD_NO_VALUE: { severity: 'error', summary: 'a shown field with no value' },
|
|
29
30
|
HITL_FIELD_VALUE_NOT_REF: { severity: 'error', summary: 'a shown field filled from a literal' },
|
|
30
31
|
HITL_FIELD_VALUE_NOT_PATH: { severity: 'error', summary: 'a shown field filled from a computed expression' },
|
|
@@ -217,10 +218,13 @@ export function checkHitl(inputs, ctx) {
|
|
|
217
218
|
at('HITL_FIELD_TYPE_UNKNOWN', `Human task "${step}" gives the form field "${id}" the type ${JSON.stringify(f?.type)}, `
|
|
218
219
|
+ `${formFieldTypeWhy(f?.type)}. Valid: ${validFormFieldTypesText()}.`, formFieldTypeHint(f?.type));
|
|
219
220
|
}
|
|
220
|
-
const optionsProblem = formFieldOptionsProblem(f
|
|
221
|
+
const optionsProblem = formFieldOptionsProblem(f);
|
|
221
222
|
if (optionsProblem !== undefined) {
|
|
222
223
|
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' }]`);
|
|
223
224
|
}
|
|
225
|
+
for (const problem of formFieldSettingProblems(f)) {
|
|
226
|
+
at('HITL_FIELD_SETTING_INVALID', `Human task "${step}" gives the form field "${id}" ${problem}. The platform refuses the form.`);
|
|
227
|
+
}
|
|
224
228
|
if (isAnswerField(f))
|
|
225
229
|
answers++; // an `output`, or an `inOut` whose reviewed value comes back
|
|
226
230
|
if (f?.direction === 'input' || f?.direction === 'inOut') {
|