@uipath/maestro-builder-sdk 6.63.0 → 6.65.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.
@@ -340,15 +340,15 @@ export function checkInlineAgent(inputs, ctx) {
340
340
  //
341
341
  // An artifact that compiles and can never validate is the worst of the two
342
342
  // failures, so this is said here — before the emit — rather than left to a rung.
343
- // It is an ERROR because there is no spelling that works today, and `--no-check`
344
- // remains the escape for a tenant whose manifest does declare the handle.
343
+ // It is an ERROR because there is no spelling that works today. `compile` does
344
+ // not run check, so a tenant whose manifest does declare the handle still compiles it.
345
345
  if (inputs?.memory !== undefined) {
346
346
  at('AGENT_MEMORY_NOT_AUTHORABLE', `Inline agent step "${step}" attaches \`memory\`, which the platform cannot resolve: the tenant's `
347
347
  + `\`uipath.agent.autonomous\` manifest declares no \`memory\` handle (only escalation, context, tool, `
348
348
  + `success, error) and the registry serves no \`uipath.agent.resource.memory.*\` node type. The flow `
349
349
  + `compiles and then fails \`uip maestro flow validate\` as RetryWillNotFix. Drop \`memory\`; for `
350
350
  + `retrieval over a knowledge source use a context index instead (\`tools\`/context grounding). `
351
- + `Pass --no-check only on a tenant whose manifest does declare the handle.`);
351
+ + `On a tenant whose manifest does declare the handle, ignore this finding: \`compile\` does not run check.`);
352
352
  }
353
353
  // A context `query` is what the RUNTIME searches with, and it is not an
354
354
  // expression slot. A plain string is the model-invoked `dynamic` variant: the
@@ -468,7 +468,7 @@ export function checkInlineAgent(inputs, ctx) {
468
468
  + `of the prompt) with one line per field, in the platform's own wording: "${rewrite}" Declare a field \`'object'\` or `
469
469
  + `\`'array'\` if it should carry structure. \`uip maestro flow validate\` accepts this prompt, so `
470
470
  + `nothing later catches it. If this is a false positive (the prompt only mentions JSON as data), `
471
- + `compile with --no-check.`);
471
+ + `ignore this finding: \`compile\` does not run check.`);
472
472
  }
473
473
  }
474
474
  // The prompts-vs-variables agreement, in both directions. Both prompts are
@@ -270,6 +270,40 @@ function liftConnector(node, ctx) {
270
270
  missing,
271
271
  };
272
272
  }
273
+ /**
274
+ * Whether a connector operation node is a draft the canvas saved, by what the
275
+ * file itself shows: no connection or folder (`BINDING_MISSING`), or a field its
276
+ * own definition marks `required` that the node gives no value
277
+ * (`CONNECTOR_INPUT_MISSING`). For instance a Slack send saved before a
278
+ * connection was picked has `detail.connectionId: null`. An operation with no
279
+ * object picked is not one: `compile` can only stand in for it, so it is never
280
+ * written either way.
281
+ */
282
+ function isConnectorDraft(node, definition) {
283
+ const detail = (node.inputs?.detail ?? {});
284
+ const label = (v) => (typeof v === 'string' ? v : undefined);
285
+ if (missingBindingLabels({ connection: label(detail.connectionId), folder: label(detail.connectionFolderKey) }).length > 0)
286
+ return true;
287
+ const fields = definition?.inputDefinition?.fields;
288
+ if (!Array.isArray(fields))
289
+ return false;
290
+ const slots = ['bodyParameters', 'queryParameters', 'pathParameters']
291
+ .map((slot) => detail[slot])
292
+ .filter((m) => !!m && typeof m === 'object' && !Array.isArray(m));
293
+ const parts = Array.isArray(detail.multipartParameters) ? detail.multipartParameters : [];
294
+ const given = (name) => parts.some((p) => p?.name === name) || slots.some((m) => {
295
+ if (Object.prototype.hasOwnProperty.call(m, name))
296
+ return true;
297
+ let at = m;
298
+ for (const seg of name.split('.')) {
299
+ if (!at || typeof at !== 'object' || !Object.prototype.hasOwnProperty.call(at, seg))
300
+ return false;
301
+ at = at[seg];
302
+ }
303
+ return true;
304
+ });
305
+ return fields.some((f) => f?.required === true && typeof f.name === 'string' && !given(f.name));
306
+ }
273
307
  function printConnector(l, ctx) {
274
308
  // A present input whose value is `undefined` (the inputs check accepts one, and
275
309
  // serialize keeps it in memory) prints as `undefined`, as it did before the port.
@@ -284,7 +318,7 @@ function printConnector(l, ctx) {
284
318
  ['object', optional(l.object)],
285
319
  ],
286
320
  ...(l.missing.length > 0
287
- ? { comment: `TODO: bind ${l.missing.join(' and ')}; compile refuses this node until it has both labels (BINDING_MISSING)` }
321
+ ? { comment: `TODO: bind ${l.missing.join(' and ')}; check refuses this node until it has both labels (BINDING_MISSING)` }
288
322
  : {}),
289
323
  };
