@uipath/maestro-builder-sdk 6.60.0 → 6.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +10 -12
  2. package/dist/api-index.md +5 -4
  3. package/dist/check-cli.js +8 -7
  4. package/dist/check.d.ts +11 -0
  5. package/dist/check.js +192 -783
  6. package/dist/cli-library.d.ts +2 -1
  7. package/dist/cli-library.js +7 -2
  8. package/dist/cli-result.d.ts +14 -0
  9. package/dist/cli-result.js +18 -1
  10. package/dist/compile-cli.js +2 -2
  11. package/dist/core/compile-refusal.d.ts +7 -1
  12. package/dist/core/compile-refusal.js +9 -1
  13. package/dist/core/connector-checks.d.ts +8 -30
  14. package/dist/core/connector-checks.js +11 -60
  15. package/dist/core/connector-inputs.js +9 -1
  16. package/dist/core/event-checks.d.ts +15 -2
  17. package/dist/core/event-checks.js +28 -13
  18. package/dist/core/hitl-routing.d.ts +10 -1
  19. package/dist/core/hitl-routing.js +20 -5
  20. package/dist/core/lookups.d.ts +13 -0
  21. package/dist/core/lookups.js +26 -0
  22. package/dist/core/refusals.d.ts +2 -0
  23. package/dist/core/refusals.js +5 -1
  24. package/dist/flow-node-type.d.ts +35 -21
  25. package/dist/flow-parallel-hitl.d.ts +105 -0
  26. package/dist/flow-parallel-hitl.js +214 -0
  27. package/dist/flow-sdk.d.ts +4 -4
  28. package/dist/flow-sdk.js +2 -2
  29. package/dist/flow-terminate-names.d.ts +2 -2
  30. package/dist/flow-terminate-names.js +2 -2
  31. package/dist/merge-compile.js +2 -1
  32. package/dist/nodes/agent/flow/check.js +13 -88
  33. package/dist/nodes/agent/flow/inline-agent.js +41 -14
  34. package/dist/nodes/connector/flow/node-type.js +45 -18
  35. package/dist/nodes/document/flow/ixp.d.ts +1 -1
  36. package/dist/nodes/document/flow/ixp.js +22 -26
  37. package/dist/nodes/hitl/flow/check.js +4 -11
  38. package/dist/nodes/hitl/flow/node-type.js +16 -7
  39. package/dist/nodes/http/flow/check.js +5 -31
  40. package/dist/nodes/http/flow/node-type.js +7 -4
  41. package/dist/nodes/queue/flow/check.d.ts +4 -3
  42. package/dist/nodes/queue/flow/check.js +7 -6
  43. package/dist/nodes/queue/flow/node-type.js +1 -1
  44. package/dist/nodes/transform/flow/check.d.ts +2 -0
  45. package/dist/nodes/transform/flow/check.js +3 -6
  46. package/dist/nodes/transform/flow/node-type.js +6 -6
  47. package/dist/raw-node.js +8 -8
  48. package/dist/serialize.d.ts +38 -3
  49. package/dist/serialize.js +757 -322
  50. package/package.json +2 -2
@@ -15,7 +15,8 @@ export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts
15
15
  /**
16
16
  * Flow's check options from a library the CLI already opened: the shared
17
17
  * connector and event hooks, Flow's own field validator (it renders Flow
18
- * expressions, as `serialize` does), and the measured list envelopes.
18
+ * expressions, as `serialize` does), the measured list envelopes, and the
19
+ * library and bindings its serializer pass compiles with.
19
20
  *
20
21
  * One builder for `flow check` and `flow compile`'s check pass, so the two
21
22
  * cannot disagree.
@@ -5,14 +5,19 @@ export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts
5
5
  /**
6
6
  * Flow's check options from a library the CLI already opened: the shared
7
7
  * connector and event hooks, Flow's own field validator (it renders Flow
8
- * expressions, as `serialize` does), and the measured list envelopes.
8
+ * expressions, as `serialize` does), the measured list envelopes, and the
9
+ * library and bindings its serializer pass compiles with.
9
10
  *
10
11
  * One builder for `flow check` and `flow compile`'s check pass, so the two
11
12
  * cannot disagree.
12
13
  */
