@uipath/maestro-builder-sdk 6.66.5 → 6.66.7
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/check.js +2 -10
- package/dist/core/agent-sidecar.d.ts +13 -3
- package/dist/core/agent-sidecar.js +10 -0
- package/dist/core/refusals.d.ts +23 -22
- package/dist/core/refusals.js +23 -22
- package/dist/nodes/agent/flow/check.js +14 -42
- package/dist/nodes/agent/flow/inline-agent.js +62 -1
- package/dist/nodes/agent/flow/node-type.js +2 -2
- package/dist/nodes/agent/flow/spec.d.ts +1 -1
- 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/serialize.js +49 -6
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -1571,16 +1571,8 @@ function checkSubflow(step, child, inputs, parent, ancestors, diags) {
|
|
|
1571
1571
|
+ `emitted file at every call site, so a cycle has no base case and cannot be expanded.`);
|
|
1572
1572
|
return;
|
|
1573
1573
|
}
|
|
1574
|
-
// A
|
|
1575
|
-
//
|
|
1576
|
-
// what the child gets anyway, so `.trigger(manual(), { label, version })` is
|
|
1577
|
-
// allowed: it is how the child's start node is labelled (#1050), and the
|
|
1578
|
-
// form decompile writes for a renamed one.
|
|
1579
|
-
if (child.trigger !== undefined && child.trigger.kind !== 'manual') {
|
|
1580
|
-
at('SUBFLOW_CHILD_TRIGGER', `Subflow "${step}" runs a flow with its own \`.trigger(…)\`. A subflow is started by its caller, never by a `
|
|
1581
|
-
+ `trigger of its own — the schedule would be dropped. Remove the .trigger() from "${child.id}" `
|
|
1582
|
-
+ `(\`.trigger(manual(), { label })\` is allowed, to name the child's start node).`);
|
|
1583
|
-
}
|
|
1574
|
+
// A child's own `.trigger(…)` or `.entryPoint()` is the serializer's
|
|
1575
|
+
// SUBFLOW_CHILD_TRIGGER / SUBFLOW_CHILD_ENTRY_POINT (`refuseChildRoots`).
|
|
1584
1576
|
// `{ shared }` merges one input across a flow's ROOTS (#924); a subflow has
|
|
1585
1577
|
// one root, its caller: the serializer's SHARED_INPUT_IN_SUBFLOW.
|
|
1586
1578
|
const declared = new Map(child.inputs.map((i) => [i.name, String(i.type)]));
|
|
@@ -17,9 +17,9 @@ export interface AgentInputPlan {
|
|
|
17
17
|
* value is INLINED into the agent.json prompt instead.
|
|
18
18
|
* - `expression` — a computed expression spanning zero or several references.
|
|
19
19
|
* No single JobArgument can exist, so there is no honest name to emit, and
|
|
20
|
-
* the author's placeholder has nothing to point at
|
|
21
|
-
*
|
|
22
|
-
* script step first).
|
|
20
|
+
* the author's placeholder has nothing to point at. When a prompt references
|
|
21
|
+
* one, the inline agent's serialize refuses it (`INLINE_AGENT_COMPOSITE_INPUT`)
|
|
22
|
+
* and names the fix (compute it in a script step first).
|
|
23
23
|
*/
|
|
24
24
|
export type AgentInputBinding = {
|
|
25
25
|
kind: 'ref';
|
|
@@ -129,6 +129,16 @@ export declare function inlineAgentJson(spec: {
|
|
|
129
129
|
/** The agent's display name (the step's label). */
|
|
130
130
|
name: string;
|
|
131
131
|
}): any;
|
|
132
|
+
/**
|
|
133
|
+
* The input names an agent's prompts reference as `{{input.<name>}}`, system
|
|
134
|
+
* prompt first, in order (repeats kept). Both prompts are searched, because
|
|
135
|
+
* flow-v1's pre-pass scans both. For instance `{ systemPrompt: 'Use {{input.a}}',
|
|
136
|
+
* userPrompt: '{{input.b}} and {{input.a}}' }` gives `['a', 'b', 'a']`.
|
|
137
|
+
*/
|
|
138
|
+
export declare function promptPlaceholders(s: {
|
|
139
|
+
systemPrompt?: unknown;
|
|
140
|
+
userPrompt?: unknown;
|
|
141
|
+
}): string[];
|
|
132
142
|
/** Which writer produced an `agent.json`: the inline, conversational or voice agent's. */
|
|
133
143
|
export type AgentSidecarDialect = 'inline' | 'conversational' | 'voice';
|
|
134
144
|
/**
|
|
@@ -263,6 +263,16 @@ export function inlineAgentJson(spec) {
|
|
|
263
263
|
}
|
|
264
264
|
/** `{{input.<name>}}` — the agent-template placeholder, in both prompt directions. */
|
|
265
265
|
const INPUT_PLACEHOLDER = /\{\{\s*input\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\}/g;
|
|
266
|
+
/**
|
|
267
|
+
* The input names an agent's prompts reference as `{{input.<name>}}`, system
|
|
268
|
+
* prompt first, in order (repeats kept). Both prompts are searched, because
|
|
269
|
+
* flow-v1's pre-pass scans both. For instance `{ systemPrompt: 'Use {{input.a}}',
|
|
270
|
+
* userPrompt: '{{input.b}} and {{input.a}}' }` gives `['a', 'b', 'a']`.
|
|
271
|
+
*/
|
|
272
|
+
export function promptPlaceholders(s) {
|
|
273
|
+
const prompts = `${String(s.systemPrompt ?? '')}\n${String(s.userPrompt ?? '')}`;
|
|
274
|
+
return [...prompts.matchAll(INPUT_PLACEHOLDER)].map((m) => m[1]);
|
|
275
|
+
}
|
|
266
276
|
/**
|
|
267
277
|
* Split an agent prompt into the `contentTokens` the designer renders it from.
|
|
268
278
|
*
|
package/dist/core/refusals.d.ts
CHANGED
|
@@ -197,7 +197,7 @@ export declare const REFUSALS: {
|
|
|
197
197
|
readonly DATAFABRIC_NO_ENTITY: "lint";
|
|
198
198
|
readonly DATAFABRIC_QUERY_OPTS_WITHOUT_MULTIPLE: "lint";
|
|
199
199
|
readonly DATAFABRIC_RECORD_REF: "lowering";
|
|
200
|
-
readonly DATAFABRIC_RESOURCE_KEY_NO_FOLDER: "
|
|
200
|
+
readonly DATAFABRIC_RESOURCE_KEY_NO_FOLDER: "lowering";
|
|
201
201
|
readonly DATAFABRIC_RESULT_MODE_VERSION: "lowering";
|
|
202
202
|
readonly DATAFABRIC_SYSTEM_COLUMN: "lint";
|
|
203
203
|
readonly DEAD_END: "lint";
|
|
@@ -212,9 +212,9 @@ export declare const REFUSALS: {
|
|
|
212
212
|
readonly DELAY_NO_DURATION: "lint";
|
|
213
213
|
readonly DELAY_UNTIL_NOT_ISO: "lint";
|
|
214
214
|
readonly DELAY_ZERO: "lint";
|
|
215
|
-
readonly DOCVAL_NO_EXTRACTION: "
|
|
216
|
-
readonly DOCVAL_RENDER_UNKNOWN: "
|
|
217
|
-
readonly DOCVAL_TAXONOMY_REQUIRED: "
|
|
215
|
+
readonly DOCVAL_NO_EXTRACTION: "lowering";
|
|
216
|
+
readonly DOCVAL_RENDER_UNKNOWN: "lowering";
|
|
217
|
+
readonly DOCVAL_TAXONOMY_REQUIRED: "lowering";
|
|
218
218
|
readonly DOC_NO_FILEREF: "lint";
|
|
219
219
|
readonly DOWHILE_CONST_COND: "lint";
|
|
220
220
|
readonly DOWHILE_LIMIT: "lint";
|
|
@@ -303,19 +303,19 @@ export declare const REFUSALS: {
|
|
|
303
303
|
readonly GUARDRAIL_NO_SCOPE: "lint";
|
|
304
304
|
readonly GUARDRAIL_NO_VALIDATOR: "lint";
|
|
305
305
|
readonly GUARDRAIL_TYPE_UNKNOWN: "lint";
|
|
306
|
-
readonly HITL_APP_INCOMPLETE: "
|
|
306
|
+
readonly HITL_APP_INCOMPLETE: "lowering";
|
|
307
307
|
readonly HITL_APP_INPUT_REQUIRED: "lint";
|
|
308
308
|
readonly HITL_APP_INPUT_UNKNOWN: "lint";
|
|
309
|
-
readonly HITL_APP_LOCAL_HAS_FOLDER: "
|
|
309
|
+
readonly HITL_APP_LOCAL_HAS_FOLDER: "lowering";
|
|
310
310
|
readonly HITL_APP_MISSING: "lint";
|
|
311
311
|
readonly HITL_APP_NO_SCHEMA: "lint";
|
|
312
|
-
readonly HITL_APP_OUTCOME_UNKNOWN: "
|
|
313
|
-
readonly HITL_APP_SCHEMA_INVALID: "
|
|
314
|
-
readonly HITL_APP_WITHOUT_VARIANT: "
|
|
312
|
+
readonly HITL_APP_OUTCOME_UNKNOWN: "lowering";
|
|
313
|
+
readonly HITL_APP_SCHEMA_INVALID: "lowering";
|
|
314
|
+
readonly HITL_APP_WITHOUT_VARIANT: "lowering";
|
|
315
315
|
readonly HITL_APP_WITH_FIELDS: "lint";
|
|
316
316
|
readonly HITL_CHANNEL_UNKNOWN: "lint";
|
|
317
317
|
readonly HITL_COMPLETED_PORT_GONE: "lowering";
|
|
318
|
-
readonly HITL_DOCUMENT_WITHOUT_VARIANT: "
|
|
318
|
+
readonly HITL_DOCUMENT_WITHOUT_VARIANT: "lowering";
|
|
319
319
|
readonly HITL_DOCVAL_NO_DOCUMENT: "lint";
|
|
320
320
|
readonly HITL_DOCVAL_WITH_FIELDS: "lint";
|
|
321
321
|
readonly HITL_DUP_FIELD: "lint";
|
|
@@ -325,16 +325,16 @@ export declare const REFUSALS: {
|
|
|
325
325
|
readonly HITL_EXPOSE_ERROR_VERSION: "lowering";
|
|
326
326
|
readonly HITL_FAN_OUT_INTO_JOIN: "lowering";
|
|
327
327
|
readonly HITL_FIELD_DIRECTION: "lint";
|
|
328
|
-
readonly HITL_FIELD_NO_ID: "
|
|
328
|
+
readonly HITL_FIELD_NO_ID: "lowering";
|
|
329
329
|
readonly HITL_FIELD_NO_VALUE: "lint";
|
|
330
330
|
readonly HITL_FIELD_OPTIONS: "lint";
|
|
331
331
|
readonly HITL_FIELD_OPTIONS_IGNORED: "lint";
|
|
332
332
|
readonly HITL_FIELD_RESERVED_ID: "lint";
|
|
333
333
|
readonly HITL_FIELD_SETTING_INVALID: "lint";
|
|
334
334
|
readonly HITL_FIELD_TYPE_UNKNOWN: "lint";
|
|
335
|
-
readonly HITL_FIELD_VALUE_NOT_PATH: "
|
|
336
|
-
readonly HITL_FIELD_VALUE_NOT_REF: "
|
|
337
|
-
readonly HITL_FIELD_VARIABLE: "
|
|
335
|
+
readonly HITL_FIELD_VALUE_NOT_PATH: "lowering";
|
|
336
|
+
readonly HITL_FIELD_VALUE_NOT_REF: "lowering";
|
|
337
|
+
readonly HITL_FIELD_VARIABLE: "lowering";
|
|
338
338
|
readonly HITL_LEGACY_COMPLETED: "lint";
|
|
339
339
|
readonly HITL_LEGACY_COMPLETED_CONFLICT: "lowering";
|
|
340
340
|
readonly HITL_LEGACY_COMPLETED_NO_EFFECT: "lint";
|
|
@@ -343,7 +343,7 @@ export declare const REFUSALS: {
|
|
|
343
343
|
readonly HITL_NO_OUTCOMES: "lint";
|
|
344
344
|
readonly HITL_OUTCOMES_UNREACHABLE: "lint";
|
|
345
345
|
readonly HITL_OUTCOME_ACTION: "lint";
|
|
346
|
-
readonly HITL_OUTCOME_BAD_ID: "
|
|
346
|
+
readonly HITL_OUTCOME_BAD_ID: "lowering";
|
|
347
347
|
readonly HITL_OUTCOME_NO_NAME: "lint";
|
|
348
348
|
readonly HITL_OUTCOME_PORTS_NONE: "lowering";
|
|
349
349
|
readonly HITL_OUTCOME_PORTS_OFF: "lowering";
|
|
@@ -359,7 +359,7 @@ export declare const REFUSALS: {
|
|
|
359
359
|
readonly HITL_RECIPIENT_TYPE: "lint";
|
|
360
360
|
readonly HITL_RECIPIENT_VALUE: "lint";
|
|
361
361
|
readonly HITL_UNREACHABLE_AFTER: "lint";
|
|
362
|
-
readonly HITL_VARIANT_UNKNOWN: "
|
|
362
|
+
readonly HITL_VARIANT_UNKNOWN: "lowering";
|
|
363
363
|
readonly HTTP_BINDING_REQUIRES_MANAGED: "lowering";
|
|
364
364
|
readonly HTTP_BRANCH_CONST: "lint";
|
|
365
365
|
readonly HTTP_BRANCH_DUP: "lint";
|
|
@@ -378,10 +378,10 @@ export declare const REFUSALS: {
|
|
|
378
378
|
readonly HTTP_TIMEOUT_NOT_DURATION: "lint";
|
|
379
379
|
readonly HTTP_V1_RETIRED: "lint";
|
|
380
380
|
readonly IMPLICIT_SPLIT: "lint";
|
|
381
|
-
readonly INLINE_AGENT_COMPOSITE_INPUT: "
|
|
381
|
+
readonly INLINE_AGENT_COMPOSITE_INPUT: "lowering";
|
|
382
382
|
readonly INLINE_AGENT_EMPTY_RETURNS: "lint";
|
|
383
383
|
readonly INLINE_AGENT_ESCALATION_APP_KEY_NOT_GUID: "lint";
|
|
384
|
-
readonly INLINE_AGENT_ESCALATION_APP_WITH_FIELDS: "
|
|
384
|
+
readonly INLINE_AGENT_ESCALATION_APP_WITH_FIELDS: "lowering";
|
|
385
385
|
readonly INLINE_AGENT_ESCALATION_FIELD_OPTIONS: "lint";
|
|
386
386
|
readonly INLINE_AGENT_ESCALATION_FIELD_OPTIONS_IGNORED: "lint";
|
|
387
387
|
readonly INLINE_AGENT_ESCALATION_FIELD_SETTING_INVALID: "lint";
|
|
@@ -389,8 +389,8 @@ export declare const REFUSALS: {
|
|
|
389
389
|
readonly INLINE_AGENT_ESCALATION_NO_APP: "lint";
|
|
390
390
|
readonly INLINE_AGENT_ESCALATION_NO_FIELDS: "lint";
|
|
391
391
|
readonly INLINE_AGENT_ESCALATION_NO_NAME: "lint";
|
|
392
|
-
readonly INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP: "
|
|
393
|
-
readonly INLINE_AGENT_ESCALATION_VARIANT: "
|
|
392
|
+
readonly INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP: "lowering";
|
|
393
|
+
readonly INLINE_AGENT_ESCALATION_VARIANT: "lowering";
|
|
394
394
|
readonly INLINE_AGENT_INPUT_NAME_AMBIGUOUS: "lint";
|
|
395
395
|
readonly INLINE_AGENT_MEMORY_ID_NOT_GUID: "lint";
|
|
396
396
|
readonly INLINE_AGENT_MEMORY_INCOMPLETE: "lint";
|
|
@@ -430,7 +430,7 @@ export declare const REFUSALS: {
|
|
|
430
430
|
readonly INTSVC_OPERATION_NOT_IDENTIFIER: "lint";
|
|
431
431
|
readonly INTSVC_PATH_NOT_ABSOLUTE: "lint";
|
|
432
432
|
readonly INTSVC_PATH_PARAM_UNBOUND: "lint";
|
|
433
|
-
readonly IXP_FORBIDDEN_INPUT: "
|
|
433
|
+
readonly IXP_FORBIDDEN_INPUT: "lowering";
|
|
434
434
|
readonly IXP_NO_FILE: "lint";
|
|
435
435
|
readonly IXP_NO_FOLDER: "lint";
|
|
436
436
|
readonly IXP_NO_MODEL_NAME: "lint";
|
|
@@ -602,7 +602,8 @@ export declare const REFUSALS: {
|
|
|
602
602
|
readonly STICKY_NOTE_NO_CONTENT: "lowering";
|
|
603
603
|
readonly STICKY_NOTE_POSITION: "lowering";
|
|
604
604
|
readonly STICKY_NOTE_SIZE: "lowering";
|
|
605
|
-
readonly
|
|
605
|
+
readonly SUBFLOW_CHILD_ENTRY_POINT: "lowering";
|
|
606
|
+
readonly SUBFLOW_CHILD_TRIGGER: "lowering";
|
|
606
607
|
readonly SUBFLOW_CYCLE: "lint";
|
|
607
608
|
readonly SUBFLOW_HAS_NO_OUTPUT: "lint";
|
|
608
609
|
readonly SUBFLOW_MISSING_INPUT: "lint";
|
package/dist/core/refusals.js
CHANGED
|
@@ -238,7 +238,7 @@ export const REFUSALS = {
|
|
|
238
238
|
DATAFABRIC_NO_ENTITY: 'lint',
|
|
239
239
|
DATAFABRIC_QUERY_OPTS_WITHOUT_MULTIPLE: 'lint',
|
|
240
240
|
DATAFABRIC_RECORD_REF: 'lowering',
|
|
241
|
-
DATAFABRIC_RESOURCE_KEY_NO_FOLDER: '
|
|
241
|
+
DATAFABRIC_RESOURCE_KEY_NO_FOLDER: 'lowering',
|
|
242
242
|
DATAFABRIC_RESULT_MODE_VERSION: 'lowering',
|
|
243
243
|
DATAFABRIC_SYSTEM_COLUMN: 'lint',
|
|
244
244
|
DEAD_END: 'lint',
|
|
@@ -253,9 +253,9 @@ export const REFUSALS = {
|
|
|
253
253
|
DELAY_NO_DURATION: 'lint',
|
|
254
254
|
DELAY_UNTIL_NOT_ISO: 'lint',
|
|
255
255
|
DELAY_ZERO: 'lint',
|
|
256
|
-
DOCVAL_NO_EXTRACTION: '
|
|
257
|
-
DOCVAL_RENDER_UNKNOWN: '
|
|
258
|
-
DOCVAL_TAXONOMY_REQUIRED: '
|
|
256
|
+
DOCVAL_NO_EXTRACTION: 'lowering',
|
|
257
|
+
DOCVAL_RENDER_UNKNOWN: 'lowering',
|
|
258
|
+
DOCVAL_TAXONOMY_REQUIRED: 'lowering',
|
|
259
259
|
DOC_NO_FILEREF: 'lint',
|
|
260
260
|
DOWHILE_CONST_COND: 'lint',
|
|
261
261
|
DOWHILE_LIMIT: 'lint',
|
|
@@ -344,19 +344,19 @@ export const REFUSALS = {
|
|
|
344
344
|
GUARDRAIL_NO_SCOPE: 'lint',
|
|
345
345
|
GUARDRAIL_NO_VALIDATOR: 'lint',
|
|
346
346
|
GUARDRAIL_TYPE_UNKNOWN: 'lint',
|
|
347
|
-
HITL_APP_INCOMPLETE: '
|
|
347
|
+
HITL_APP_INCOMPLETE: 'lowering',
|
|
348
348
|
HITL_APP_INPUT_REQUIRED: 'lint',
|
|
349
349
|
HITL_APP_INPUT_UNKNOWN: 'lint',
|
|
350
|
-
HITL_APP_LOCAL_HAS_FOLDER: '
|
|
350
|
+
HITL_APP_LOCAL_HAS_FOLDER: 'lowering',
|
|
351
351
|
HITL_APP_MISSING: 'lint',
|
|
352
352
|
HITL_APP_NO_SCHEMA: 'lint',
|
|
353
|
-
HITL_APP_OUTCOME_UNKNOWN: '
|
|
354
|
-
HITL_APP_SCHEMA_INVALID: '
|
|
355
|
-
HITL_APP_WITHOUT_VARIANT: '
|
|
353
|
+
HITL_APP_OUTCOME_UNKNOWN: 'lowering',
|
|
354
|
+
HITL_APP_SCHEMA_INVALID: 'lowering',
|
|
355
|
+
HITL_APP_WITHOUT_VARIANT: 'lowering',
|
|
356
356
|
HITL_APP_WITH_FIELDS: 'lint',
|
|
357
357
|
HITL_CHANNEL_UNKNOWN: 'lint',
|
|
358
358
|
HITL_COMPLETED_PORT_GONE: 'lowering',
|
|
359
|
-
HITL_DOCUMENT_WITHOUT_VARIANT: '
|
|
359
|
+
HITL_DOCUMENT_WITHOUT_VARIANT: 'lowering',
|
|
360
360
|
HITL_DOCVAL_NO_DOCUMENT: 'lint',
|
|
361
361
|
HITL_DOCVAL_WITH_FIELDS: 'lint',
|
|
362
362
|
HITL_DUP_FIELD: 'lint',
|
|
@@ -366,16 +366,16 @@ export const REFUSALS = {
|
|
|
366
366
|
HITL_EXPOSE_ERROR_VERSION: 'lowering',
|
|
367
367
|
HITL_FAN_OUT_INTO_JOIN: 'lowering',
|
|
368
368
|
HITL_FIELD_DIRECTION: 'lint',
|
|
369
|
-
HITL_FIELD_NO_ID: '
|
|
369
|
+
HITL_FIELD_NO_ID: 'lowering',
|
|
370
370
|
HITL_FIELD_NO_VALUE: 'lint',
|
|
371
371
|
HITL_FIELD_OPTIONS: 'lint',
|
|
372
372
|
HITL_FIELD_OPTIONS_IGNORED: 'lint',
|
|
373
373
|
HITL_FIELD_RESERVED_ID: 'lint',
|
|
374
374
|
HITL_FIELD_SETTING_INVALID: 'lint',
|
|
375
375
|
HITL_FIELD_TYPE_UNKNOWN: 'lint',
|
|
376
|
-
HITL_FIELD_VALUE_NOT_PATH: '
|
|
377
|
-
HITL_FIELD_VALUE_NOT_REF: '
|
|
378
|
-
HITL_FIELD_VARIABLE: '
|
|
376
|
+
HITL_FIELD_VALUE_NOT_PATH: 'lowering',
|
|
377
|
+
HITL_FIELD_VALUE_NOT_REF: 'lowering',
|
|
378
|
+
HITL_FIELD_VARIABLE: 'lowering',
|
|
379
379
|
HITL_LEGACY_COMPLETED: 'lint',
|
|
380
380
|
HITL_LEGACY_COMPLETED_CONFLICT: 'lowering',
|
|
381
381
|
HITL_LEGACY_COMPLETED_NO_EFFECT: 'lint',
|
|
@@ -384,7 +384,7 @@ export const REFUSALS = {
|
|
|
384
384
|
HITL_NO_OUTCOMES: 'lint',
|
|
385
385
|
HITL_OUTCOMES_UNREACHABLE: 'lint',
|
|
386
386
|
HITL_OUTCOME_ACTION: 'lint',
|
|
387
|
-
HITL_OUTCOME_BAD_ID: '
|
|
387
|
+
HITL_OUTCOME_BAD_ID: 'lowering',
|
|
388
388
|
HITL_OUTCOME_NO_NAME: 'lint',
|
|
389
389
|
HITL_OUTCOME_PORTS_NONE: 'lowering',
|
|
390
390
|
HITL_OUTCOME_PORTS_OFF: 'lowering',
|
|
@@ -400,7 +400,7 @@ export const REFUSALS = {
|
|
|
400
400
|
HITL_RECIPIENT_TYPE: 'lint',
|
|
401
401
|
HITL_RECIPIENT_VALUE: 'lint',
|
|
402
402
|
HITL_UNREACHABLE_AFTER: 'lint',
|
|
403
|
-
HITL_VARIANT_UNKNOWN: '
|
|
403
|
+
HITL_VARIANT_UNKNOWN: 'lowering',
|
|
404
404
|
HTTP_BINDING_REQUIRES_MANAGED: 'lowering',
|
|
405
405
|
HTTP_BRANCH_CONST: 'lint',
|
|
406
406
|
HTTP_BRANCH_DUP: 'lint',
|
|
@@ -419,10 +419,10 @@ export const REFUSALS = {
|
|
|
419
419
|
HTTP_TIMEOUT_NOT_DURATION: 'lint',
|
|
420
420
|
HTTP_V1_RETIRED: 'lint',
|
|
421
421
|
IMPLICIT_SPLIT: 'lint',
|
|
422
|
-
INLINE_AGENT_COMPOSITE_INPUT: '
|
|
422
|
+
INLINE_AGENT_COMPOSITE_INPUT: 'lowering',
|
|
423
423
|
INLINE_AGENT_EMPTY_RETURNS: 'lint',
|
|
424
424
|
INLINE_AGENT_ESCALATION_APP_KEY_NOT_GUID: 'lint',
|
|
425
|
-
INLINE_AGENT_ESCALATION_APP_WITH_FIELDS: '
|
|
425
|
+
INLINE_AGENT_ESCALATION_APP_WITH_FIELDS: 'lowering',
|
|
426
426
|
INLINE_AGENT_ESCALATION_FIELD_OPTIONS: 'lint',
|
|
427
427
|
INLINE_AGENT_ESCALATION_FIELD_OPTIONS_IGNORED: 'lint',
|
|
428
428
|
INLINE_AGENT_ESCALATION_FIELD_SETTING_INVALID: 'lint',
|
|
@@ -430,8 +430,8 @@ export const REFUSALS = {
|
|
|
430
430
|
INLINE_AGENT_ESCALATION_NO_APP: 'lint',
|
|
431
431
|
INLINE_AGENT_ESCALATION_NO_FIELDS: 'lint',
|
|
432
432
|
INLINE_AGENT_ESCALATION_NO_NAME: 'lint',
|
|
433
|
-
INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP: '
|
|
434
|
-
INLINE_AGENT_ESCALATION_VARIANT: '
|
|
433
|
+
INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP: 'lowering',
|
|
434
|
+
INLINE_AGENT_ESCALATION_VARIANT: 'lowering',
|
|
435
435
|
INLINE_AGENT_INPUT_NAME_AMBIGUOUS: 'lint',
|
|
436
436
|
INLINE_AGENT_MEMORY_ID_NOT_GUID: 'lint',
|
|
437
437
|
INLINE_AGENT_MEMORY_INCOMPLETE: 'lint',
|
|
@@ -471,7 +471,7 @@ export const REFUSALS = {
|
|
|
471
471
|
INTSVC_OPERATION_NOT_IDENTIFIER: 'lint',
|
|
472
472
|
INTSVC_PATH_NOT_ABSOLUTE: 'lint',
|
|
473
473
|
INTSVC_PATH_PARAM_UNBOUND: 'lint',
|
|
474
|
-
IXP_FORBIDDEN_INPUT: '
|
|
474
|
+
IXP_FORBIDDEN_INPUT: 'lowering',
|
|
475
475
|
IXP_NO_FILE: 'lint',
|
|
476
476
|
IXP_NO_FOLDER: 'lint',
|
|
477
477
|
IXP_NO_MODEL_NAME: 'lint',
|
|
@@ -643,7 +643,8 @@ export const REFUSALS = {
|
|
|
643
643
|
STICKY_NOTE_NO_CONTENT: 'lowering',
|
|
644
644
|
STICKY_NOTE_POSITION: 'lowering',
|
|
645
645
|
STICKY_NOTE_SIZE: 'lowering',
|
|
646
|
-
|
|
646
|
+
SUBFLOW_CHILD_ENTRY_POINT: 'lowering',
|
|
647
|
+
SUBFLOW_CHILD_TRIGGER: 'lowering',
|
|
647
648
|
SUBFLOW_CYCLE: 'lint',
|
|
648
649
|
SUBFLOW_HAS_NO_OUTPUT: 'lint',
|
|
649
650
|
SUBFLOW_MISSING_INPUT: 'lint',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Expr } from '../../../core/expr.js';
|
|
2
2
|
import { GUID_RE } from '../../../core/binding-messages.js';
|
|
3
|
+
import { promptPlaceholders } from '../../../core/agent-sidecar.js';
|
|
3
4
|
import { prepareCommand } from '../../../core/cli-spelling.js';
|
|
4
5
|
import { nearestName } from '../../../core/edit-distance.js';
|
|
5
|
-
import { FLAT_SEP,
|
|
6
|
+
import { FLAT_SEP, flatNameSegmentsOf } from '../../../core/expr.js';
|
|
6
7
|
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText, } from '../../../core/form-field-type.js';
|
|
7
8
|
import { ownEntry } from '../../../core/own-entry.js';
|
|
8
9
|
export const AGENT_DIAGNOSTICS = {
|
|
@@ -473,9 +474,7 @@ export function checkInlineAgent(inputs, ctx) {
|
|
|
473
474
|
}
|
|
474
475
|
// The prompts-vs-variables agreement, in both directions. Both prompts are
|
|
475
476
|
// searched, because flow-v1's pre-pass scans both.
|
|
476
|
-
const
|
|
477
|
-
const placeholders = [...prompts.matchAll(/\{\{\s*input\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\}/g)]
|
|
478
|
-
.map((m) => m[1]);
|
|
477
|
+
const placeholders = promptPlaceholders(inputs ?? {});
|
|
479
478
|
const declaredInputs = Object.keys(inputs?.inputs ?? {});
|
|
480
479
|
// Direction 1: a declared input no prompt references. The platform may prune it,
|
|
481
480
|
// in which case the agent receives nothing and the authored list sits unused.
|
|
@@ -508,30 +507,10 @@ export function checkInlineAgent(inputs, ctx) {
|
|
|
508
507
|
? `inputs: { ${undeclared[0]}: input('${undeclared[0]}'), … }`
|
|
509
508
|
: `inputs: { ${undeclared[0]}: input('${undeclared[0]}') }`);
|
|
510
509
|
}
|
|
511
|
-
// Direction 3
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
// and therefore no name a `{{input.<x>}}` token could address. The compiler
|
|
516
|
-
// declares no argument for it, so its value does not reach the agent, and the
|
|
517
|
-
// placeholder reaches the model as literal text. An error rather than a
|
|
518
|
-
// warning, and for the same reason `INLINE_AGENT_UNDECLARED_PLACEHOLDER` is
|
|
519
|
-
// one: the value never arrives.
|
|
520
|
-
for (const [name, val] of Object.entries(inputs?.inputs ?? {})) {
|
|
521
|
-
if (!(val instanceof Expr) || val.literal)
|
|
522
|
-
continue;
|
|
523
|
-
if (flatNameOf(val.js) !== undefined)
|
|
524
|
-
continue;
|
|
525
|
-
if (!placeholders.includes(name))
|
|
526
|
-
continue;
|
|
527
|
-
at('INLINE_AGENT_COMPOSITE_INPUT', `Inline agent step "${step}"'s input \`${name}\` is bound to a computed expression `
|
|
528
|
-
+ `(\`${val.js.length > 60 ? `${val.js.slice(0, 60)}…` : val.js}\`), and a prompt references it as `
|
|
529
|
-
+ `\`{{input.${name}}}\`. The platform does not send the agent the names you declared: it names each `
|
|
530
|
-
+ `argument after the REFERENCE PATH the input is bound to (\`$vars.a.output.b\` → \`a__output__b\`). `
|
|
531
|
-
+ `A computed expression is not one reference, so it gets no argument: its value does not reach the `
|
|
532
|
-
+ `agent, and the placeholder reaches the model as that literal text.`, `compute it in a script step first, then bind the step's output: `
|
|
533
|
-
+ `.step('${name}Value', script({ code: 'return …;' })) … inputs: { ${name}: out('${name}Value') }`);
|
|
534
|
-
}
|
|
510
|
+
// Direction 3, an input bound to a COMPUTED EXPRESSION that a prompt
|
|
511
|
+
// references, is the serializer's INLINE_AGENT_COMPOSITE_INPUT
|
|
512
|
+
// (`compositeInputRefusal`, inline-agent.ts): the binding has no argument
|
|
513
|
+
// name, so writing the flow would drop it.
|
|
535
514
|
// A step id containing `__` makes the flat name AMBIGUOUS on the platform: the
|
|
536
515
|
// encoding joins path segments with `__` and v1's own inverse (`decodeFlatName`)
|
|
537
516
|
// documents that it round-trips only while ids "disallow `__`". A warning, not
|
|
@@ -694,18 +673,15 @@ export function checkInlineAgent(inputs, ctx) {
|
|
|
694
673
|
at('INLINE_AGENT_ESCALATION_NO_NAME', `Inline agent step "${step}" declares an escalation without a \`name\`. The node's definition requires one `
|
|
695
674
|
+ `(\`minLength: 1\`).`);
|
|
696
675
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
676
|
+
// An unknown variant, a quick form naming an app, and an app task with
|
|
677
|
+
// inline fields are the serializer's INLINE_AGENT_ESCALATION_VARIANT /
|
|
678
|
+
// _QUICKFORM_WITH_APP / _APP_WITH_FIELDS (`refuseLossyEscalation`,
|
|
679
|
+
// inline-agent.ts): the emitter would rewrite or drop them. An unknown
|
|
680
|
+
// variant has no rules of its own to check here.
|
|
681
|
+
if (e?.variant !== undefined && e.variant !== 'quick-form')
|
|
700
682
|
continue;
|
|
701
|
-
}
|
|
702
683
|
if (e?.variant === 'quick-form') {
|
|
703
|
-
//
|
|
704
|
-
// with nothing to fill in is a notification nobody can answer.
|
|
705
|
-
if (e.app !== undefined) {
|
|
706
|
-
at('INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP', `Inline agent step "${step}"'s quick-form escalation also names an app. The form lives in the flow — `
|
|
707
|
-
+ `drop \`app\`, or drop the variant for the app-backed escalation.`);
|
|
708
|
-
}
|
|
684
|
+
// A form with nothing to fill in is a notification nobody can answer.
|
|
709
685
|
if (!Array.isArray(e.fields) || e.fields.length === 0) {
|
|
710
686
|
at('INLINE_AGENT_ESCALATION_NO_FIELDS', `Inline agent step "${step}"'s quick-form escalation declares no \`fields\` — the reviewer gets an `
|
|
711
687
|
+ `empty form.`);
|
|
@@ -733,10 +709,6 @@ export function checkInlineAgent(inputs, ctx) {
|
|
|
733
709
|
}
|
|
734
710
|
continue;
|
|
735
711
|
}
|
|
736
|
-
if (e?.fields !== undefined) {
|
|
737
|
-
at('INLINE_AGENT_ESCALATION_APP_WITH_FIELDS', `Inline agent step "${step}"'s escalation declares inline \`fields\`, but its form belongs to the deployed `
|
|
738
|
-
+ `app. Use variant: 'quick-form' for an inline form.`);
|
|
739
|
-
}
|
|
740
712
|
if (blank(e?.app?.key) || blank(e?.app?.name)) {
|
|
741
713
|
at('INLINE_AGENT_ESCALATION_NO_APP', `Inline agent step "${step}"'s escalation needs \`app.key\` and \`app.name\` — the deployed Action Center app `
|
|
742
714
|
+ `that carries the review task. An escalation with no app is a task nobody can open.`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { agentInputPlan, inlineAgentJson } from '../../../core/agent-sidecar.js';
|
|
1
|
+
import { agentInputPlan, inlineAgentJson, promptPlaceholders } from '../../../core/agent-sidecar.js';
|
|
2
|
+
import { Expr } from '../../../core/expr.js';
|
|
2
3
|
import { derivedContextSource, derivedEscalationSource, derivedToolSource } from '../../../core/agent-resource-identity.js';
|
|
3
4
|
import { GUID_RE } from '../../../core/binding-messages.js';
|
|
4
5
|
import { formFieldSettingsWire, wireFormFieldType } from '../../../core/form-field-type.js';
|
|
@@ -68,6 +69,10 @@ export function serializeInlineAgent(s, ctx) {
|
|
|
68
69
|
// What the PLATFORM will call each of these — computed once and shared by the
|
|
69
70
|
// descriptor array below and the sidecar. See `agentInputPlan`.
|
|
70
71
|
const plan = agentInputPlan(s.inputs ?? {}, render);
|
|
72
|
+
// A composite input is held, not thrown here: the agent's tools, context,
|
|
73
|
+
// escalations and memory are still judged, so `check` (one refusal per
|
|
74
|
+
// step) shows their errors in the same round. Thrown at the end.
|
|
75
|
+
const compositeInput = compositeInputRefusal(s, plan.byName, step);
|
|
71
76
|
// A SHELL node (#927): the prompts and the model live only in the
|
|
72
77
|
// `<source>/agent.json` sidecar written below, never on the node. Studio Web
|
|
73
78
|
// decides which copy wins by `hasEmbeddedAgentContent` (@uipath/flow-schema):
|
|
@@ -406,6 +411,7 @@ export function serializeInlineAgent(s, ctx) {
|
|
|
406
411
|
// the author's values — the exact shape the deployed corpus carries.
|
|
407
412
|
const escalations = s.escalation === undefined ? [] : Array.isArray(s.escalation) ? s.escalation : [s.escalation];
|
|
408
413
|
for (const esc of escalations) {
|
|
414
|
+
refuseLossyEscalation(esc, step);
|
|
409
415
|
const where = `inline agent "${step}": escalation "${esc.name}"`;
|
|
410
416
|
const channelId = fnv1aX4Uuid(`${ctx.flowId}/${step}/escalation/${esc.name}/channel`);
|
|
411
417
|
if (esc.variant === 'quick-form') {
|
|
@@ -530,6 +536,8 @@ export function serializeInlineAgent(s, ctx) {
|
|
|
530
536
|
// (AGENT_MEMORY_NOT_AUTHORABLE); its shape maps cleanly off `AgentMemoryRef`
|
|
531
537
|
// but cannot be exercised while the handle does not exist.
|
|
532
538
|
}
|
|
539
|
+
if (compositeInput)
|
|
540
|
+
throw compositeInput;
|
|
533
541
|
return {
|
|
534
542
|
definition: { source: 'snapshot', nodeType: def.nodeType, version: def.version, manifest: def },
|
|
535
543
|
// Unlike the published families the inline agent KEEPS no node-level model:
|
|
@@ -552,3 +560,56 @@ function unknownToolKind(step, kind) {
|
|
|
552
560
|
+ "kinds: 'builtin', 'connector', 'process', 'agent', 'api', 'flow', 'maestro', 'function', 'ixp', 'mcp', 'a2a', "
|
|
553
561
|
+ "'clientside', 'httpRequest'.");
|
|
554
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* The refusal for an input bound to a COMPUTED EXPRESSION that a prompt references as
|
|
565
|
+
* `{{input.<name>}}` (#1518). The platform names each agent argument after the
|
|
566
|
+
* reference path it is bound to (`agentInputPlan`), so an expression spanning
|
|
567
|
+
* zero or several references gets no argument: writing the flow anyway would
|
|
568
|
+
* drop the binding from `agentInputVariables` and `inputSchema`, and the
|
|
569
|
+
* placeholder would reach the model as literal text.
|
|
570
|
+
*
|
|
571
|
+
* For instance `inputs: { body: js\`${input('body')} + '!'\` }` with the prompt
|
|
572
|
+
* `Reply to {{input.body}}` is refused; bind a script step's output instead.
|
|
573
|
+
* An unreferenced one is `check`'s INLINE_AGENT_UNREFERENCED_INPUT warning.
|
|
574
|
+
*/
|
|
575
|
+
function compositeInputRefusal(s, byName, step) {
|
|
576
|
+
const placeholders = new Set(promptPlaceholders(s));
|
|
577
|
+
for (const [name, val] of Object.entries(s.inputs ?? {})) {
|
|
578
|
+
if (byName.get(name)?.kind !== 'expression' || !placeholders.has(name))
|
|
579
|
+
continue;
|
|
580
|
+
const js = val instanceof Expr ? val.js : String(val).replace(/^=js:/, '');
|
|
581
|
+
return new CompileRefusal('INLINE_AGENT_COMPOSITE_INPUT', `Inline agent step "${step}"'s input \`${name}\` is bound to a computed expression `
|
|
582
|
+
+ `(\`${js.length > 60 ? `${js.slice(0, 60)}…` : js}\`), and a prompt references it as `
|
|
583
|
+
+ `\`{{input.${name}}}\`. The platform does not send the agent the names you declared: it names each `
|
|
584
|
+
+ `argument after the REFERENCE PATH the input is bound to (\`$vars.a.output.b\` → \`a__output__b\`). `
|
|
585
|
+
+ `A computed expression is not one reference, so it gets no argument: its value does not reach the `
|
|
586
|
+
+ `agent, and the placeholder reaches the model as that literal text.`, `compute it in a script step first, then bind the step's output: `
|
|
587
|
+
+ `.step('${name}Value', script({ code: 'return …;' })) … inputs: { ${name}: out('${name}Value') }`);
|
|
588
|
+
}
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Refuse an escalation the emitter below would rewrite or strip (#1518): it
|
|
593
|
+
* writes `'quick-form'` as the inline form and everything else as an app task,
|
|
594
|
+
* so an unknown variant would become an app task, a quick form's \`app\` would
|
|
595
|
+
* be dropped, and an app task's inline \`fields\` would be dropped.
|
|
596
|
+
*/
|
|
597
|
+
function refuseLossyEscalation(e, step) {
|
|
598
|
+
const esc = e;
|
|
599
|
+
if (esc.variant !== undefined && esc.variant !== 'quick-form') {
|
|
600
|
+
throw new CompileRefusal('INLINE_AGENT_ESCALATION_VARIANT', `Inline agent step "${step}"'s escalation names the variant ${JSON.stringify(esc.variant)}. Valid: `
|
|
601
|
+
+ `'quick-form' — or omit it for the app-backed default.`);
|
|
602
|
+
}
|
|
603
|
+
if (esc.variant === 'quick-form') {
|
|
604
|
+
// The form lives INLINE: an app would be emitted nowhere.
|
|
605
|
+
if (esc.app !== undefined) {
|
|
606
|
+
throw new CompileRefusal('INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP', `Inline agent step "${step}"'s quick-form escalation also names an app. The form lives in the flow — `
|
|
607
|
+
+ `drop \`app\`, or drop the variant for the app-backed escalation.`);
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (esc.fields !== undefined) {
|
|
612
|
+
throw new CompileRefusal('INLINE_AGENT_ESCALATION_APP_WITH_FIELDS', `Inline agent step "${step}"'s escalation declares inline \`fields\`, but its form belongs to the deployed `
|
|
613
|
+
+ `app. Use variant: 'quick-form' for an inline form.`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
@@ -114,7 +114,7 @@ function serializeVoiceAgent(s, ctx) {
|
|
|
114
114
|
const json = voiceAgentJson({
|
|
115
115
|
systemPrompt: s.systemPrompt, plan, voice: s.voice, maxIterations: s.maxIterations, source, name: ctx.step,
|
|
116
116
|
});
|
|
117
|
-
ctx.sidecar({ path: `${source}/agent.json`, json });
|
|
117
|
+
ctx.sidecar({ path: `${source}/agent.json`, json }, s.source !== undefined ? `voice agent step "${ctx.step}"` : undefined);
|
|
118
118
|
if (plan.refs.length > 0)
|
|
119
119
|
ctx.typeAgentInputs(json.inputSchema, plan.refs);
|
|
120
120
|
// The resource model lives on the definition (its `model.source: true` is
|
|
@@ -169,7 +169,7 @@ function serializeConversationalAgent(s, ctx) {
|
|
|
169
169
|
maxTokenPerResponse: s.maxTokenPerResponse, maxIterations: s.maxIterations,
|
|
170
170
|
guardrails: s.guardrails, source, name: ctx.step,
|
|
171
171
|
}),
|
|
172
|
-
});
|
|
172
|
+
}, s.source !== undefined ? `conversational agent step "${ctx.step}"` : undefined);
|
|
173
173
|
// A SHELL node, like the inline agent's (#927): exactly the keys
|
|
174
174
|
// `toAgentShellInputs` keeps. The system prompt, the model and the run
|
|
175
175
|
// settings live only in the sidecar (`metadata.isConversational` there is the
|
|
@@ -250,7 +250,7 @@ export interface InlineAgentInputs {
|
|
|
250
250
|
*
|
|
251
251
|
* - a **literal** (`{ tier: 'Enterprise' }`) is INLINED into both prompts — there
|
|
252
252
|
* is no argument for one, so a token could never substitute;
|
|
253
|
-
* - a **computed expression** is refused by `
|
|
253
|
+
* - a **computed expression** a prompt references is refused by `compile`
|
|
254
254
|
* (`INLINE_AGENT_COMPOSITE_INPUT`): the platform names one argument per
|
|
255
255
|
* reference, so an expression spanning several has none. Compute it in a
|
|
256
256
|
* `script` step first and bind that step's output.
|
|
@@ -91,10 +91,8 @@ function checkScope(inputs, ctx) {
|
|
|
91
91
|
+ 'breaks on deploy to another one.', 'resolve the key with `uip solution resources list --kind Entity`, or drop { folderKey } and keep '
|
|
92
92
|
+ 'the entity tenant-scoped');
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
+ 'tenant-scoped entity needs neither and serializes as a dotted literal.');
|
|
97
|
-
}
|
|
94
|
+
// A resourceKey without a folderKey is the serializer's
|
|
95
|
+
// DATAFABRIC_RESOURCE_KEY_NO_FOLDER (`applyScope`): it would be dropped.
|
|
98
96
|
}
|
|
99
97
|
/**
|
|
100
98
|
* The query options, none of which the node's `inputDefinition` declares — they
|
|
@@ -47,10 +47,15 @@ export declare function fieldRows(values: Record<string, string | Expr> | undefi
|
|
|
47
47
|
export declare function filterRows(filters: ReadonlyArray<DataFabricFilter>, ctx: FlowSerializeContext): Array<Record<string, unknown>>;
|
|
48
48
|
/**
|
|
49
49
|
* The folder scope, last in `entityConfig`. A folder-scoped entity is portable
|
|
50
|
-
* only with its `Entity` bindings
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* only with its `Entity` bindings, so both keys means emit both. Same id for
|
|
51
|
+
* the same (entity, resourceKey), so two steps on one entity share one pair
|
|
52
|
+
* rather than duplicating it.
|
|
53
|
+
*
|
|
54
|
+
* A `resourceKey` alone has nowhere to go: `_resourceKey` and the bindings are
|
|
55
|
+
* written only with a folder, so it would be dropped from the `.flow` without a
|
|
56
|
+
* word. `compile` does not run `check` (#1497), so it is refused here (#1518).
|
|
57
|
+
* A `folderKey` alone is written (`_folderKey`, a source-org literal), so its
|
|
58
|
+
* half (DATAFABRIC_FOLDER_NO_RESOURCE_KEY) stays `check`'s.
|
|
54
59
|
*/
|
|
55
60
|
export declare function applyScope(cfg: EntityConfig, inputs: ScopeInputs, ctx: FlowSerializeContext): void;
|
|
56
61
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* decision came with the code.
|
|
9
9
|
*/
|
|
10
10
|
import { Expr } from '../../../core/expr.js';
|
|
11
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
11
12
|
export function entityConfigOf(node) {
|
|
12
13
|
const inputs = node?.inputs ?? {};
|
|
13
14
|
return (inputs.entityConfig ?? {});
|
|
@@ -63,12 +64,21 @@ export function filterRows(filters, ctx) {
|
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
66
|
* The folder scope, last in `entityConfig`. A folder-scoped entity is portable
|
|
66
|
-
* only with its `Entity` bindings
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* only with its `Entity` bindings, so both keys means emit both. Same id for
|
|
68
|
+
* the same (entity, resourceKey), so two steps on one entity share one pair
|
|
69
|
+
* rather than duplicating it.
|
|
70
|
+
*
|
|
71
|
+
* A `resourceKey` alone has nowhere to go: `_resourceKey` and the bindings are
|
|
72
|
+
* written only with a folder, so it would be dropped from the `.flow` without a
|
|
73
|
+
* word. `compile` does not run `check` (#1497), so it is refused here (#1518).
|
|
74
|
+
* A `folderKey` alone is written (`_folderKey`, a source-org literal), so its
|
|
75
|
+
* half (DATAFABRIC_FOLDER_NO_RESOURCE_KEY) stays `check`'s.
|
|
70
76
|
*/
|
|
71
77
|
export function applyScope(cfg, inputs, ctx) {
|
|
78
|
+
if (inputs.resourceKey !== undefined && inputs.folderKey === undefined) {
|
|
79
|
+
throw new CompileRefusal('DATAFABRIC_RESOURCE_KEY_NO_FOLDER', `"${ctx.step}" sets { resourceKey } without { folderKey }, and compile writes the resource key only with a folder key. `
|
|
80
|
+
+ 'Add { folderKey } to scope a folder entity, or drop { resourceKey } for a tenant-scoped one.', "folderKey: '<folder key>'");
|
|
81
|
+
}
|
|
72
82
|
if (inputs.folderKey !== undefined)
|
|
73
83
|
cfg._folderKey = inputs.folderKey;
|
|
74
84
|
if (inputs.folderKey !== undefined && inputs.resourceKey !== undefined) {
|
|
@@ -216,10 +216,27 @@ function ixpDeploymentBindings(spec, existing) {
|
|
|
216
216
|
* definition does not have.
|
|
217
217
|
*
|
|
218
218
|
* They are not reachable through `ixpExtract()`'s typed surface, which is the
|
|
219
|
-
* point of having one; this
|
|
219
|
+
* point of having one; this refusal exists for the JS caller and for a
|
|
220
220
|
* hand-written spec object, where TypeScript is not in the way.
|
|
221
221
|
*/
|
|
222
222
|
const IXP_FORBIDDEN_INPUTS = ['digitizationMode', 'documentTaxonomy', 'attachmentId', 'fileName', 'mimeType'];
|
|
223
|
+
/**
|
|
224
|
+
* Refuse an input of the prior schema (IXP_FORBIDDEN_INPUT). The node's inputs
|
|
225
|
+
* are the definition's `inputDefaults` plus `fileRef` and `pageRange`, so
|
|
226
|
+
* writing the step anyway would silently drop the field (#1518). One refusal
|
|
227
|
+
* per step names every such field, each with where its value went.
|
|
228
|
+
*/
|
|
229
|
+
function refuseForbiddenInputs(s, step) {
|
|
230
|
+
const set = IXP_FORBIDDEN_INPUTS.filter((f) => s[f] !== undefined);
|
|
231
|
+
if (set.length === 0)
|
|
232
|
+
return;
|
|
233
|
+
const why = (f) => `\`${f}\`, which the current node does not declare — it belonged to a prior schema. `
|
|
234
|
+
+ (f === 'documentTaxonomy' ? 'The taxonomy travels in the resolved `model` blob.'
|
|
235
|
+
: f === 'digitizationMode' ? 'The serializer defaults it to `fileUpload` internally.'
|
|
236
|
+
: 'It is derived from `fileRef` upstream.');
|
|
237
|
+
throw new CompileRefusal('IXP_FORBIDDEN_INPUT', `IxP step "${step}" sets ${set.map(why).join(' It also sets ')} Remove ${set.length === 1 ? 'it' : 'them'}: `
|
|
238
|
+
+ `the \`.flow\` would drop ${set.length === 1 ? 'it' : 'them'}.`);
|
|
239
|
+
}
|
|
223
240
|
/**
|
|
224
241
|
* `ixpExtract(...)`-specific checks.
|
|
225
242
|
*
|
|
@@ -255,14 +272,7 @@ function checkIxpExtract(inputs, ctx) {
|
|
|
255
272
|
at('IXP_NO_FILE', `IxP step "${step}" has no \`fileRef\`. It is the node's only required INPUT (\`inputDefinition.required\`) `
|
|
256
273
|
+ `and it takes a whole Flow Attachment object — normally a \`file\`-typed flow input.`, `fileRef: out('start', 'invoiceFile') // .input({ invoiceFile: types.file })`);
|
|
257
274
|
}
|
|
258
|
-
|
|
259
|
-
if (inputs?.[f] !== undefined) {
|
|
260
|
-
at('IXP_FORBIDDEN_INPUT', `IxP step "${step}" sets \`${f}\`, which the current node does not declare — it belonged to a prior `
|
|
261
|
-
+ `schema. ${f === 'documentTaxonomy' ? 'The taxonomy travels in the resolved `model` blob.'
|
|
262
|
-
: f === 'digitizationMode' ? 'The serializer defaults it to `fileUpload` internally.'
|
|
263
|
-
: 'It is derived from `fileRef` upstream.'}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
275
|
+
// An input of the prior schema is the serializer's IXP_FORBIDDEN_INPUT.
|
|
266
276
|
}
|
|
267
277
|
/**
|
|
268
278
|
* The extractor's node: the definition's `inputDefaults` with the file the
|
|
@@ -270,6 +280,7 @@ function checkIxpExtract(inputs, ctx) {
|
|
|
270
280
|
*/
|
|
271
281
|
function serializeIxpExtract(s, ctx) {
|
|
272
282
|
const def = ixpExtractDef(s, ctx.step); // throws on an unparseable project
|
|
283
|
+
refuseForbiddenInputs(s, ctx.step);
|
|
273
284
|
const { versionId } = parseIxpProjectType(s.project); // ixpExtractDef threw if it could not parse
|
|
274
285
|
// The bindings first: two steps on the same project share ONE pair, and the
|
|
275
286
|
// definition's `model.bindings.resourceKey` names the key they hang off.
|
|
@@ -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),
|
package/dist/serialize.js
CHANGED
|
@@ -292,6 +292,13 @@ export function serializeWithSidecars(built, opts = {}) {
|
|
|
292
292
|
const flow = serialize(built, { ...opts, sidecars });
|
|
293
293
|
return { flow, sidecars };
|
|
294
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* A sidecar path as the file system sees it on macOS (default APFS) and
|
|
297
|
+
* Windows, which ignore case: two paths with one key are one file there.
|
|
298
|
+
*/
|
|
299
|
+
function sidecarKey(path) {
|
|
300
|
+
return path.toLowerCase();
|
|
301
|
+
}
|
|
295
302
|
/**
|
|
296
303
|
* `variables.nodes` — one entry per node OUTPUT, derived from the nodes we just
|
|
297
304
|
* emitted.
|
|
@@ -1815,7 +1822,7 @@ function serializeFlow(built, opts) {
|
|
|
1815
1822
|
* gets them and one that does not is unaffected.
|
|
1816
1823
|
*/
|
|
1817
1824
|
const sidecars = opts.sidecars ?? [];
|
|
1818
|
-
/** The sidecar paths an explicit `source` chose → who chose it (see `addSidecar`). */
|
|
1825
|
+
/** The sidecar paths an explicit `source` chose (by {@link sidecarKey}) → who chose it (see `addSidecar`). */
|
|
1819
1826
|
const explicitSidecars = new Map();
|
|
1820
1827
|
/**
|
|
1821
1828
|
* Add one sidecar file. An agent or a resource whose explicit `source` names
|
|
@@ -1825,19 +1832,26 @@ function serializeFlow(built, opts) {
|
|
|
1825
1832
|
* naming both; `owner` is the one whose `source` is explicit, as the refusal
|
|
1826
1833
|
* names it (`inline agent step "answer"'s context index "SOP"`). The same file
|
|
1827
1834
|
* twice (a subflow body emitted for two callers) is not a clash.
|
|
1835
|
+
*
|
|
1836
|
+
* Paths are compared by {@link sidecarKey}, ignoring case: on macOS (default
|
|
1837
|
+
* APFS) and Windows, `D84E2C17-…/agent.json` and `d84e2c17-…/agent.json` are
|
|
1838
|
+
* one file (#1278).
|
|
1828
1839
|
*/
|
|
1829
1840
|
const addSidecar = (file, owner) => {
|
|
1830
|
-
const
|
|
1831
|
-
const
|
|
1841
|
+
const key = sidecarKey(file.path);
|
|
1842
|
+
const earlier = sidecars.find((f) => sidecarKey(f.path) === key);
|
|
1843
|
+
const earlierOwner = explicitSidecars.get(key);
|
|
1832
1844
|
if (earlier !== undefined && JSON.stringify(earlier.json) !== JSON.stringify(file.json)
|
|
1833
1845
|
&& (owner !== undefined || earlierOwner !== undefined)) {
|
|
1834
1846
|
const names = [earlierOwner ?? 'another agent or resource', owner ?? 'another agent or resource'];
|
|
1835
|
-
|
|
1847
|
+
const where = earlier.path === file.path ? `"${file.path}"`
|
|
1848
|
+
: `"${earlier.path}" and "${file.path}" (one file on macOS and Windows, which ignore case)`;
|
|
1849
|
+
throw new CompileRefusal('INLINE_AGENT_RESOURCE_SOURCE_DUP', `${names[0].charAt(0).toUpperCase()}${names[0].slice(1)} and ${names[1]} would both write ${where} with `
|
|
1836
1850
|
+ 'different content, so the second file would replace the first. Give each its own `source`, or omit '
|
|
1837
1851
|
+ '`source` to derive one.');
|
|
1838
1852
|
}
|
|
1839
1853
|
if (owner !== undefined && earlierOwner === undefined)
|
|
1840
|
-
explicitSidecars.set(
|
|
1854
|
+
explicitSidecars.set(key, owner);
|
|
1841
1855
|
sidecars.push(file);
|
|
1842
1856
|
};
|
|
1843
1857
|
/**
|
|
@@ -2230,6 +2244,34 @@ function serializeFlow(built, opts) {
|
|
|
2230
2244
|
|| scope.pendingEdges.some((p) => p.from.node === node && isOutcome(p.from.port));
|
|
2231
2245
|
return tails.filter((t) => capsOpenTail.has(t.node) && !hasOutcomeEdge(t.node));
|
|
2232
2246
|
}
|
|
2247
|
+
/**
|
|
2248
|
+
* A subflow body is started by its caller, through the one manual Start node
|
|
2249
|
+
* `emitSubflowBodyIn` writes. So a child's own way of starting has nowhere to
|
|
2250
|
+
* go in the `.flow`, and writing the body without it would silently drop what
|
|
2251
|
+
* the author wrote (#1067): a `.trigger(scheduled(…))` would never fire, and an
|
|
2252
|
+
* `.entryPoint()` root and its steps would vanish. Both are refused.
|
|
2253
|
+
* `.trigger(manual(), { label, version })` is allowed: it names the child's
|
|
2254
|
+
* Start node (#1050), and it is the form decompile writes for a renamed one.
|
|
2255
|
+
*/
|
|
2256
|
+
function refuseChildRoots(child, step) {
|
|
2257
|
+
// `subflow(undefined, …)` is `check`'s SUBFLOW_NO_CHILD.
|
|
2258
|
+
if (!child || !Array.isArray(child.steps))
|
|
2259
|
+
return;
|
|
2260
|
+
const kind = child.trigger?.kind;
|
|
2261
|
+
if (child.trigger !== undefined && kind !== 'manual') {
|
|
2262
|
+
const noun = kind === 'scheduled' ? 'schedule' : kind === 'event' ? 'event subscription' : 'trigger';
|
|
2263
|
+
throw new CompileRefusal('SUBFLOW_CHILD_TRIGGER', `Subflow "${step}" runs a flow with its own \`.trigger(…)\`. A subflow is started by its caller, never by a `
|
|
2264
|
+
+ `trigger of its own — the ${noun} would be dropped. Remove the .trigger() from "${child.id}" `
|
|
2265
|
+
+ `(\`.trigger(manual(), { label })\` is allowed, to name the child's start node).`);
|
|
2266
|
+
}
|
|
2267
|
+
const roots = child.entryPoints ?? [];
|
|
2268
|
+
if (roots.length > 0) {
|
|
2269
|
+
throw new CompileRefusal('SUBFLOW_CHILD_ENTRY_POINT', `Subflow "${step}" runs a flow with ${roots.length === 1 ? 'an entry point' : 'entry points'} `
|
|
2270
|
+
+ `(${roots.map((r) => `"${r.id}"`).join(', ')}). A subflow has one root, its caller, so the entry `
|
|
2271
|
+
+ `${roots.length === 1 ? 'point and its steps' : 'points and their steps'} would be dropped. Call a flow `
|
|
2272
|
+
+ `without .entryPoint(), or move each entry point's steps into a flow of its own.`);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2233
2275
|
/**
|
|
2234
2276
|
* Emit a child flow into its own scope and register it under the calling
|
|
2235
2277
|
* node's id — that key is how the format names a subflow body, and how
|
|
@@ -2238,7 +2280,7 @@ function serializeFlow(built, opts) {
|
|
|
2238
2280
|
* The child gets its own manual Start node (a subflow always starts manually
|
|
2239
2281
|
* — nothing schedules a subflow; its caller is the trigger) and its own
|
|
2240
2282
|
* variables. A child's `.trigger(manual(), { label, version })` sets that
|
|
2241
|
-
* node's canvas label and exact version; `
|
|
2283
|
+
* node's canvas label and exact version; `refuseChildRoots` refuses any other kind.
|
|
2242
2284
|
* Every `in` variable carries `triggerNodeId` pointing at that
|
|
2243
2285
|
* Start: it is what binds the caller's value, and omitting it is the single
|
|
2244
2286
|
* defect that makes a subflow validate clean and then fault at run time with
|
|
@@ -3012,6 +3054,7 @@ function serializeFlow(built, opts) {
|
|
|
3012
3054
|
if (registered)
|
|
3013
3055
|
return emitRegistered(registered, step, tails, scope, parentId);
|
|
3014
3056
|
if (spec.kind === 'subflow') {
|
|
3057
|
+
refuseChildRoots(spec.child, step.name);
|
|
3015
3058
|
const id = stepUid(scope, step.name);
|
|
3016
3059
|
// `subflows` is ONE flat map for the whole file, keyed by the calling
|
|
3017
3060
|
// node's id. `stepUid` draws from the file-wide allocator, including in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.66.
|
|
3
|
+
"version": "6.66.7",
|
|
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",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"@types/node": "^22.7.0",
|
|
101
101
|
"esbuild": "^0.28.1"
|
|
102
102
|
},
|
|
103
|
-
"gitref": "
|
|
103
|
+
"gitref": "00764f58249484b8ee341c2cc0d24b38eee8ec14"
|
|
104
104
|
}
|