@uipath/maestro-builder-sdk 6.66.6 → 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 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 subflow always starts manually — its caller is what starts it. Nothing
1575
- // schedules a subflow, so a schedule here is silently ignored. `manual()` is
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 — `check`'s
21
- * `INLINE_AGENT_COMPOSITE_INPUT` says so and names the fix (compute it in a
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
  *
@@ -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: "lint";
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: "lint";
216
- readonly DOCVAL_RENDER_UNKNOWN: "lint";
217
- readonly DOCVAL_TAXONOMY_REQUIRED: "lint";
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: "lint";
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: "lint";
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: "lint";
313
- readonly HITL_APP_SCHEMA_INVALID: "lint";
314
- readonly HITL_APP_WITHOUT_VARIANT: "lint";
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: "lint";
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: "lint";
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: "lint";
336
- readonly HITL_FIELD_VALUE_NOT_REF: "lint";
337
- readonly HITL_FIELD_VARIABLE: "lint";
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: "lint";
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: "lint";
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: "lint";
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: "lint";
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: "lint";
393
- readonly INLINE_AGENT_ESCALATION_VARIANT: "lint";
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: "lint";
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 SUBFLOW_CHILD_TRIGGER: "lint";
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";
@@ -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: 'lint',
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: 'lint',
257
- DOCVAL_RENDER_UNKNOWN: 'lint',
258
- DOCVAL_TAXONOMY_REQUIRED: 'lint',
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: 'lint',
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: 'lint',
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: 'lint',
354
- HITL_APP_SCHEMA_INVALID: 'lint',
355
- HITL_APP_WITHOUT_VARIANT: 'lint',
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: 'lint',
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: 'lint',
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: 'lint',
377
- HITL_FIELD_VALUE_NOT_REF: 'lint',
378
- HITL_FIELD_VARIABLE: 'lint',
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: 'lint',
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: 'lint',
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: 'lint',
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: 'lint',
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: 'lint',
434
- INLINE_AGENT_ESCALATION_VARIANT: 'lint',
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: 'lint',
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
- SUBFLOW_CHILD_TRIGGER: 'lint',
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, flatNameOf, flatNameSegmentsOf } from '../../../core/expr.js';
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 prompts = `${String(inputs?.systemPrompt ?? '')}\n${String(inputs?.userPrompt ?? '')}`;
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: an input bound to a COMPUTED EXPRESSION rather than to one
512
- // reference. The platform names each of an agent's arguments after the
513
- // reference path it is bound to (the shell node's `agentInputVariables`), so
514
- // an expression spanning zero or several references has no single argument —
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
- if (e?.variant !== undefined && e.variant !== 'quick-form') {
698
- at('INLINE_AGENT_ESCALATION_VARIANT', `Inline agent step "${step}"'s escalation names the variant ${JSON.stringify(e.variant)}. Valid: `
699
- + `'quick-form' — or omit it for the app-backed default.`);
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
- // The form lives INLINE: an app would be emitted and ignored, and a form
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
+ }
@@ -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 `check`
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
- if (inputs.resourceKey !== undefined && inputs.folderKey === undefined) {
95
- ctx.report('DATAFABRIC_RESOURCE_KEY_NO_FOLDER', `"${ctx.step}" sets { resourceKey } without { folderKey }. The pair exists to scope a FOLDER entity; a `
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; the half-authored form is refused by check(),
51
- * so reaching here with both means emit both. Same id for the same (entity,
52
- * resourceKey), so two steps on one entity share one pair rather than
53
- * duplicating it.
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; the half-authored form is refused by check(),
67
- * so reaching here with both means emit both. Same id for the same (entity,
68
- * resourceKey), so two steps on one entity share one pair rather than
69
- * duplicating it.
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 check exists for the JS caller and for a
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
- for (const f of IXP_FORBIDDEN_INPUTS) {
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 !== 'quick-form' && variant !== 'action-app'
90
- && variant !== 'document-validation') {
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
- at('HITL_DOCUMENT_WITHOUT_VARIANT', `Human task "${step}" names a document to validate, but its variant is `
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
- const er = doc.extractionResult;
149
- const erEmpty = er === undefined || er === null || (typeof er === 'string' && !er.trim());
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
- at('HITL_APP_WITHOUT_VARIANT', `Human task "${step}" names a deployed app, but its variant is `
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
- // An in-solution app has no folder to author. A published one does: with
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
- at('HITL_APP_SCHEMA_INVALID', `Human task "${step}" gives its Action App a schema that is not usable: ${contract}. `
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
- if (contract.outcomes.length > 0) {
241
- for (const o of Array.isArray(inputs?.outcomes) ? inputs.outcomes : []) {
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, i) => {
272
- const where = `fields[${i}]`;
219
+ fields.forEach((f) => {
273
220
  const id = typeof f?.id === 'string' ? f.id.trim() : '';
274
- if (!id) {
275
- at('HITL_FIELD_NO_ID', `Human task "${step}" has a form field with no id (${where}). The id is the key the `
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
- // The landing variable is read as `$vars.<variable>`, so it has to be one
303
- // identifier — not a path, not a `vars.` prefix — and only an asked field
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
- else if (!(value instanceof Expr)) {
325
- at('HITL_FIELD_VALUE_NOT_REF', `Human task "${step}" gives the form field "${id}" a raw `
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
- at('HITL_OUTCOME_BAD_ID', `Human task "${step}" outcome ${JSON.stringify(name ?? i)} has an id that is not a non-empty string. `
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 — `check` rejects one (HITL_FIELD_VALUE_NOT_PATH) before we get here.
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: `check` reports the unknown one (HITL_VARIANT_UNKNOWN).
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
@@ -2244,6 +2244,34 @@ function serializeFlow(built, opts) {
2244
2244
  || scope.pendingEdges.some((p) => p.from.node === node && isOutcome(p.from.port));
2245
2245
  return tails.filter((t) => capsOpenTail.has(t.node) && !hasOutcomeEdge(t.node));
2246
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
+ }
2247
2275
  /**
2248
2276
  * Emit a child flow into its own scope and register it under the calling
2249
2277
  * node's id — that key is how the format names a subflow body, and how
@@ -2252,7 +2280,7 @@ function serializeFlow(built, opts) {
2252
2280
  * The child gets its own manual Start node (a subflow always starts manually
2253
2281
  * — nothing schedules a subflow; its caller is the trigger) and its own
2254
2282
  * variables. A child's `.trigger(manual(), { label, version })` sets that
2255
- * node's canvas label and exact version; `check` refuses any other kind.
2283
+ * node's canvas label and exact version; `refuseChildRoots` refuses any other kind.
2256
2284
  * Every `in` variable carries `triggerNodeId` pointing at that
2257
2285
  * Start: it is what binds the caller's value, and omitting it is the single
2258
2286
  * defect that makes a subflow validate clean and then fault at run time with
@@ -3026,6 +3054,7 @@ function serializeFlow(built, opts) {
3026
3054
  if (registered)
3027
3055
  return emitRegistered(registered, step, tails, scope, parentId);
3028
3056
  if (spec.kind === 'subflow') {
3057
+ refuseChildRoots(spec.child, step.name);
3029
3058
  const id = stepUid(scope, step.name);
3030
3059
  // `subflows` is ONE flat map for the whole file, keyed by the calling
3031
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.6",
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": "108478742e5fc426dc26ba07020d85a3fdbcaa66"
103
+ "gitref": "00764f58249484b8ee341c2cc0d24b38eee8ec14"
104
104
  }