290
324
  }
@@ -302,6 +336,7 @@ const connector10 = {
302
336
  },
303
337
  serialize: (spec, ctx) => serializeConnector(spec, ctx),
304
338
  deserialize: (node, ctx) => liftConnector(node, ctx),
339
+ isDraft: (node, definition) => isConnectorDraft(node, definition),
305
340
  print: (spec, ctx) => printConnector(spec, ctx),
306
341
  check: (spec, ctx) => ctx.connector(spec),
307
342
  // Every Expr in the inputs, nested ones included, resolves against the flow's names.
@@ -29,6 +29,8 @@ export type DraftCode = BindingRefusalCode | typeof CONNECTOR_INPUT_MISSING | 'C
29
29
  export interface DraftFinding {
30
30
  /** The emitted node's id. */
31
31
  nodeId: string;
32
+ /** The step whose node it is. Set by a carried-drafts compile ({@link SerializeOpts.carriedDrafts}). */
33
+ step?: string;
32
34
  /** A {@link DraftCode}, or a node-type package's own code reported through `ctx.draft`. */
33
35
  code: DraftCode | string;
34
36
  /** The sentence the strict compile refuses with. */
@@ -71,6 +73,28 @@ export interface SerializeOpts {
71
73
  * ordinary compile), behaviour is unchanged.
72
74
  */
73
75
  draft?: (finding: DraftFinding) => void;
76
+ /**
77
+ * Carried drafts, for `compile` (docs/CHECK_COMPILE_CONTRACT_DESIGN.md D2).
78
+ *
79
+ * A step marked `{ draft: true }` ({@link StepOptions.draft}) that the
80
+ * ordinary compile refuses only for draft findings ({@link DraftCode}: a
81
+ * connector with no connection, an empty required input) is written anyway
82
+ * when the flow CARRIES the definition of the node it emits
83
+ * (`.definitions([...])`). Decompile sets both for a canvas draft node. The
84
+ * node is written with that definition and the inputs it has, and each
85
+ * finding is reported here. A draft with no flag, or no carried definition,
86
+ * or one the node type can only stand in for (a generic operation with no
87
+ * object), is refused exactly as without this option. So is any other
88
+ * refusal. A flow with no draft compiles byte for byte as without it.
89
+ *
90
+ * For instance, a decompiled canvas flow whose Slack send has no `messageToSend`
91
+ * compiles with a `CONNECTOR_INPUT_MISSING` finding here, and a second Slack
92
+ * send written by hand with no `messageToSend` (no flag) is refused, though
93
+ * the flow carries the definition of that operation.
94
+ *
95
+ * Ignored when {@link draft} is given (merge's compile tolerates every draft).
96
+ */
97
+ carriedDrafts?: (finding: DraftFinding) => void;
74
98
  /**
75
99
  * Collect mode, for `check` (docs/CHECK_COMPILE_CONTRACT_DESIGN.md §3.5).
76
100
  *
package/dist/serialize.js CHANGED
@@ -2542,6 +2542,76 @@ function serializeFlow(built, opts) {
2542
2542
  }
2543
2543
  return [{ node: id, port }];
2544
2544
  }
2545
+ /**
2546
+ * Run a node type's `serialize`, and in a carried-drafts compile
2547
+ * ({@link SerializeOpts.carriedDrafts}) try a refused `{ draft: true }` step
2548
+ * once more as a draft. The ordinary emission runs first, so a flow with no draft compiles
2549
+ * exactly as it would without the option. When it throws a refusal and the flow
2550
+ * carries any definition, what that emission bound is undone and the node is
2551
+ * emitted again with `drafting` on. That second contribution is returned with
2552
+ * the first refusal as `strict` when it reported a draft finding; the caller
2553
+ * writes it only with a carried definition, and throws `strict` otherwise. A
2554
+ * second emission that throws, or reports no finding, ends in `strict` too, so
2555
+ * a refusal that is not a draft reads as it always has.
2556
+ */
2557
+ function serializeCarriedDraft(impl, spec, ctx, scope, id, bound, stepOptions, findings, enableDrafting, warnings) {
2558
+ if (!opts.carriedDrafts || opts.draft || opts.collect || carriedDefs.size === 0 || stepOptions?.draft !== true) {
2559
+ return { contribution: impl.serialize(spec, ctx) };
2560
+ }
2561
+ const mark = {
2562
+ resources: new Set(resourceBindings.keys()), sidecars: sidecars.length,
2563
+ agentSchemas: scope.agentSchemas.length, unbound: unboundDrafts.has(id),
2564
+ };
2565
+ // Each emission's warnings are held, so a node emitted twice reports only
2566
+ // the warnings of the emission that is kept (or of the refused one).
2567
+ const flush = (held) => { for (const w of held)
2568
+ opts.warn?.(w); };
2569
+ const first = [];
2570
+ warnings.held = first;
2571
+ try {
2572
+ const contribution = impl.serialize(spec, ctx);
2573
+ warnings.held = undefined;
2574
+ flush(first);
2575
+ return { contribution };
2576
+ }
2577
+ catch (error) {
2578
+ warnings.held = undefined;
2579
+ if (!(error instanceof CompileRefusal)) {
2580
+ flush(first);
2581
+ throw error;
2582
+ }
2583
+ for (const key of [...resourceBindings.keys()])
2584
+ if (!mark.resources.has(key))
2585
+ resourceBindings.delete(key);
2586
+ for (const f of sidecars.splice(mark.sidecars)) {
2587
+ if (!sidecars.some((kept) => kept.path === f.path))
2588
+ explicitSidecars.delete(f.path);
2589
+ }
2590
+ scope.agentSchemas.length = mark.agentSchemas;
2591
+ if (!mark.unbound)
2592
+ unboundDrafts.delete(id);
2593
+ bound.length = 0;
2594
+ enableDrafting();
2595
+ const second = [];
2596
+ warnings.held = second;
2597
+ let contribution;
2598
+ try {
2599
+ contribution = impl.serialize(spec, ctx);
2600
+ }
2601
+ catch {
2602
+ flush(first);
2603
+ throw error;
2604
+ }
2605
+ finally {
2606
+ warnings.held = undefined;
2607
+ }
2608
+ if (findings.length === 0) {
2609
+ flush(first);
2610
+ throw error;
2611
+ }
2612
+ return { contribution, strict: error, strictWarnings: first, draftWarnings: second };
2613
+ }
2614
+ }
2545
2615
  /**
2546
2616
  * Lower a step whose kind is a registered node type (flow-node-registry.ts):
2547
2617
  * the node type serializes its spec through a context, and this owns the rest —
@@ -2572,6 +2642,19 @@ function serializeFlow(built, opts) {
2572
2642
  rendered.add(copy);
2573
2643
  return copy;
2574
2644
  };
2645
+ // Merge's compile drafts every node; `compile` (carriedDrafts) drafts a node
2646
+ // only on a second try, after the ordinary emission refused it.
2647
+ let drafting = opts.draft !== undefined;
2648
+ const carriedFindings = [];
2649
+ // Where this step's warnings go: `opts.warn`, or held while a carried-drafts
2650
+ // compile does not yet know which emission it keeps (serializeCarriedDraft).
2651
+ const warnings = {};
2652
+ const bindingOpts = opts.warn
2653
+ ? { ...opts, warn: (w) => { if (warnings.held)
2654
+ warnings.held.push(w);
2655
+ else
2656
+ opts.warn(w); } }
2657
+ : opts;
2575
2658
  const ctx = {
2576
2659
  step: step.name,
2577
2660
  nodeId: id,
@@ -2598,7 +2681,7 @@ function serializeFlow(built, opts) {
2598
2681
  ? resolved.carried
2599
2682
  : bundledDefinition(typeName, v) ?? carriedFor(typeName, v)),
2600
2683
  bindings: resourceBindings,
2601
- resolveBinding: ((name, attribute, at, target) => resolveBinding(opts, name, attribute, at, target)),
2684
+ resolveBinding: ((name, attribute, at, target) => resolveBinding(bindingOpts, name, attribute, at, target)),
2602
2685
  bindConnection: (c) => {
2603
2686
  const { entries } = connectionResourceBindings({
2604
2687
  def: { model: c.model },
@@ -2625,15 +2708,26 @@ function serializeFlow(built, opts) {
2625
2708
  const out = renderDeep(value, scope.rename, rendered);
2626
2709
  return out !== null && typeof out === 'object' ? fileCopy(out) : out;
2627
2710
  },
2628
- drafting: opts.draft !== undefined,
2711
+ get drafting() { return drafting; },
2629
2712
  draft: (code, message) => {
2630
2713
  if (code === 'BINDING_MISSING')
2631
2714
  unboundDrafts.add(id);
2632
- opts.draft?.({ nodeId: id, code: code, message });
2715
+ if (opts.draft)
2716
+ opts.draft({ nodeId: id, code: code, message });
2717
+ else
2718
+ carriedFindings.push({ nodeId: id, step: step.name, code: code, message });
2633
2719
  },
2634
2720
  };
2635
2721
  const bound = [];
2636
- const contribution = impl.serialize(spec, ctx);
2722
+ const { contribution, strict, strictWarnings, draftWarnings } = serializeCarriedDraft(impl, spec, ctx, scope, id, bound, step.options, carriedFindings, () => { drafting = true; }, warnings);
2723
+ // A carried draft refused after all reports the warnings of the refused emission.
2724
+ const refuse = () => {
2725
+ for (const w of strictWarnings ?? [])
2726
+ opts.warn?.(w);
2727
+ throw strict;
2728
+ };
2729
+ if (strict !== undefined && contribution.standIn !== undefined)
2730
+ refuse();
2637
2731
  if (contribution.standIn !== undefined) {
2638
2732
  // A draft stand-in: the node as the draft has it, with no definition to
2639
2733
  // place it against (see FlowNodeContribution.standIn).
@@ -2677,6 +2771,22 @@ function serializeFlow(built, opts) {
2677
2771
  const origin = entry.family.package;
2678
2772
  def = { ...def, source: { package: origin.name, ...(origin.version !== undefined ? { version: origin.version } : {}) } };
2679
2773
  }
2774
+ // A draft `compile` writes: only a step marked `{ draft: true }` (checked in
2775
+ // serializeCarriedDraft) with the definition the flow carries for the node it
2776
+ // emits (decompile's copy of the canvas file's entry), else refused as the
2777
+ // ordinary emission refused it (SerializeOpts.carriedDrafts).
2778
+ if (strict !== undefined) {
2779
+ const carried = typeof def?.nodeType === 'string' && typeof def?.version === 'string'
2780
+ ? carriedFor(def.nodeType, def.version)
2781
+ : undefined;
2782
+ if (carried === undefined)
2783
+ refuse();
2784
+ def = carried;
2785
+ for (const w of draftWarnings ?? [])
2786
+ opts.warn?.(w);
2787
+ for (const f of carriedFindings)
2788
+ opts.carriedDrafts?.(f);
2789
+ }
2680
2790
  // A type that owns a family of names (`typeNamePrefix`) is placed under the
2681
2791
  // name its definition carries — `uipath.core.function.<key>`, not the prefix.
2682
2792
  // The bare prefix is allowed: an empty `key` builds it, check refuses that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.63.0",
3
+ "version": "6.65.0",
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": "a4d62ecfe71de0865a81621edbf878ae84d0152b"
103
+ "gitref": "cef5c166db15d92f544e04c9817e280551ecc9dd"
104
104
  }