@uipath/maestro-builder-sdk 6.66.6 → 6.66.8
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/bindings-v2.d.ts +15 -1
- package/dist/bindings-v2.js +29 -6
- package/dist/bpmn/check.js +16 -113
- package/dist/bpmn/serialize.d.ts +41 -0
- package/dist/bpmn/serialize.js +103 -1
- package/dist/bpmn/typed-node.d.ts +12 -0
- package/dist/bpmn/typed-node.js +24 -0
- package/dist/case/check.js +5 -40
- package/dist/case/serialize.js +6 -0
- package/dist/case/task-shape.d.ts +31 -0
- package/dist/case/task-shape.js +48 -0
- package/dist/check.js +7 -43
- package/dist/core/agent-sidecar.d.ts +13 -3
- package/dist/core/agent-sidecar.js +10 -0
- package/dist/core/compile-refusal.d.ts +25 -0
- package/dist/core/compile-refusal.js +19 -0
- package/dist/core/process-resource.d.ts +9 -0
- package/dist/core/process-resource.js +4 -0
- package/dist/core/refusals.d.ts +47 -44
- package/dist/core/refusals.js +47 -44
- package/dist/nodes/agent/flow/check.js +14 -42
- package/dist/nodes/agent/flow/inline-agent.js +62 -1
- package/dist/nodes/agent/flow/spec.d.ts +1 -1
- package/dist/nodes/connector/flow/node-type.js +5 -3
- package/dist/nodes/data-fabric/flow/check.js +2 -4
- package/dist/nodes/data-fabric/flow/shared.d.ts +9 -4
- package/dist/nodes/data-fabric/flow/shared.js +14 -4
- package/dist/nodes/document/flow/ixp.js +20 -9
- package/dist/nodes/hitl/flow/check.js +29 -110
- package/dist/nodes/hitl/flow/node-type.js +170 -3
- package/dist/nodes/process/flow/check.js +15 -80
- package/dist/nodes/process/flow/node-type.js +4 -0
- package/dist/nodes/process/flow/refusals.d.ts +8 -0
- package/dist/nodes/process/flow/refusals.js +92 -0
- package/dist/nodes/queue/flow/check.d.ts +25 -0
- package/dist/nodes/queue/flow/check.js +48 -18
- package/dist/nodes/queue/flow/node-type.js +13 -1
- package/dist/nodes/transform/flow/check.js +31 -43
- package/dist/nodes/transform/flow/node-type.js +35 -9
- package/dist/serialize.js +172 -20
- package/package.json +2 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Expr } from '../../../core/expr.js';
|
|
2
1
|
import { nearestName } from '../../../core/edit-distance.js';
|
|
3
2
|
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText, } from '../../../core/form-field-type.js';
|
|
4
3
|
import { isAnswerField } from '../../../core/hitl-answers.js';
|
|
@@ -85,15 +84,18 @@ export function checkHitl(inputs, ctx) {
|
|
|
85
84
|
// Which of the three human-task node types this step emits. The two suffixed
|
|
86
85
|
// variants differ in exactly one authoring-visible way — who OWNS the form —
|
|
87
86
|
// and that is what the next four rules are about.
|
|
87
|
+
//
|
|
88
|
+
// What the serializer cannot write faithfully is refused there, and reported
|
|
89
|
+
// from there (`refuseUnwritable` in node-type.ts, #1518): HITL_VARIANT_UNKNOWN,
|
|
90
|
+
// HITL_DOCUMENT_WITHOUT_VARIANT, DOCVAL_NO_EXTRACTION, DOCVAL_TAXONOMY_REQUIRED,
|
|
91
|
+
// DOCVAL_RENDER_UNKNOWN, HITL_APP_WITHOUT_VARIANT, HITL_APP_INCOMPLETE,
|
|
92
|
+
// HITL_APP_SCHEMA_INVALID, HITL_APP_OUTCOME_UNKNOWN, HITL_APP_LOCAL_HAS_FOLDER,
|
|
93
|
+
// HITL_FIELD_NO_ID, HITL_FIELD_VARIABLE, HITL_FIELD_VALUE_NOT_REF,
|
|
94
|
+
// HITL_FIELD_VALUE_NOT_PATH and HITL_OUTCOME_BAD_ID. The rules below still stop
|
|
95
|
+
// where those used to, so a task they refuse gets no follow-on findings.
|
|
88
96
|
const variant = inputs?.variant;
|
|
89
|
-
if (variant !== undefined && variant
|
|
90
|
-
|
|
91
|
-
const nearest = nearestName(String(variant), HITL_VARIANTS);
|
|
92
|
-
at('HITL_VARIANT_UNKNOWN', `Human task "${step}" names the variant ${JSON.stringify(variant)}, which is not a human-task `
|
|
93
|
-
+ `node type that resolves on this tenant. Valid: ${HITL_VARIANTS.join(', ')} — or omit it for `
|
|
94
|
-
+ `the default node type (uipath.human-in-the-loop).`, nearest !== undefined ? `variant: '${nearest}'` : `Omit \`variant\`.`);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
+
if (variant !== undefined && !HITL_VARIANTS.includes(variant))
|
|
98
|
+
return; // HITL_VARIANT_UNKNOWN
|
|
97
99
|
const isApp = variant === 'action-app';
|
|
98
100
|
const isDocval = variant === 'document-validation';
|
|
99
101
|
// The error output is the BASE node's 1.2 definition, which no variant has.
|
|
@@ -127,12 +129,8 @@ export function checkHitl(inputs, ctx) {
|
|
|
127
129
|
? 'outcomePorts: false (every outcome continues to the next step), or outcomePorts: true / .stepSwitch (route each one)'
|
|
128
130
|
: 'Drop `legacyCompleted`: one outcome continues to the next step on its own handle.');
|
|
129
131
|
}
|
|
130
|
-
if (inputs?.document !== undefined && !isDocval)
|
|
131
|
-
|
|
132
|
-
+ `${variant === undefined ? 'the default node type' : `"${variant}"`} — only the validation `
|
|
133
|
-
+ `station reads it, so it would be emitted and ignored.`, `variant: 'document-validation'`);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
132
|
+
if (inputs?.document !== undefined && !isDocval)
|
|
133
|
+
return; // HITL_DOCUMENT_WITHOUT_VARIANT
|
|
136
134
|
if (isDocval) {
|
|
137
135
|
if (Array.isArray(inputs?.fields) && inputs.fields.length > 0) {
|
|
138
136
|
at('HITL_DOCVAL_WITH_FIELDS', `Human task "${step}" is a Document Validation task but also declares ${inputs.fields.length} `
|
|
@@ -145,31 +143,12 @@ export function checkHitl(inputs, ctx) {
|
|
|
145
143
|
+ `has nothing to validate.`, `document: { extractionResult: out('<extractStep>', 'ExtractionResult') }`);
|
|
146
144
|
return;
|
|
147
145
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (erEmpty) {
|
|
151
|
-
at('DOCVAL_NO_EXTRACTION', `Human task "${step}" gives the validation station no extractionResult. Bind the `
|
|
152
|
-
+ `ExtractionResult output of the upstream extract step — the platform's own validator `
|
|
153
|
-
+ `rejects the node without it.`, `extractionResult: out('<extractStep>', 'ExtractionResult')`);
|
|
154
|
-
}
|
|
155
|
-
if (doc.render === 'custom') {
|
|
156
|
-
const tx = doc.taxonomy;
|
|
157
|
-
if (tx === undefined || tx === null || (typeof tx === 'string' && !tx.trim())) {
|
|
158
|
-
at('DOCVAL_TAXONOMY_REQUIRED', `Human task "${step}" renders validation in a custom app (render: 'custom') but names no `
|
|
159
|
-
+ `taxonomy — the platform requires one for the app's Validation Control.`, `taxonomy: out('<classifyStep>', 'taxonomy') // or the taxonomy id string`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
else if (doc.render !== undefined && doc.render !== 'standard') {
|
|
163
|
-
at('DOCVAL_RENDER_UNKNOWN', `Human task "${step}" gives the validation render type ${JSON.stringify(doc.render)}. Valid: `
|
|
164
|
-
+ `'standard' (the native station, the default) or 'custom' (an app with a Validation Control).`, `render: 'standard'`);
|
|
165
|
-
}
|
|
146
|
+
// Its extractionResult, render and taxonomy are the serializer's
|
|
147
|
+
// DOCVAL_NO_EXTRACTION, DOCVAL_RENDER_UNKNOWN and DOCVAL_TAXONOMY_REQUIRED.
|
|
166
148
|
// No inline form — skip the field rules; outcomes still route, fall through.
|
|
167
149
|
}
|
|
168
150
|
if (!isApp && !(isDocval && inputs?.document?.render === 'custom') && inputs?.app !== undefined) {
|
|
169
|
-
|
|
170
|
-
+ `${variant === undefined ? 'the default node type' : `"${variant}"`}, whose form lives in the `
|
|
171
|
-
+ `flow. The app would be emitted and then ignored — the reviewer still gets the inline form.`, `variant: 'action-app'`);
|
|
172
|
-
return;
|
|
151
|
+
return; // HITL_APP_WITHOUT_VARIANT
|
|
173
152
|
}
|
|
174
153
|
if (isApp) {
|
|
175
154
|
// The app owns the form, so `fields` is not merely unnecessary here — it is a
|
|
@@ -186,25 +165,11 @@ export function checkHitl(inputs, ctx) {
|
|
|
186
165
|
+ `App — but names no app, so nothing tells the platform which form to show.`, `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
|
|
187
166
|
return;
|
|
188
167
|
}
|
|
189
|
-
//
|
|
190
|
-
// none it is serialized as an inline app, and on a serializer before
|
|
191
|
-
// flow-v1 #4076 the task loses its folderPath context and fails at run
|
|
192
|
-
// time (170004, "Invalid input for the HITL task").
|
|
168
|
+
// A missing name, key or folder is the serializer's HITL_APP_INCOMPLETE.
|
|
193
169
|
const inSolution = app.location === 'in-solution';
|
|
194
170
|
for (const k of inSolution ? ['name', 'key'] : ['name', 'key', 'folderPath']) {
|
|
195
|
-
if (typeof app[k] !== 'string' || !app[k].trim())
|
|
196
|
-
at('HITL_APP_INCOMPLETE', inSolution
|
|
197
|
-
? `Human task "${step}" names an in-solution Action App with no ${k}. An in-solution app is `
|
|
198
|
-
+ `resolved by its key (\`resource.key\` in resources/solution_folder/app/codedAction/<App>.json), `
|
|
199
|
-
+ `with the name shown to the reviewer — both are needed for the platform to find it.`
|
|
200
|
-
: `Human task "${step}" names an Action App with no ${k}. An app is resolved by its key plus `
|
|
201
|
-
+ `the folder it is deployed in, with the name shown to the reviewer — all three are needed `
|
|
202
|
-
+ `for the platform to find it. If the app is in the same solution as the flow, set `
|
|
203
|
-
+ `\`location: 'in-solution'\` instead of a folder.`, inSolution
|
|
204
|
-
? `app: { name: 'InvoiceReview', key: '<resource.key>', location: 'in-solution' }`
|
|
205
|
-
: `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
|
|
171
|
+
if (typeof app[k] !== 'string' || !app[k].trim())
|
|
206
172
|
return;
|
|
207
|
-
}
|
|
208
173
|
}
|
|
209
174
|
// The app's action schema: what types its inputs, and what it answers.
|
|
210
175
|
const bound = Object.keys(app.inputs ?? {});
|
|
@@ -218,10 +183,7 @@ export function checkHitl(inputs, ctx) {
|
|
|
218
183
|
}
|
|
219
184
|
}
|
|
220
185
|
else if (typeof contract === 'string') {
|
|
221
|
-
|
|
222
|
-
+ `Pass the app's action-schema.json verbatim, or the platform's action schema (its `
|
|
223
|
-
+ `inputs/outputs/inOuts/outcomes arrays).`);
|
|
224
|
-
return;
|
|
186
|
+
return; // HITL_APP_SCHEMA_INVALID
|
|
225
187
|
}
|
|
226
188
|
else {
|
|
227
189
|
const names = contract.inputs;
|
|
@@ -237,22 +199,8 @@ export function checkHitl(inputs, ctx) {
|
|
|
237
199
|
at('HITL_APP_INPUT_REQUIRED', `Human task "${step}" leaves the Action App's required input(s) ${missing.join(', ')} unbound, `
|
|
238
200
|
+ `so the task's arguments fail the app's own schema.`, `inputs: { ${missing.map((m) => `${JSON.stringify(m)}: …`).join(', ')} }`);
|
|
239
201
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const name = typeof o === 'string' ? o : o?.name;
|
|
243
|
-
if (typeof name === 'string' && name !== '' && !contract.outcomes.includes(name)) {
|
|
244
|
-
const nearest = nearestName(name, contract.outcomes);
|
|
245
|
-
at('HITL_APP_OUTCOME_UNKNOWN', `Human task "${step}" routes on the outcome "${name}", but its Action App completes only with `
|
|
246
|
-
+ `${contract.outcomes.join(', ')}. The reviewer can never pick it.`, nearest !== undefined ? `'${nearest}'` : undefined);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (inSolution && app.folderPath !== undefined && app.folderPath !== '') {
|
|
252
|
-
at('HITL_APP_LOCAL_HAS_FOLDER', `Human task "${step}" names an in-solution Action App with folderPath ${JSON.stringify(app.folderPath)}. `
|
|
253
|
-
+ `An in-solution app has no folder — it is emitted with the empty folder the platform's app picker `
|
|
254
|
-
+ `writes for an app inline to the solution — so the value would be dropped. Remove it, or drop `
|
|
255
|
-
+ `\`location: 'in-solution'\` to call an app published to that folder.`, `Delete \`folderPath\`.`);
|
|
202
|
+
// An outcome the app does not declare is the serializer's HITL_APP_OUTCOME_UNKNOWN,
|
|
203
|
+
// and an in-solution app's folderPath its HITL_APP_LOCAL_HAS_FOLDER.
|
|
256
204
|
}
|
|
257
205
|
// Outcomes still matter for an app task: the flow's own routing and its
|
|
258
206
|
// `Action`/`status` outputs are derived from them, so fall through to the
|
|
@@ -268,14 +216,10 @@ export function checkHitl(inputs, ctx) {
|
|
|
268
216
|
}
|
|
269
217
|
const seen = new Set();
|
|
270
218
|
let answers = 0;
|
|
271
|
-
fields.forEach((f
|
|
272
|
-
const where = `fields[${i}]`;
|
|
219
|
+
fields.forEach((f) => {
|
|
273
220
|
const id = typeof f?.id === 'string' ? f.id.trim() : '';
|
|
274
|
-
if (!id)
|
|
275
|
-
|
|
276
|
-
+ `reviewer's answer comes back under, so a field without one is dropped.`, `id: 'amount'`);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
221
|
+
if (!id)
|
|
222
|
+
return; // HITL_FIELD_NO_ID
|
|
279
223
|
if (seen.has(id)) {
|
|
280
224
|
at('HITL_DUP_FIELD', `Human task "${step}" declares the form field "${id}" twice. The answer object is keyed `
|
|
281
225
|
+ `by field id, so the second silently replaces the first.`, `Rename one of them.`);
|
|
@@ -299,20 +243,8 @@ export function checkHitl(inputs, ctx) {
|
|
|
299
243
|
for (const problem of formFieldSettingProblems(f)) {
|
|
300
244
|
at('HITL_FIELD_SETTING_INVALID', `Human task "${step}" gives the form field "${id}" ${problem}. The platform refuses the form.`);
|
|
301
245
|
}
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// has one: a shown field's row is written without it (#1309).
|
|
305
|
-
const variable = f?.variable;
|
|
306
|
-
if (variable !== undefined) {
|
|
307
|
-
if (f?.direction !== 'output') {
|
|
308
|
-
at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" sets \`variable\` on a ${f?.direction === 'inOut' ? 'pre-filled' : 'shown'} field. `
|
|
309
|
-
+ `Only an asked field (direction 'output') lands its answer in a variable; the SDK writes the row without one.`, 'Drop `variable`, or make the field direction: \'output\'.');
|
|
310
|
-
}
|
|
311
|
-
else if (typeof variable !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(variable)) {
|
|
312
|
-
at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" has variable ${JSON.stringify(variable)}, which is not one identifier. `
|
|
313
|
-
+ `It is read as $vars.<variable>, so give the name only — no \`vars.\` prefix, no dots.`, typeof variable === 'string' && variable.startsWith('vars.') ? `variable: '${variable.slice(5)}'` : `variable: '${step}_${id}'`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
246
|
+
// A `variable` on a row that has none, or one that is no identifier, is the
|
|
247
|
+
// serializer's HITL_FIELD_VARIABLE (#1309).
|
|
316
248
|
if (isAnswerField(f))
|
|
317
249
|
answers++; // an `output`, or an `inOut` whose reviewed value comes back
|
|
318
250
|
if (f?.direction === 'input' || f?.direction === 'inOut') {
|
|
@@ -321,20 +253,8 @@ export function checkHitl(inputs, ctx) {
|
|
|
321
253
|
at('HITL_FIELD_NO_VALUE', `Human task "${step}" shows the form field "${id}" to the reviewer but gives it no value, `
|
|
322
254
|
+ `so the field renders empty — the flow says "show them this" and shows them nothing.`, `value: input('<flowInput>') // or out('<step>', '<field>')`);
|
|
323
255
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
+ `${Array.isArray(value) ? 'array' : typeof value} as its value. A shown field is filled `
|
|
327
|
-
+ `from a variable PATH, not a constant — the platform reads the string as a lookup, so a `
|
|
328
|
-
+ `literal resolves to nothing on the form.`, `value: input('<flowInput>')`);
|
|
329
|
-
}
|
|
330
|
-
else if (value.literal || !/^\$vars\.[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(value.js.trim())) {
|
|
331
|
-
// Same trap as a transform's `collection`: a computed expression resolves
|
|
332
|
-
// locally and to nothing once deployed, where it is used as a path.
|
|
333
|
-
at('HITL_FIELD_VALUE_NOT_PATH', `Human task "${step}" fills the form field "${id}" from the expression `
|
|
334
|
-
+ `"${value.literal ? String(value.value) : value.js.trim().slice(0, 60)}". A field binding is `
|
|
335
|
-
+ `a variable PATH, so a computed expression resolves to nothing on the deployed form. `
|
|
336
|
-
+ `Compute it in a script step first and point the field at that step's output.`, `value: out('<scriptStep>')`);
|
|
337
|
-
}
|
|
256
|
+
// A value that is no `$vars.` path is the serializer's HITL_FIELD_VALUE_NOT_REF
|
|
257
|
+
// (a constant) or HITL_FIELD_VALUE_NOT_PATH (an expression).
|
|
338
258
|
}
|
|
339
259
|
else if (f?.direction !== 'output') {
|
|
340
260
|
at('HITL_FIELD_DIRECTION', `Human task "${step}" gives the form field "${id}" the direction `
|
|
@@ -359,8 +279,7 @@ export function checkHitl(inputs, ctx) {
|
|
|
359
279
|
const id = outcomeIdOf(o, i);
|
|
360
280
|
const explicitId = typeof o === 'object' && o !== null ? o.id : undefined;
|
|
361
281
|
if (explicitId !== undefined && (typeof explicitId !== 'string' || explicitId === '')) {
|
|
362
|
-
|
|
363
|
-
+ `Leave \`id\` out to use the slug of the name.`, `{ name: ${JSON.stringify(name ?? 'Approve')} }`);
|
|
282
|
+
// The serializer's HITL_OUTCOME_BAD_ID; the id it would get is no clash to report.
|
|
364
283
|
}
|
|
365
284
|
else if (ids.has(id) && ids.get(id) !== String(name ?? '')) {
|
|
366
285
|
// Same name, same id: HITL_DUP_OUTCOME below reports it once, as a name clash.
|
|
@@ -17,9 +17,10 @@ import { formFieldJsonSchema, formFieldRowLoss, formFieldRowLossMessage, formFie
|
|
|
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 { nearestName } from '../../../core/edit-distance.js';
|
|
20
21
|
import { schemaId } from '../../../core/stable-id.js';
|
|
21
22
|
import { wireExprBody } from '../../../core/wire-expr.js';
|
|
22
|
-
import { appAnswerSchemas, appInputSchema, liftActionSchema, normalizeActionSchema } from './action-schema.js';
|
|
23
|
+
import { appAnswerSchemas, appContract, appInputSchema, liftActionSchema, normalizeActionSchema } from './action-schema.js';
|
|
23
24
|
import { checkHitl, checkHitlRead } from './check.js';
|
|
24
25
|
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
25
26
|
import { processResourceBindings } from '../../../core/process-resource.js';
|
|
@@ -287,7 +288,7 @@ function appBindingExpr(value, ctx) {
|
|
|
287
288
|
* A shown field's `binding`: the platform resolves it as a path against the
|
|
288
289
|
* workflow's variables (`vars.<flowInput>`, `vars.<node>.output.<field>`), so the
|
|
289
290
|
* `$vars.` prefix our expressions carry is dropped. A computed expression cannot
|
|
290
|
-
* be a path — `
|
|
291
|
+
* be a path — `refuseUnwritable` refuses one (HITL_FIELD_VALUE_NOT_PATH) before we get here.
|
|
291
292
|
*/
|
|
292
293
|
function bindingPath(value, ctx) {
|
|
293
294
|
if (!(value instanceof Expr))
|
|
@@ -300,6 +301,171 @@ function bindingPath(value, ctx) {
|
|
|
300
301
|
// `__hitlInputBindings` the designer reads back stores the binding verbatim.
|
|
301
302
|
return path.startsWith('vars.') ? path : `vars.${path}`;
|
|
302
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* The human-task shapes this serializer cannot write faithfully, refused while
|
|
306
|
+
* the node is emitted (#1518). `compile` no longer runs `check` (#1497), so
|
|
307
|
+
* without these each one compiled clean into a `.flow` that dropped, rewrote or
|
|
308
|
+
* invented what the author wrote. For instance an unknown `variant: 'quik-form'`
|
|
309
|
+
* became a plain base node, `document: {}` became the extraction result
|
|
310
|
+
* `"undefined"`, and an asked field with no id landed in `vars.undefined`.
|
|
311
|
+
* `check` reports the same refusals, on the step, from this one place (collect
|
|
312
|
+
* mode). Every condition each code names is refused, the lossy shape and the
|
|
313
|
+
* rest alike: the rest are tasks the platform cannot run as written (a blank
|
|
314
|
+
* extraction result, an app it cannot find, a computed field binding that
|
|
315
|
+
* resolves to nothing), and `compile` refused all of them before #1497.
|
|
316
|
+
* The checks run in `checkHitl`'s order, so the first refusal is the one
|
|
317
|
+
* `check` used to report first.
|
|
318
|
+
*/
|
|
319
|
+
function refuseUnwritable(inputs, typeName, step) {
|
|
320
|
+
const variant = inputs?.variant;
|
|
321
|
+
const isApp = typeName === ACTION_APP;
|
|
322
|
+
const isDocval = typeName === DOC_VALIDATION;
|
|
323
|
+
const variantText = variant === undefined ? 'the default node type' : `"${variant}"`;
|
|
324
|
+
// An unknown variant resolves to the base node type (`matches`), so the
|
|
325
|
+
// variant the author named would leave no trace in the `.flow`.
|
|
326
|
+
if (variant !== undefined && !VARIANTS.includes(variant)) {
|
|
327
|
+
const nearest = nearestName(String(variant), [...VARIANTS]);
|
|
328
|
+
throw new CompileRefusal('HITL_VARIANT_UNKNOWN', `Human task "${step}" names the variant ${JSON.stringify(variant)}, which is not a human-task `
|
|
329
|
+
+ `node type that resolves on this tenant. Valid: ${VARIANTS.join(', ')} — or omit it for `
|
|
330
|
+
+ `the default node type (uipath.human-in-the-loop).`, nearest !== undefined ? `variant: '${nearest}'` : `Omit \`variant\`.`);
|
|
331
|
+
}
|
|
332
|
+
// Only the station's node type writes `document` (`docvalInputs`).
|
|
333
|
+
if (inputs?.document !== undefined && !isDocval) {
|
|
334
|
+
throw new CompileRefusal('HITL_DOCUMENT_WITHOUT_VARIANT', `Human task "${step}" names a document to validate, but its variant is ${variantText} — only the validation `
|
|
335
|
+
+ `station reads it, so it would be emitted and ignored.`, `variant: 'document-validation'`);
|
|
336
|
+
}
|
|
337
|
+
const doc = inputs?.document;
|
|
338
|
+
if (isDocval && doc && typeof doc === 'object') {
|
|
339
|
+
// A missing one was written as the text "undefined" (or "null").
|
|
340
|
+
const er = doc.extractionResult;
|
|
341
|
+
if (er === undefined || er === null || (typeof er === 'string' && !er.trim())) {
|
|
342
|
+
throw new CompileRefusal('DOCVAL_NO_EXTRACTION', `Human task "${step}" gives the validation station no extractionResult. Bind the `
|
|
343
|
+
+ `ExtractionResult output of the upstream extract step — the platform's own validator `
|
|
344
|
+
+ `rejects the node without it.`, `extractionResult: out('<extractStep>', 'ExtractionResult')`);
|
|
345
|
+
}
|
|
346
|
+
const render = doc.render;
|
|
347
|
+
if (render === 'custom') {
|
|
348
|
+
// A null taxonomy was written as the text "null".
|
|
349
|
+
const tx = doc.taxonomy;
|
|
350
|
+
if (tx === undefined || tx === null || (typeof tx === 'string' && !tx.trim())) {
|
|
351
|
+
throw new CompileRefusal('DOCVAL_TAXONOMY_REQUIRED', `Human task "${step}" renders validation in a custom app (render: 'custom') but names no `
|
|
352
|
+
+ `taxonomy — the platform requires one for the app's Validation Control.`, `taxonomy: out('<classifyStep>', 'taxonomy') // or the taxonomy id string`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (render !== undefined && render !== 'standard') {
|
|
356
|
+
// A null render was written as 'standard'.
|
|
357
|
+
throw new CompileRefusal('DOCVAL_RENDER_UNKNOWN', `Human task "${step}" gives the validation render type ${JSON.stringify(render)}. Valid: `
|
|
358
|
+
+ `'standard' (the native station, the default) or 'custom' (an app with a Validation Control).`, `render: 'standard'`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Only an app task and a custom-render station write `app` (`emitsApp`).
|
|
362
|
+
if (!isApp && !(isDocval && doc?.render === 'custom') && inputs?.app !== undefined) {
|
|
363
|
+
throw new CompileRefusal('HITL_APP_WITHOUT_VARIANT', `Human task "${step}" names a deployed app, but its variant is ${variantText}, whose form lives in the `
|
|
364
|
+
+ `flow. The app would be emitted and then ignored — the reviewer still gets the inline form.`, `variant: 'action-app'`);
|
|
365
|
+
}
|
|
366
|
+
const app = inputs?.app;
|
|
367
|
+
if (isApp && app && typeof app === 'object') {
|
|
368
|
+
// A published app with no folder was written with folderPath '', byte for
|
|
369
|
+
// byte an in-solution app.
|
|
370
|
+
const inSolution = app.location === 'in-solution';
|
|
371
|
+
for (const k of inSolution ? ['name', 'key'] : ['name', 'key', 'folderPath']) {
|
|
372
|
+
if (typeof app[k] !== 'string' || !app[k].trim()) {
|
|
373
|
+
throw new CompileRefusal('HITL_APP_INCOMPLETE', inSolution
|
|
374
|
+
? `Human task "${step}" names an in-solution Action App with no ${k}. An in-solution app is `
|
|
375
|
+
+ `resolved by its key (\`resource.key\` in resources/solution_folder/app/codedAction/<App>.json), `
|
|
376
|
+
+ `with the name shown to the reviewer — both are needed for the platform to find it.`
|
|
377
|
+
: `Human task "${step}" names an Action App with no ${k}. An app is resolved by its key plus `
|
|
378
|
+
+ `the folder it is deployed in, with the name shown to the reviewer — all three are needed `
|
|
379
|
+
+ `for the platform to find it. If the app is in the same solution as the flow, set `
|
|
380
|
+
+ `\`location: 'in-solution'\` instead of a folder.`, inSolution
|
|
381
|
+
? `app: { name: 'InvoiceReview', key: '<resource.key>', location: 'in-solution' }`
|
|
382
|
+
: `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// An unusable actionSchema was dropped; with an inputSchema too, the
|
|
386
|
+
// inputSchema was replaced by the lowered actionSchema.
|
|
387
|
+
const contract = appContract(app);
|
|
388
|
+
if (typeof contract === 'string') {
|
|
389
|
+
throw new CompileRefusal('HITL_APP_SCHEMA_INVALID', `Human task "${step}" gives its Action App a schema that is not usable: ${contract}. `
|
|
390
|
+
+ `Pass the app's action-schema.json verbatim, or the platform's action schema (its `
|
|
391
|
+
+ `inputs/outputs/inOuts/outcomes arrays).`);
|
|
392
|
+
}
|
|
393
|
+
// The schema's outcome list is not written, so the mismatch would be gone.
|
|
394
|
+
if (contract !== undefined && contract.outcomes.length > 0) {
|
|
395
|
+
for (const o of Array.isArray(inputs?.outcomes) ? inputs.outcomes : []) {
|
|
396
|
+
const name = typeof o === 'string' ? o : o?.name;
|
|
397
|
+
if (typeof name === 'string' && name !== '' && !contract.outcomes.includes(name)) {
|
|
398
|
+
const nearest = nearestName(name, contract.outcomes);
|
|
399
|
+
throw new CompileRefusal('HITL_APP_OUTCOME_UNKNOWN', `Human task "${step}" routes on the outcome "${name}", but its Action App completes only with `
|
|
400
|
+
+ `${contract.outcomes.join(', ')}. The reviewer can never pick it.`, nearest !== undefined ? `'${nearest}'` : undefined);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// An in-solution app is written with folderPath '' whatever the source says.
|
|
405
|
+
if (inSolution && app.folderPath !== undefined && app.folderPath !== '') {
|
|
406
|
+
throw new CompileRefusal('HITL_APP_LOCAL_HAS_FOLDER', `Human task "${step}" names an in-solution Action App with folderPath ${JSON.stringify(app.folderPath)}. `
|
|
407
|
+
+ `An in-solution app has no folder — it is emitted with the empty folder the platform's app picker `
|
|
408
|
+
+ `writes for an app inline to the solution — so the value would be dropped. Remove it, or drop `
|
|
409
|
+
+ `\`location: 'in-solution'\` to call an app published to that folder.`, `Delete \`folderPath\`.`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// An app task or a validation station drops `fields` (check's
|
|
413
|
+
// HITL_APP_WITH_FIELDS / HITL_DOCVAL_WITH_FIELDS), so its rows are never
|
|
414
|
+
// written and the field rules do not apply — matching `checkHitl`.
|
|
415
|
+
const fields = !isApp && !isDocval && Array.isArray(inputs?.fields) ? inputs.fields : [];
|
|
416
|
+
fields.forEach((f, i) => {
|
|
417
|
+
// An asked field with no id landed in `vars.undefined`.
|
|
418
|
+
const id = typeof f?.id === 'string' ? f.id.trim() : '';
|
|
419
|
+
if (!id) {
|
|
420
|
+
throw new CompileRefusal('HITL_FIELD_NO_ID', `Human task "${step}" has a form field with no id (fields[${i}]). The id is the key the `
|
|
421
|
+
+ `reviewer's answer comes back under, so a field without one is dropped.`, `id: 'amount'`);
|
|
422
|
+
}
|
|
423
|
+
// Only an asked row is written with a `variable`, as `vars.<variable>`.
|
|
424
|
+
const variable = f.variable;
|
|
425
|
+
if (variable !== undefined) {
|
|
426
|
+
if (f.direction !== 'output') {
|
|
427
|
+
throw new CompileRefusal('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" sets \`variable\` on a ${f.direction === 'inOut' ? 'pre-filled' : 'shown'} field. `
|
|
428
|
+
+ `Only an asked field (direction 'output') lands its answer in a variable; the SDK writes the row without one.`, 'Drop `variable`, or make the field direction: \'output\'.');
|
|
429
|
+
}
|
|
430
|
+
if (typeof variable !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(variable)) {
|
|
431
|
+
throw new CompileRefusal('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" has variable ${JSON.stringify(variable)}, which is not one identifier. `
|
|
432
|
+
+ `It is read as $vars.<variable>, so give the name only — no \`vars.\` prefix, no dots.`, typeof variable === 'string' && variable.startsWith('vars.') ? `variable: '${variable.slice(5)}'` : `variable: '${step}_${id}'`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// A shown row's value is written as a variable path (`bindingPath`): a
|
|
436
|
+
// constant `'hello'` became the path `hello`, and `lit('hello')` or an
|
|
437
|
+
// expression not rooted in `$vars.` became `vars.<it>`.
|
|
438
|
+
if (f.direction === 'input' || f.direction === 'inOut') {
|
|
439
|
+
const value = f.value;
|
|
440
|
+
if (value === undefined || value === null)
|
|
441
|
+
return; // check's HITL_FIELD_NO_VALUE
|
|
442
|
+
if (!(value instanceof Expr)) {
|
|
443
|
+
throw new CompileRefusal('HITL_FIELD_VALUE_NOT_REF', `Human task "${step}" gives the form field "${id}" a raw `
|
|
444
|
+
+ `${Array.isArray(value) ? 'array' : typeof value} as its value. A shown field is filled `
|
|
445
|
+
+ `from a variable PATH, not a constant — the platform reads the string as a lookup, so a `
|
|
446
|
+
+ `literal resolves to nothing on the form. If the value already is an \`input(...)\` or `
|
|
447
|
+
+ `\`out(...)\` reference, two copies of the SDK are loaded: an expression built by the other `
|
|
448
|
+
+ `copy is not recognised as one — import every helper from one SDK install.`, `value: input('<flowInput>')`);
|
|
449
|
+
}
|
|
450
|
+
if (value.literal || !/^\$vars\.[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(value.js.trim())) {
|
|
451
|
+
throw new CompileRefusal('HITL_FIELD_VALUE_NOT_PATH', `Human task "${step}" fills the form field "${id}" from the expression `
|
|
452
|
+
+ `"${value.literal ? String(value.value) : value.js.trim().slice(0, 60)}". A field binding is `
|
|
453
|
+
+ `a variable PATH, so a computed expression resolves to nothing on the deployed form. `
|
|
454
|
+
+ `Compute it in a script step first and point the field at that step's output.`, `value: out('<scriptStep>')`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
// An id that is not a non-empty string was replaced by the name's slug (`outcomeIdOf`).
|
|
459
|
+
const outcomes = Array.isArray(inputs?.outcomes) ? inputs.outcomes : [];
|
|
460
|
+
outcomes.forEach((o, i) => {
|
|
461
|
+
const explicitId = typeof o === 'object' && o !== null ? o.id : undefined;
|
|
462
|
+
if (explicitId !== undefined && (typeof explicitId !== 'string' || explicitId === '')) {
|
|
463
|
+
const name = (typeof o === 'string' ? o : o?.name)?.trim();
|
|
464
|
+
throw new CompileRefusal('HITL_OUTCOME_BAD_ID', `Human task "${step}" outcome ${JSON.stringify(name ?? i)} has an id that is not a non-empty string. `
|
|
465
|
+
+ `Leave \`id\` out to use the slug of the name.`, `{ name: ${JSON.stringify(name ?? 'Approve')} }`);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
303
469
|
/**
|
|
304
470
|
* The exits `.stepSwitch` routes over, or why the task has none — read off the
|
|
305
471
|
* same model the task's tails come from.
|
|
@@ -639,6 +805,7 @@ function hitlAt(typeName, version, factory) {
|
|
|
639
805
|
throw new CompileRefusal('HITL_EXPOSE_ERROR_VERSION', `"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
|
|
640
806
|
+ `Drop the pin or match it.`);
|
|
641
807
|
}
|
|
808
|
+
refuseUnwritable(inputs, typeName, ctx.step);
|
|
642
809
|
const { inputs: nodeInputs, outputs } = hitlInputs(inputs, typeName, ctx);
|
|
643
810
|
// The definition's `outputDefinition` gives the two output SLOTS; the form
|
|
644
811
|
// gives their shape (see hitlInputs), so the instance's outputs replace it.
|
|
@@ -696,7 +863,7 @@ function hitlType(typeName, versions, factory, variant) {
|
|
|
696
863
|
kind: 'hitl', typeName, factory: 'hitl', defaultVersion: '1.0',
|
|
697
864
|
versions: Object.fromEntries(versions.map((v) => [v, hitlAt(typeName, v, factory)])),
|
|
698
865
|
// The base node is the one an absent or UNKNOWN variant resolves to, as it
|
|
699
|
-
// always has:
|
|
866
|
+
// always has: its serialize refuses the unknown one (HITL_VARIANT_UNKNOWN).
|
|
700
867
|
matches: ({ inputs }) => (variant === undefined
|
|
701
868
|
? !VARIANTS.includes(inputs?.variant)
|
|
702
869
|
: inputs?.variant === variant),
|
|
@@ -2,7 +2,7 @@ import { AGENTIC_PROCESS_FORMS } from '../../../core/agentic-process-forms.js';
|
|
|
2
2
|
import { GUID_RE } from '../../../core/binding-messages.js';
|
|
3
3
|
import { nearestName } from '../../../core/edit-distance.js';
|
|
4
4
|
import { ownEntry } from '../../../core/own-entry.js';
|
|
5
|
-
import {
|
|
5
|
+
import { blank } from '../../../core/process-resource.js';
|
|
6
6
|
export const PROCESS_DIAGNOSTICS = {
|
|
7
7
|
AGENT_EMPTY_INPUT_FLOOR: { severity: 'warning', summary: 'an agent step that binds no inputs' },
|
|
8
8
|
AGENT_EMPTY_RETURNS: { severity: 'warning', summary: 'an agent step with returns: {}' },
|
|
@@ -90,16 +90,8 @@ export const PROCESS_DIAGNOSTICS = {
|
|
|
90
90
|
export function checkRpaWorkflow(inputs, ctx) {
|
|
91
91
|
const step = ctx.step;
|
|
92
92
|
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
93
|
-
|
|
94
|
-
if (blank(inputs?.key) && !
|
|
95
|
-
at('RPA_NO_KEY', renamedFieldMessage('RPA', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
|
|
96
|
-
}
|
|
97
|
-
else if (blank(inputs?.key)) {
|
|
98
|
-
at('RPA_NO_KEY', `RPA step "${step}" has no \`key\`. That is the published process's release key (a GUID), and `
|
|
99
|
-
+ `it becomes part of the node's type — without it there is no process to start. Find it with `
|
|
100
|
-
+ `\`uip or processes list --all-folders --process-type Process\` (the row's \`Key\`).`, `key: '486edc26-0658-4ac1-92c9-1ef953927151'`);
|
|
101
|
-
}
|
|
102
|
-
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
93
|
+
// A blank key is the serializer's RPA_NO_KEY (refusals.ts).
|
|
94
|
+
if (!blank(inputs?.key) && !GUID_RE.test(String(inputs.key).trim())) {
|
|
103
95
|
at('RPA_KEY_NOT_KEY', `RPA step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a release key. `
|
|
104
96
|
+ `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
|
|
105
97
|
+ `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
|
|
@@ -121,12 +113,7 @@ export function checkRpaWorkflow(inputs, ctx) {
|
|
|
121
113
|
at('RPA_LOCAL_PROJECT_ID_NOT_GUID', `In-solution RPA step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
|
|
122
114
|
+ `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
|
|
123
115
|
}
|
|
124
|
-
|
|
125
|
-
at('RPA_LOCAL_HAS_FOLDER', `In-solution RPA step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
|
|
126
|
-
+ `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
|
|
127
|
-
+ `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
|
|
128
|
-
+ `call the published copy.`);
|
|
129
|
-
}
|
|
116
|
+
// A folderPath beside in-solution is the serializer's RPA_LOCAL_HAS_FOLDER (refusals.ts).
|
|
130
117
|
// The two are swappable by shape, and swapping them is invisible until
|
|
131
118
|
// Orchestrator answers "Folder does not exist or the user does not have access".
|
|
132
119
|
const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
|
|
@@ -160,16 +147,8 @@ export function checkRpaWorkflow(inputs, ctx) {
|
|
|
160
147
|
export function checkApiWorkflow(inputs, ctx) {
|
|
161
148
|
const step = ctx.step;
|
|
162
149
|
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
163
|
-
|
|
164
|
-
if (blank(inputs?.key) && !
|
|
165
|
-
at('APIWF_NO_KEY', renamedFieldMessage('API workflow', step, 'workflow'), `key: ${JSON.stringify(inputs.workflow)}`);
|
|
166
|
-
}
|
|
167
|
-
else if (blank(inputs?.key)) {
|
|
168
|
-
at('APIWF_NO_KEY', `API workflow step "${step}" has no \`key\`. That is the published workflow's key (a GUID), and `
|
|
169
|
-
+ `it becomes part of the node's type — without it there is no workflow to invoke. Find it with `
|
|
170
|
-
+ `\`uip or processes list --all-folders\` (an API workflow's ProcessKey reads "<name>.api.<name>").`, `key: 'ce857908-ee1d-4392-b552-38bcea0be29c'`);
|
|
171
|
-
}
|
|
172
|
-
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
150
|
+
// A blank key is the serializer's APIWF_NO_KEY (refusals.ts).
|
|
151
|
+
if (!blank(inputs?.key) && !GUID_RE.test(String(inputs.key).trim())) {
|
|
173
152
|
at('APIWF_KEY_NOT_KEY', `API workflow step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
|
|
174
153
|
+ `\`key\` is the GUID the tenant gives the published workflow (it becomes the node type's suffix); `
|
|
175
154
|
+ `the workflow's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
|
|
@@ -208,13 +187,8 @@ export function checkApiWorkflow(inputs, ctx) {
|
|
|
208
187
|
export function checkPublishedFunction(inputs, ctx) {
|
|
209
188
|
const step = ctx.step;
|
|
210
189
|
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
211
|
-
|
|
212
|
-
if (blank(inputs?.key)) {
|
|
213
|
-
at('FUNCTION_NO_KEY', `Function step "${step}" has no \`key\`. That is the published function's key (a GUID), and it becomes `
|
|
214
|
-
+ `part of the node's type — without it there is no function to invoke. Find it with `
|
|
215
|
-
+ `\`uip maestro flow registry search "uipath.core.function"\`.`, `key: '7059bdb5-fdd7-4e13-9d7b-1748aaeb129d'`);
|
|
216
|
-
}
|
|
217
|
-
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
190
|
+
// A blank key is the serializer's FUNCTION_NO_KEY (refusals.ts).
|
|
191
|
+
if (!blank(inputs?.key) && !GUID_RE.test(String(inputs.key).trim())) {
|
|
218
192
|
at('FUNCTION_KEY_NOT_KEY', `Function step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. \`key\` is the `
|
|
219
193
|
+ `GUID the tenant gives the published function (it becomes the node type's suffix); the function's NAME `
|
|
220
194
|
+ `goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
|
|
@@ -238,12 +212,7 @@ export function checkPublishedFunction(inputs, ctx) {
|
|
|
238
212
|
at('FUNCTION_LOCAL_PROJECT_ID_NOT_GUID', `In-solution function step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
|
|
239
213
|
+ `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
|
|
240
214
|
}
|
|
241
|
-
|
|
242
|
-
at('FUNCTION_LOCAL_HAS_FOLDER', `In-solution function step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
|
|
243
|
-
+ `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
|
|
244
|
-
+ `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
|
|
245
|
-
+ `call the published copy.`);
|
|
246
|
-
}
|
|
215
|
+
// A folderPath beside in-solution is the serializer's FUNCTION_LOCAL_HAS_FOLDER (refusals.ts).
|
|
247
216
|
const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
|
|
248
217
|
if (name.includes('/')) {
|
|
249
218
|
at('FUNCTION_NAME_IS_PATH', `Function step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
|
|
@@ -282,17 +251,8 @@ export function checkPublishedFunction(inputs, ctx) {
|
|
|
282
251
|
export function checkAgenticProcess(inputs, ctx) {
|
|
283
252
|
const step = ctx.step;
|
|
284
253
|
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
285
|
-
|
|
286
|
-
if (blank(inputs?.key) && !
|
|
287
|
-
at('AGENTIC_NO_KEY', renamedFieldMessage('Agentic process', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
|
|
288
|
-
}
|
|
289
|
-
else if (blank(inputs?.key)) {
|
|
290
|
-
at('AGENTIC_NO_KEY', `Agentic process step "${step}" has no \`key\`. That is the published process's key (a GUID), `
|
|
291
|
-
+ `and it becomes part of the node's type — without it there is no process to start. Find it with `
|
|
292
|
-
+ `\`uip or processes list --all-folders --limit 100\` (an agentic process's ProcessKey reads `
|
|
293
|
-
+ `"<name>.agentic.<name>"; the listing paginates at 50, so pass a limit or scope it to a folder).`, `key: '4fc450ab-89be-4462-8fc8-21ac4c1d6fb9'`);
|
|
294
|
-
}
|
|
295
|
-
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
254
|
+
// A blank key is the serializer's AGENTIC_NO_KEY (refusals.ts).
|
|
255
|
+
if (!blank(inputs?.key) && !GUID_RE.test(String(inputs.key).trim())) {
|
|
296
256
|
at('AGENTIC_KEY_NOT_KEY', `Agentic process step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
|
|
297
257
|
+ `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
|
|
298
258
|
+ `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
|
|
@@ -337,29 +297,14 @@ export function checkAgenticProcess(inputs, ctx) {
|
|
|
337
297
|
at('AGENTIC_LOCAL_PROJECT_ID_NOT_GUID', `In-solution agentic process step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
|
|
338
298
|
+ `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
|
|
339
299
|
}
|
|
340
|
-
|
|
341
|
-
at('AGENTIC_LOCAL_HAS_FOLDER', `In-solution agentic process step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
|
|
342
|
-
+ `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
|
|
343
|
-
+ `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
|
|
344
|
-
+ `call the published copy.`);
|
|
345
|
-
}
|
|
300
|
+
// A folderPath beside in-solution is the serializer's AGENTIC_LOCAL_HAS_FOLDER (refusals.ts).
|
|
346
301
|
const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
|
|
347
302
|
if (name.includes('/')) {
|
|
348
303
|
at('AGENTIC_NAME_IS_PATH', `Agentic process step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
|
|
349
304
|
+ `\`name\` is the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. `
|
|
350
305
|
+ `Together they form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
|
|
351
306
|
}
|
|
352
|
-
//
|
|
353
|
-
// node is emitted — so a process argument by this name is dropped on the way to
|
|
354
|
-
// the tenant and nothing local notices.
|
|
355
|
-
if (inputs?.inputs && Object.prototype.hasOwnProperty.call(inputs.inputs, 'fireAndForget')) {
|
|
356
|
-
at('AGENTIC_RESERVED_INPUT', `Agentic process step "${step}" passes an input called \`fireAndForget\`. That name is RESERVED: it is `
|
|
357
|
-
+ `the platform's design-time switch for starting the process without waiting, and its serializer `
|
|
358
|
-
+ `removes the key from the node's inputs before emitting — so a process argument of that name never `
|
|
359
|
-
+ `arrives. Locally it would appear to work, because this engine passes the whole envelope through. `
|
|
360
|
-
+ `Rename the argument, and note that this surface cannot author fire-and-forget at all (the engine `
|
|
361
|
-
+ `always waits for the job).`);
|
|
362
|
-
}
|
|
307
|
+
// An argument called `fireAndForget` is the serializer's AGENTIC_RESERVED_INPUT (refusals.ts).
|
|
363
308
|
// An empty `returns: {}` is a statement that the process returns nothing, which is
|
|
364
309
|
// legal — but it is almost always a half-finished edit, and the difference between
|
|
365
310
|
// it and a missing `returns` is invisible in the emitted file.
|
|
@@ -500,19 +445,9 @@ export function checkAgenticProcessRead({ node: name, field, rest, reader: step
|
|
|
500
445
|
export function checkAgent(inputs, ctx) {
|
|
501
446
|
const step = ctx.step;
|
|
502
447
|
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
503
|
-
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
504
448
|
const inSolution = inputs?.location === 'in-solution';
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
else if (blank(inputs?.key)) {
|
|
509
|
-
at('AGENT_NO_KEY', `Agent step "${step}" has no \`key\`. It is a GUID and becomes part of the node's `
|
|
510
|
-
+ `type — without it there is no agent to start. For a published agent, find the Orchestrator `
|
|
511
|
-
+ `release with \`uip or processes list --all-folders --limit 200\` (take the row's \`Key\`) or `
|
|
512
|
-
+ `\`uip maestro flow registry search uipath.core.agent\`. For an in-solution agent, read `
|
|
513
|
-
+ `\`resource.key\` from \`resources/solution_folder/process/agent/<project>.json\`.`, `key: '6a47dc5b-5fd4-461f-b97d-6c1817abcaaa'`);
|
|
514
|
-
}
|
|
515
|
-
else if (!GUID_RE.test(String(inputs.key).trim())) {
|
|
449
|
+
// A blank key is the serializer's AGENT_NO_KEY (refusals.ts).
|
|
450
|
+
if (!blank(inputs?.key) && !GUID_RE.test(String(inputs.key).trim())) {
|
|
516
451
|
at('AGENT_KEY_NOT_GUID', `Agent step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. \`key\` is `
|
|
517
452
|
+ `the GUID the tenant gives the published agent (it becomes the node type's suffix); the agent's NAME `
|
|
518
453
|
+ `goes in \`name\` and its folder in \`folderPath\`. Passing a ProcessKey like `
|
|
@@ -17,6 +17,7 @@ import { ownEntry } from '../../../core/own-entry.js';
|
|
|
17
17
|
import { resourceNodeSortOrder } from '../../../core/process-resource-sort-order.js';
|
|
18
18
|
import { processResourceBindings, processResourceLocation } from '../../../core/process-resource.js';
|
|
19
19
|
import { checkAgent, checkAgentRead, checkAgenticProcess, checkAgenticProcessRead, checkApiWorkflow, checkApiWorkflowRead, checkPublishedFunction, checkPublishedFunctionRead, checkRpaWorkflow, rpaWorkflowRead, } from './check.js';
|
|
20
|
+
import { refuseLossyProcess } from './refusals.js';
|
|
20
21
|
/** The RPA-workflow node type family. The process's release key is the suffix. */
|
|
21
22
|
const RPA_WORKFLOW_PREFIX = 'uipath.core.rpa-workflow.';
|
|
22
23
|
/**
|
|
@@ -635,6 +636,9 @@ export function processDefinition(spec) {
|
|
|
635
636
|
* are identical, and the DATA paths that differ are all downstream in the engine.
|
|
636
637
|
*/
|
|
637
638
|
function serializeProcess(spec, ctx) {
|
|
639
|
+
// A missing key, a folder beside in-solution, a reserved `fireAndForget`
|
|
640
|
+
// argument: each would be written wrong, so it is refused here (#1518).
|
|
641
|
+
refuseLossyProcess(spec.kind, spec.inputs, ctx.step);
|
|
638
642
|
const s = spec.inputs;
|
|
639
643
|
const fireAndForget = spec.kind === 'agenticProcess' && s.completion === 'fire-and-forget';
|
|
640
644
|
const def = processDefinition(spec);
|