13
14
  export function flowCheckOpts(library, bindings) {
14
15
  // Flow's own refusal of an operation `prepare` synthesized from Integration Service (BPMN-only).
15
- const shared = connectorCheckOpts(library, bindings, connectorInputErrors, synthesizedDefinitionRefusal);
16
+ // The serializer pass gets what `compile` serializes with.
17
+ const shared = {
18
+ ...connectorCheckOpts(library, bindings, connectorInputErrors, synthesizedDefinitionRefusal),
19
+ ...(library ? { library } : {}),
20
+ };
16
21
  if (!library)
17
22
  return shared;
18
23
  return {
@@ -32,8 +32,22 @@ export declare function reportDiagnostics(diags: ReadonlyArray<{
32
32
  level: string;
33
33
  code: string;
34
34
  message: string;
35
+ step?: string;
35
36
  suggestion?: string;
36
37
  }>): void;
38
+ /**
39
+ * One diagnostic as `check` and `compile` print it: its mark and code, the
40
+ * message, the step it is on (unless the message already names it), and the
41
+ * suggestion. For instance
42
+ * `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`.
43
+ */
44
+ export declare function diagnosticLine(d: {
45
+ level: string;
46
+ code: string;
47
+ message: string;
48
+ step?: string;
49
+ suggestion?: string;
50
+ }): string;
37
51
  /**
38
52
  * The error diagnostic for a throw that ends a compile: the error's own string
39
53
  * `code` when it carries one, else `COMPILE_FAILED`.
@@ -44,9 +44,26 @@ export function reportDiagnostics(diags) {
44
44
  reportResult({
45
45
  errors: diags.filter((d) => d.level === 'error').length,
46
46
  warnings: diags.filter((d) => d.level !== 'error').length,
47
- diagnostics: diags.map((d) => ({ level: d.level, code: d.code, message: d.message, ...(d.suggestion ? { suggestion: d.suggestion } : {}) })),
47
+ diagnostics: diags.map((d) => ({
48
+ level: d.level, code: d.code, message: d.message,
49
+ ...(d.step !== undefined ? { step: d.step } : {}),
50
+ ...(d.suggestion ? { suggestion: d.suggestion } : {}),
51
+ })),
48
52
  });
49
53
  }
54
+ /**
55
+ * One diagnostic as `check` and `compile` print it: its mark and code, the
56
+ * message, the step it is on (unless the message already names it), and the
57
+ * suggestion. For instance
58
+ * `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`.
59
+ */
60
+ export function diagnosticLine(d) {
61
+ // A step inside a subflow is `<caller> › <step>`; the message names the innermost one.
62
+ const named = d.step !== undefined && d.message.includes(`"${d.step.split(' › ').pop()}"`);
63
+ return `${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`
64
+ + (d.step !== undefined && !named ? ` (step "${d.step}")` : '')
65
+ + (d.suggestion ? ` (did you mean "${d.suggestion}"?)` : '');
66
+ }
50
67
  /**
51
68
  * The error diagnostic for a throw that ends a compile: the error's own string
52
69
  * `code` when it carries one, else `COMPILE_FAILED`.
@@ -17,7 +17,7 @@
17
17
  * product's own gates are supposed to answer. `.build()` still throws on
18
18
  * structural problems; only the diagnostic pass is skipped.
19
19
  */
20
- import { reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
20
+ import { diagnosticLine, reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
21
21
  import { writeJsonFile } from './core/json-file.js';
22
22
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
23
23
  import { basename, dirname, join, relative, resolve } from 'node:path';
@@ -80,7 +80,7 @@ export async function run(argv) {
80
80
  const diags = check(built, flowCheckOpts(library, bindings));
81
81
  const errors = diags.filter((d) => d.level === 'error');
82
82
  for (const d of diags)
83
- console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
83
+ console.error(` ${diagnosticLine(d)}`);
84
84
  diagnostics.push(...diags);
85
85
  if (errors.length) {
86
86
  reportDiagnostics(diagnostics);
@@ -17,5 +17,11 @@
17
17
  */
18
18
  export declare class CompileRefusal extends Error {
19
19
  readonly code: string;
20
- constructor(code: string, message: string);
20
+ /**
21
+ * What to write instead, when the refusal knows (`check` reports it as the
22
+ * diagnostic's `suggestion`). For instance `value: 'approve'` for a
23
+ * `.stepSwitch` arm that names an exit the step does not declare.
24
+ */
25
+ readonly suggestion?: string;
26
+ constructor(code: string, message: string, suggestion?: string);
21
27
  }
@@ -17,10 +17,18 @@
17
17
  */
18
18
  export class CompileRefusal extends Error {
19
19
  code;
20
- constructor(code, message) {
20
+ /**
21
+ * What to write instead, when the refusal knows (`check` reports it as the
22
+ * diagnostic's `suggestion`). For instance `value: 'approve'` for a
23
+ * `.stepSwitch` arm that names an exit the step does not declare.
24
+ */
25
+ suggestion;
26
+ constructor(code, message, suggestion) {
21
27
  super(message);
22
28
  // `name` stays `Error`, so a converted `throw new Error(…)` prints exactly as
23
29
  // it did (`Error: …` in `String(e)` and stacks). A subclass names itself.
24
30
  this.code = code;
31
+ if (suggestion !== undefined)
32
+ this.suggestion = suggestion;
25
33
  }
26
34
  }
@@ -154,7 +154,12 @@ export interface ConnectorCheckOpts {
154
154
  * author about everything ELSE that is wrong, which is the whole point of
155
155
  * running the cheap pass first.
156
156
  */
157
- export declare function checkConnectorLookups(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, dialect: ExprDialect, report: ReportConnector): void;
157
+ export declare function checkConnectorLookups(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, dialect: ExprDialect, report: ReportConnector,
158
+ /**
159
+ * Whether to warn LOOKUP_UNRESOLVED. Flow's `check` does not: it runs the
160
+ * serializer, which refuses the node under that code.
161
+ */
162
+ unresolvedWarning?: boolean): void;
158
163
  /**
159
164
  * The schema rules: an unprepared object, and unprepared custom fields.
160
165
  *
@@ -179,9 +184,7 @@ export declare function checkConnectorSchema(where: string, spec: ConnectorSpec,
179
184
  * bindings.json for the names); without them `check` stays a pure function of the
180
185
  * built model and says nothing, as before.
181
186
  */
182
- export declare function checkConnectorContract(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, report: ReportConnector,
183
- /** The activity's own id, for {@link connectionConnectorConflicts}; omit to record nothing. */
184
- id?: string): void;
187
+ export declare function checkConnectorContract(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, report: ReportConnector): void;
185
188
  /**
186
189
  * The binding-label rules `serialize`'s `resolveBinding` enforces, for one
187
190
  * `{ connection, folder }` pair — reported at every site that resolves one
@@ -195,12 +198,7 @@ id?: string): void;
195
198
  export declare function checkBindingLabels(where: string, labels: {
196
199
  connection?: unknown;
197
200
  folder?: unknown;
198
- } | undefined, opts: ConnectorCheckOpts, report: (code: 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED', message: string) => void, target?: PrepareTarget,
199
- /** The activity, and the connector its connection must belong to, for {@link connectionConnectorConflicts}. */
200
- use?: {
201
- id: string;
202
- connector: string | undefined;
203
- }): void;
201
+ } | undefined, opts: ConnectorCheckOpts, report: (code: 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED', message: string) => void, target?: PrepareTarget): void;
204
202
  /**
205
203
  * BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
206
204
  * label — the sentence compile throws (see `requireBindingPair` in serialize).
@@ -231,23 +229,3 @@ export declare function lookupPhrases(field: string, lookup: LookupSpec, spec: C
231
229
  target: string;
232
230
  call: string;
233
231
  };
234
- /** One activity's use of a connection label, as the connector the connection must belong to. */
235
- export interface ConnectionUse {
236
- /** The activity's own id — Flow's step name, BPMN's element id. */
237
- id: string;
238
- label: string;
239
- connector: string;
240
- }
241
- /** Run `check`, and hand back the connection uses its binding-label rules saw. */
242
- export declare function collectConnectionUses(check: () => void): ConnectionUse[];
243
- /**
244
- * CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
245
- * document (`connectionConnectors`, bindings-v2.ts), reported here with the same
246
- * sentence so `check` does not print `✓ no issues` for a source `compile` then
247
- * refuses. Labels are grouped by what they resolve to, so two labels bound to
248
- * one connection id are one connection, as they are in the artifact.
249
- */
250
- export declare function connectionConnectorConflicts(uses: readonly ConnectionUse[], opts: ConnectorCheckOpts): {
251
- id: string;
252
- message: string;
253
- }[];
@@ -26,7 +26,7 @@ import { isLookupToken, resolvedValue, } from './lookups.js';
26
26
  import { Expr } from './expr.js';
27
27
  import { extractRefs } from './expr-check.js';
28
28
  import { prepareCommand, resolveFlagArg } from './cli-spelling.js';
29
- import { bindingLabelRefusal, bindingMissingMessage, connectionConnectorConflictMessage, missingBindingLabels, } from './binding-messages.js';
29
+ import { bindingLabelRefusal, bindingMissingMessage, missingBindingLabels, } from './binding-messages.js';
30
30
  /**
31
31
  * Flow: an input is an {@link Expr} object. A non-literal one is runtime; a
32
32
  * literal one (`lit(…)`) carries the constant it was built from.
@@ -80,7 +80,12 @@ export const EQUALS_EXPR_DIALECT = {
80
80
  * author about everything ELSE that is wrong, which is the whole point of
81
81
  * running the cheap pass first.
82
82
  */
83
- export function checkConnectorLookups(where, spec, opts, dialect, report) {
83
+ export function checkConnectorLookups(where, spec, opts, dialect, report,
84
+ /**
85
+ * Whether to warn LOOKUP_UNRESOLVED. Flow's `check` does not: it runs the
86
+ * serializer, which refuses the node under that code.
87
+ */
88
+ unresolvedWarning = true) {
84
89
  const specs = opts.lookups?.(spec.key, spec.action, spec.version, spec.object);
85
90
  const resolutions = opts.resolutions ?? {};
86
91
  // One LOOKUP_LITERAL_ID per field: a multiselect of three pasted ids is one
@@ -170,7 +175,7 @@ export function checkConnectorLookups(where, spec, opts, dialect, report) {
170
175
  };
171
176
  for (const [name, value] of Object.entries(spec.inputs ?? {}))
172
177
  visit(value, [name]);
173
- if (unresolved.length)
178
+ if (unresolved.length && unresolvedWarning)
174
179
  report(unresolvedDiagnostic(where, spec, unresolved));
175
180
  if (literals.length)
176
181
  report(literalIdDiagnostic(where, spec, literals, dialect.descriptors));
@@ -328,9 +333,7 @@ export function checkConnectorSchema(where, spec, opts, dialect, report) {
328
333
  * bindings.json for the names); without them `check` stays a pure function of the
329
334
  * built model and says nothing, as before.
330
335
  */
331
- export function checkConnectorContract(where, spec, opts, report,
332
- /** The activity's own id, for {@link connectionConnectorConflicts}; omit to record nothing. */
333
- id) {
336
+ export function checkConnectorContract(where, spec, opts, report) {
334
337
  const target = { key: spec.key, action: spec.action, version: spec.version, object: spec.object };
335
338
  // The operation itself first: an operation compile cannot resolve has no field
336
339
  // contract to check, and `connectorInputs` answers "unknown" for it.
@@ -344,7 +347,7 @@ id) {
344
347
  report({ level: 'error', code: 'CONNECTOR_INPUT', message: `${where}: ${inputErr}` });
345
348
  }
346
349
  }
347
- checkBindingLabels(where, spec, opts, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action }, id === undefined ? undefined : { id, connector: spec.key });
350
+ checkBindingLabels(where, spec, opts, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
348
351
  }
349
352
  /**
350
353
  * The binding-label rules `serialize`'s `resolveBinding` enforces, for one
@@ -356,13 +359,7 @@ id) {
356
359
  * Silent without a bindings.json, and for a label that resolves to anything
357
360
  * other than its own name (stub values are compile's CONNECTION_STUB warning).
358
361
  */
359
- export function checkBindingLabels(where, labels, opts, report, target,
360
- /** The activity, and the connector its connection must belong to, for {@link connectionConnectorConflicts}. */
361
- use) {
362
- const label = labels?.connection;
363
- if (use?.connector && typeof label === 'string' && label !== '') {
364
- connectionUsage?.push({ id: use.id, label, connector: use.connector });
365
- }
362
+ export function checkBindingLabels(where, labels, opts, report, target) {
366
363
  const bindings = opts.bindings;
367
364
  if (!bindings || !labels)
368
365
  return;
@@ -485,49 +482,3 @@ function runtimeLookupDiagnostic(where, spec, field, lookup, js, dialect) {
485
482
  : '');
486
483
  return { level: isEmail ? 'error' : 'warning', code: 'LOOKUP_RUNTIME_VALUE', message };
487
484
  }
488
- /**
489
- * The connection uses {@link checkBindingLabels} saw during the current check —
490
- * the model and every child model, which share one `bindings[]`. Module state
491
- * rather than a `ConnectorCheckOpts` field because a check is synchronous and
492
- * this is not an input; {@link collectConnectionUses} saves and restores it, so
493
- * a nested check cannot mix two models.
494
- */
495
- let connectionUsage;
496
- /** Run `check`, and hand back the connection uses its binding-label rules saw. */
497
- export function collectConnectionUses(check) {
498
- const outer = connectionUsage;
499
- connectionUsage = [];
500
- try {
501
- check();
502
- return connectionUsage;
503
- }
504
- finally {
505
- connectionUsage = outer;
506
- }
507
- }
508
- /**
509
- * CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
510
- * document (`connectionConnectors`, bindings-v2.ts), reported here with the same
511
- * sentence so `check` does not print `✓ no issues` for a source `compile` then
512
- * refuses. Labels are grouped by what they resolve to, so two labels bound to
513
- * one connection id are one connection, as they are in the artifact.
514
- */
515
- export function connectionConnectorConflicts(uses, opts) {
516
- const byConnection = new Map();
517
- for (const use of uses) {
518
- const key = opts.bindings?.resolve(use.label) ?? use.label;
519
- const byConnector = byConnection.get(key) ?? new Map();
520
- byConnection.set(key, byConnector);
521
- byConnector.set(use.connector, [...(byConnector.get(use.connector) ?? []), use.id]);
522
- }
523
- const out = [];
524
- for (const [key, byConnector] of byConnection) {
525
- if (byConnector.size < 2)
526
- continue;
527
- out.push({
528
- id: [...byConnector.values()][1][0],
529
- message: connectionConnectorConflictMessage(key, byConnector),
530
- });
531
- }
532
- return out;
533
- }
@@ -22,7 +22,7 @@
22
22
  * The ROUTING is the library's business either way: a field's location is a
23
23
  * property of the operation, not of the family emitting it.
24
24
  */
25
- import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './lookups.js';
25
+ import { isLookupToken, resolvedValue, unresolvedLookupMessage, unresolvedLookupsMessage, } from './lookups.js';
26
26
  import { prepareCommand } from './cli-spelling.js';
27
27
  import { editDistance } from './edit-distance.js';
28
28
  /** Flow's dialect, re-exported so `serialize.ts` need not reach past this module. */
@@ -101,6 +101,8 @@ export function buildConnectorInputs(rc, inputs, opts) {
101
101
  const fields = (rc.fields ?? []);
102
102
  const byName = new Map(fields.map((f) => [f.name, f]));
103
103
  const errs = validateNestedConnectorKeys(fields, inputs, byName, dialect);
104
+ /** The unresolved lookups among `errs`: when they are all of them, the refusal is LOOKUP_UNRESOLVED. */
105
+ const owed = [];
104
106
  const routed = {
105
107
  bodyParameters: {}, pathParameters: {}, queryParameters: {},
106
108
  };
@@ -149,6 +151,7 @@ export function buildConnectorInputs(rc, inputs, opts) {
149
151
  if (policy.unresolvedLookups !== 'ignore') {
150
152
  for (const token of unresolved)
151
153
  errs.push(unresolvedLookupMessage(token));
154
+ owed.push(...unresolved);
152
155
  continue;
153
156
  }
154
157
  if (isLookupToken(nested.value))
@@ -171,6 +174,11 @@ export function buildConnectorInputs(rc, inputs, opts) {
171
174
  const slot = ownEntry(SLOT_FOR_LOCATION, location) ?? 'bodyParameters';
172
175
  routed[slot][f.name] = render(nested.value);
173
176
  }
177
+ // Inputs that are fine but for lookups `prepare` has not recorded are that one
178
+ // condition, LOOKUP_UNRESOLVED, with the one command that records them all.
179
+ if (errs.length && owed.length === errs.length) {
180
+ throw new CompileRefusal('LOOKUP_UNRESOLVED', `connector ${rc.nodeType}: ${unresolvedLookupsMessage(owed)}`);
181
+ }
174
182
  if (errs.length) {
175
183
  // Name the location of each required field: a "missing required input" on a
176
184
  // path parameter reads very differently from one on a body field, and the
@@ -135,7 +135,14 @@ export declare function eventParamsFor(subscription: unknown, opts: EventCheckOp
135
135
  * (compile repeats it as the hard failure), with the recorded answer looked up
136
136
  * under the event-form node type — the form `prepare` stores.
137
137
  */
138
- export declare function checkEventResolution(subscription: unknown, where: string, kind: 'trigger' | 'event', opts: EventCheckOpts, error: (code: string, message: string, suggestion?: string) => void, warn: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields): void;
138
+ export declare function checkEventResolution(subscription: unknown, where: string, kind: 'trigger' | 'event', opts: EventCheckOpts, error: (code: string, message: string, suggestion?: string) => void, warn: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields,
139
+ /**
140
+ * Whether to report what `compile` refuses — an event the library cannot
141
+ * resolve (EVENT_UNKNOWN), an unverified BYOA connection, an unresolved
142
+ * `where` lookup. Flow's `check` passes `false`: it runs its serializer,
143
+ * which refuses each under the same code.
144
+ */
145
+ compileRefusals?: boolean): void;
139
146
  /**
140
147
  * The compile-time refusal of a GENERIC event subscription that names no object,
141
148
  * or `undefined` when the resolved event is fine.
@@ -167,4 +174,10 @@ export declare function genericEventObjectRefusal(rc: {
167
174
  }, sub: {
168
175
  where?: unknown;
169
176
  }, objectOption: (object: string) => string): string | undefined;
170
- export declare function checkEventSubscription(subscription: unknown, where: string, at: (code: string, message: string, suggestion?: string) => void, warn?: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields): void;
177
+ export declare function checkEventSubscription(subscription: unknown, where: string, at: (code: string, message: string, suggestion?: string) => void, warn?: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields,
178
+ /**
179
+ * Whether to report what `compile` refuses — a generic event with no object,
180
+ * a filter leaf it cannot lower, an ambiguous backslash. Flow's `check`
181
+ * passes `false`: it runs its serializer, which refuses each under the same code.
182
+ */
183
+ compileRefusals?: boolean): void;
@@ -77,7 +77,14 @@ export function eventParamsFor(subscription, opts) {
77
77
  * (compile repeats it as the hard failure), with the recorded answer looked up
78
78
  * under the event-form node type — the form `prepare` stores.
79
79
  */
80
- export function checkEventResolution(subscription, where, kind, opts, error, warn, eventParams) {
80
+ export function checkEventResolution(subscription, where, kind, opts, error, warn, eventParams,
81
+ /**
82
+ * Whether to report what `compile` refuses — an event the library cannot
83
+ * resolve (EVENT_UNKNOWN), an unverified BYOA connection, an unresolved
84
+ * `where` lookup. Flow's `check` passes `false`: it runs its serializer,
85
+ * which refuses each under the same code.
86
+ */
87
+ compileRefusals = true) {
81
88
  const sub = subscription;
82
89
  if (!sub || typeof sub.connector !== 'string' || typeof sub.event !== 'string'
83
90
  || sub.connector === '' || sub.event === '')
@@ -97,11 +104,12 @@ export function checkEventResolution(subscription, where, kind, opts, error, war
97
104
  ...(typeof sub.object === 'string' ? { object: sub.object } : {}),
98
105
  });
99
106
  if (unresolved !== undefined) {
100
- error('EVENT_UNKNOWN', `${where}: ${unresolved}`);
107
+ if (compileRefusals)
108
+ error('EVENT_UNKNOWN', `${where}: ${unresolved}`);
101
109
  return;
102
110
  }
103
111
  const object = typeof sub.object === 'string' && sub.object !== '' ? sub.object : undefined;
104
- const byoa = opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
112
+ const byoa = compileRefusals && opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
105
113
  if (byoa)
106
114
  error(EVENT_BYOA_UNVERIFIED, byoa);
107
115
  if (eventParams?.objectUnprepared && object) {
@@ -126,7 +134,7 @@ export function checkEventResolution(subscription, where, kind, opts, error, war
126
134
  const resolutions = opts.resolutions ?? {};
127
135
  const scope = sub.where && typeof sub.where === 'object'
128
136
  ? Object.entries(sub.where) : [];
129
- for (const [key, value] of scope) {
137
+ for (const [key, value] of compileRefusals ? scope : []) {
130
138
  if (!isLookupToken(value))
131
139
  continue;
132
140
  if (resolvedEventValue(value, resolutions) !== undefined)
@@ -242,7 +250,14 @@ export function genericEventObjectRefusal(rc, sub, objectOption) {
242
250
  : '')
243
251
  + `\`uip is triggers objects ${rc.connectorKey} ${String(op.eventOperation ?? '<EVENT>')} --connection-id <id>\` lists the objects.`;
244
252
  }
245
- export function checkEventSubscription(subscription, where, at, warn, eventParams) {
253
+ export function checkEventSubscription(subscription, where, at, warn, eventParams,
254
+ /**
255
+ * Whether to report what `compile` refuses — a generic event with no object,
256
+ * a filter leaf it cannot lower, an ambiguous backslash. Flow's `check`
257
+ * passes `false`: it runs its serializer, which refuses each under the same code.
258
+ */
259
+ compileRefusals = true) {
260
+ const refuse = compileRefusals ? at : () => { };
246
261
  const sub = subscription;
247
262
  if (!sub || typeof sub !== 'object') {
248
263
  at('EVENT_NO_SUBSCRIPTION', `${where} has no event subscription. Pass one: `
@@ -276,7 +291,7 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
276
291
  for (const [path, value] of scopeEntries.flatMap(([k, v]) => scopeStrings(v, k))) {
277
292
  const ambiguous = jmesStringProblem(value);
278
293
  if (ambiguous)
279
- at('EVENT_FILTER_BACKSLASH', `${where}'s \`where.${path}\`: ${ambiguous}`);
294
+ refuse('EVENT_FILTER_BACKSLASH', `${where}'s \`where.${path}\`: ${ambiguous}`);
280
295
  }
281
296
  // The OBJECT half of the contract. A GENERIC event (`record-created` on Data
282
297
  // Fabric, Salesforce, ServiceNow, Jira, …) fires for ONE object of the connection
@@ -286,7 +301,7 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
286
301
  // built in, so an `object` there names a choice that does not exist.
287
302
  if (eventParams?.generic) {
288
303
  if (object === undefined) {
289
- at('EVENT_GENERIC_NO_OBJECT', `${where} subscribes to a GENERIC event: \`${String(sub.connector)}\` \`${String(sub.event)}\` fires for ONE `
304
+ refuse('EVENT_GENERIC_NO_OBJECT', `${where} subscribes to a GENERIC event: \`${String(sub.connector)}\` \`${String(sub.event)}\` fires for ONE `
290
305
  + `object of the connection (an entity, a table, a custom object) and the library carries no default. `
291
306
  + `Name it with \`object\`.`
292
307
  + (scopeObject !== undefined
@@ -347,28 +362,28 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
347
362
  filters.forEach((f, i) => {
348
363
  const leaf = f;
349
364
  if (!leaf || typeof leaf.field !== 'string' || leaf.field === '') {
350
- at('EVENT_FILTER_NO_FIELD', `${where}'s filters[${i}] has no \`field\` — name a field on the event payload, e.g. 'subject'.`);
365
+ refuse('EVENT_FILTER_NO_FIELD', `${where}'s filters[${i}] has no \`field\` — name a field on the event payload, e.g. 'subject'.`);
351
366
  }
352
367
  const { keys, read } = readEventFilter(leaf);
353
368
  if (keys.length === 0) {
354
- at('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] has no operator. Write exactly one of ${operators} with its value — `
369
+ refuse('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] has no operator. Write exactly one of ${operators} with its value — `
355
370
  + `\`contains\` is a case-sensitive substring test, the same one Integration Service applies `
356
371
  + `server-side; \`lessThan\`/\`greaterThan\` order a number or an ISO date.`, `{ field: '${typeof leaf?.field === 'string' ? leaf.field : 'subject'}', contains: '<text>' }`);
357
372
  }
358
373
  else if (keys.length > 1) {
359
- at('EVENT_FILTER_MANY_OPERATORS', `${where}'s filters[${i}] carries ${keys.length} operators (${keys.join(', ')}); a condition has exactly `
374
+ refuse('EVENT_FILTER_MANY_OPERATORS', `${where}'s filters[${i}] carries ${keys.length} operators (${keys.join(', ')}); a condition has exactly `
360
375
  + `one. Write two conditions instead — they are ANDed.`);
361
376
  }
362
377
  else if (!read) {
363
- at('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] gives \`${keys[0]}\` a value that is not a string, number or boolean.`);
378
+ refuse('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] gives \`${keys[0]}\` a value that is not a string, number or boolean.`);
364
379
  }
365
380
  else {
366
381
  const ambiguous = typeof read.value === 'string' ? jmesStringProblem(read.value) : undefined;
367
382
  const problem = eventFilterProblem(read);
368
383
  if (ambiguous)
369
- at('EVENT_FILTER_BACKSLASH', `${where}'s filters[${i}] on \`${read.field}\`: ${ambiguous}`);
384
+ refuse('EVENT_FILTER_BACKSLASH', `${where}'s filters[${i}] on \`${read.field}\`: ${ambiguous}`);
370
385
  else if (problem)
371
- at('EVENT_FILTER_BAD_VALUE', `${where}'s filters[${i}] on \`${read.field}\`: ${problem}`);
386
+ refuse('EVENT_FILTER_BAD_VALUE', `${where}'s filters[${i}] on \`${read.field}\`: ${problem}`);
372
387
  }
373
388
  });
374
389
  }
@@ -87,9 +87,18 @@ type EdgeLike = {
87
87
  * bare `completed` never is, unless the task asked for it with
88
88
  * `legacyCompleted: true` (`legacyCompleted` here).
89
89
  */
90
- export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean): {
90
+ export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean,
91
+ /**
92
+ * How the task's main path leaves it, when the caller knows: on its primary
93
+ * outcome (`{ primary }`, a task routed per outcome), or on every outcome
94
+ * (`'every-outcome'`). It picks the remedy a `completed` refusal names.
95
+ */
96
+ routing?: {
97
+ primary: string;
98
+ } | 'every-outcome'): {
91
99
  code: 'HITL_COMPLETED_PORT_GONE' | 'HITL_OUTCOME_PORT_UNKNOWN';
92
100
  message: string;
101
+ suggestion?: string;
93
102
  } | undefined;
94
103
  /**
95
104
  * The post-emit half of the guard: every edge of an emitted scope, whatever
@@ -183,7 +183,13 @@ function outcomeIdsOf(node) {
183
183
  * bare `completed` never is, unless the task asked for it with
184
184
  * `legacyCompleted: true` (`legacyCompleted` here).
185
185
  */
186
- export function hitlExitViolation(node, port, legacyCompleted = false) {
186
+ export function hitlExitViolation(node, port, legacyCompleted = false,
187
+ /**
188
+ * How the task's main path leaves it, when the caller knows: on its primary
189
+ * outcome (`{ primary }`, a task routed per outcome), or on every outcome
190
+ * (`'every-outcome'`). It picks the remedy a `completed` refusal names.
191
+ */
192
+ routing) {
187
193
  if (!OUTCOME_HANDLE_NODE_TYPES.has(String(node.type)))
188
194
  return undefined;
189
195
  const ids = outcomeIdsOf(node);
@@ -193,17 +199,26 @@ export function hitlExitViolation(node, port, legacyCompleted = false) {
193
199
  if (port === 'completed') {
194
200
  if (legacyCompleted)
195
201
  return undefined;
202
+ const ports = ids.map((i) => `"outcome-${i}"`).join(', ');
196
203
  return {
197
204
  code: 'HITL_COMPLETED_PORT_GONE',
198
- message: `human task "${String(node.id)}" has outcomes (${declared}), so the product draws no `
199
- + `'completed' handle on it — an edge there is remapped to the primary outcome and every other `
200
- + `outcome loses its path.`,
205
+ message: `Port "completed" does not exist on human task "${String(node.id)}". It has outcomes, so the `
206
+ + `product draws one handle per outcome (${ports}) and no 'completed' — an edge there is remapped to `
207
+ + `the primary outcome and the others lose their path.`
208
+ + (routing === 'every-outcome' ? ' .step() after this task already continues every outcome to the next step.'
209
+ : routing !== undefined ? ` The FIRST outcome ("${routing.primary}") is what continues the main path.` : ''),
210
+ ...(routing === 'every-outcome'
211
+ ? { suggestion: "drop the 'completed' route — .step() continues every outcome — or route each outcome with .stepSwitch" }
212
+ : routing !== undefined
213
+ ? { suggestion: `route the outcomes (.stepToList('outcome-${ids[1] ?? ids[0]}', …)), or continue every outcome to the next step with outcomePorts: false` }
214
+ : {}),
201
215
  };
202
216
  }
203
217
  if (port.startsWith('outcome-') && !ids.includes(port.slice('outcome-'.length))) {
204
218
  return {
205
219
  code: 'HITL_OUTCOME_PORT_UNKNOWN',
206
- message: `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}.`,
220
+ message: `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}. `
221
+ + `Ids are the outcome names slugified (lowercase, non-alphanumerics to '-').`,
207
222
  };
208
223
  }
209
224
  return undefined;
@@ -209,6 +209,19 @@ export declare function resolvedEventValue(token: LookupToken, resolutions: Look
209
209
  * @returns A diagnostic whose last line can be pasted into a shell.
210
210
  */
211
211
  export declare function unresolvedLookupMessage(token: LookupToken): string;
212
+ /**
213
+ * The message for every lookup one node still owes, with the ONE `prepare` that
214
+ * records them all (it takes every `--resolve` in one invocation). For instance:
215
+ *
216
+ * inputs `channel` (profile.email="ada@x.com"), `send_as` (name="bot") are unresolved lookups. …
217
+ * npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user \
218
+ * --resolve channel:profile.email=ada@x.com \
219
+ * --resolve send_as:name=bot
220
+ *
221
+ * @param tokens - The node's unresolved lookups, at least one, all of one node type.
222
+ * @returns A message whose last lines can be pasted into a shell.
223
+ */
224
+ export declare function unresolvedLookupsMessage(tokens: readonly LookupToken[]): string;
212
225
  type RawReference = {
213
226
  objectName?: string;
214
227
  path?: string;
@@ -230,6 +230,32 @@ export function unresolvedLookupMessage(token) {
230
230
  // is a glob and a real name has spaces, and the last line is meant to paste.
231
231
  ` --resolve ${resolveFlagArg(field, by, String(value))}`);
232
232
  }
233
+ /**
234
+ * The message for every lookup one node still owes, with the ONE `prepare` that
235
+ * records them all (it takes every `--resolve` in one invocation). For instance:
236
+ *
237
+ * inputs `channel` (profile.email="ada@x.com"), `send_as` (name="bot") are unresolved lookups. …
238
+ * npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user \
239
+ * --resolve channel:profile.email=ada@x.com \
240
+ * --resolve send_as:name=bot
241
+ *
242
+ * @param tokens - The node's unresolved lookups, at least one, all of one node type.
243
+ * @returns A message whose last lines can be pasted into a shell.
244
+ */
245
+ export function unresolvedLookupsMessage(tokens) {
246
+ if (tokens.length === 1)
247
+ return unresolvedLookupMessage(tokens[0]);
248
+ const { nodeType } = tokens[0].__lookup;
249
+ const rest = nodeType.replace(/^uipath\.connector\.(?:trigger\.|event\.)?/, '');
250
+ const dot = rest.lastIndexOf('.');
251
+ const key = dot > 0 ? rest.slice(0, dot) : rest;
252
+ const action = dot > 0 ? rest.slice(dot + 1) : rest;
253
+ const named = tokens.map((t) => `\`${t.__lookup.field}\` (${t.__lookup.by}=${JSON.stringify(t.__lookup.value)})`);
254
+ const args = tokens.map((t) => ` --resolve ${resolveFlagArg(t.__lookup.field, t.__lookup.by, String(t.__lookup.value))}`);
255
+ return `inputs ${named.join(', ')} are unresolved lookups. `
256
+ + `Compile does not make network calls, so the values have to be resolved first:\n`
257
+ + ` ${prepareCommand(key, action)} \\\n${args.join(' \\\n')}`;
258
+ }
233
259
  // ─── Classification, mirroring `_lookups.py` ─────────────────────────────────
234
260
  //
235
261
  // The generator decides which fields get a helper; `check` has to agree, or it
@@ -55,6 +55,7 @@ export declare const REFUSALS: {
55
55
  readonly CARRIED_DEFINITION_CONFLICT: "lowering";
56
56
  readonly CARRIED_DEFINITION_INVALID: "lowering";
57
57
  readonly CEQL_UNPARSEABLE: "lowering";
58
+ readonly COMPILE_FAILED: "lowering";
58
59
  readonly CONNECTION_CONNECTOR_CONFLICT: "draft";
59
60
  readonly CONNECTION_STUB: "lint";
60
61
  readonly CONNECTOR_IMPORT_NOT_A_FILE: "lowering";
@@ -209,6 +210,7 @@ export declare const REFUSALS: {
209
210
  readonly HITL_OUTCOME_ACTION: "lint";
210
211
  readonly HITL_OUTCOME_BAD_ID: "lint";
211
212
  readonly HITL_OUTCOME_NO_NAME: "lint";
213
+ readonly HITL_OUTCOME_PORTS_NONE: "lowering";
212
214
  readonly HITL_OUTCOME_PORTS_OFF: "lowering";
213
215
  readonly HITL_OUTCOME_PORTS_VARIANT: "lowering";
214
216
  readonly HITL_OUTCOME_PORT_MISPLACED: "lint";