@uipath/maestro-builder-sdk 6.8.5 → 6.9.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.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { readFileSync, existsSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
+ import { prepareCommand } from './cli-spelling.js';
11
12
  import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
12
13
  import { lookupSpecOf } from './lookups.js';
13
14
  import { loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
@@ -245,11 +246,18 @@ export class Library {
245
246
  // own `registry get` answers for both forms (see `deriveTriggerForm`).
246
247
  if (kind !== 'trigger')
247
248
  throw error;
249
+ const siblingType = `uipath.connector.event.${key}.${event}`;
248
250
  let sibling;
249
251
  try {
250
- sibling = this.resolveType(`uipath.connector.event.${key}.${event}`, version, objectName);
252
+ sibling = this.resolveType(siblingType, version, objectName);
251
253
  }
252
- catch {
254
+ catch (siblingError) {
255
+ // The wait form IS in the library and refused the request itself — a
256
+ // GENERIC event with no `object`, or a disagreeing one. That refusal is
257
+ // the true answer; replacing it with the start form's "not in library"
258
+ // told the author the connector was missing.
259
+ if (this.carries(siblingType, version))
260
+ throw siblingError;
253
261
  throw error;
254
262
  }
255
263
  rc = deriveTriggerForm(sibling, nodeType);
@@ -528,7 +536,7 @@ export class Library {
528
536
  + `\`…connector.trigger.…\` starts a flow, \`…connector.event.…\` waits mid-flow.`
529
537
  : '';
530
538
  throw new Error(`connector not in library: ${nodeType}${version ? '@' + version : ''}.${hint}`
531
- + this.describeMiss(nodeType));
539
+ + this.describeMiss(nodeType, version, objectName));
532
540
  }
533
541
  /**
534
542
  * Turn a lookup miss into something actionable: which operations this connector
@@ -541,7 +549,7 @@ export class Library {
541
549
  * `connector not in library: …send-mail-v2.` said nothing that would reveal
542
550
  * that. It resorted to grepping the library's JSON out of `node_modules`.
543
551
  */
544
- describeMiss(nodeType) {
552
+ describeMiss(nodeType, version, objectName) {
545
553
  // `uipath.connector.<key>.<op>`, or `uipath.connector.(trigger|event).<key>.<ev>`
546
554
  // for the two event namespaces. No connector key contains a dot.
547
555
  const parts = nodeType.split('.');
@@ -550,23 +558,12 @@ export class Library {
550
558
  const asked = (eventNs ? parts.slice(4) : parts.slice(3)).join('.');
551
559
  if (!key)
552
560
  return '';
553
- const prefix = eventNs ? `uipath.connector.${parts[2]}.${key}.` : `uipath.connector.${key}.`;
554
- const ops = [...new Set(this.layers.flatMap((l) => l.entries
555
- .map((e) => String(e.nodeType ?? ''))
556
- .filter((t) => t.startsWith(prefix))
557
- .map((t) => t.slice(prefix.length))))].sort();
558
561
  const where = ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.`;
559
- if (!ops.length) {
560
- const keys = [...new Set(this.layers.flatMap((l) => l.entries.map((e) => {
561
- const p = String(e.nodeType ?? '').split('.');
562
- return (p[2] === 'trigger' || p[2] === 'event' ? p[3] : p[2]) ?? '';
563
- })))].filter(Boolean).sort();
564
- const near = nearest(key, keys);
565
- return ` No connector "${key}" in the library (${keys.length} indexed).`
566
- + (near && near !== key ? ` Closest: ${near}.` : '')
567
- + where
568
- + ' If the connector is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
569
- }
562
+ if (eventNs)
563
+ return this.describeEventMiss(parts[2], key, asked, where, version, objectName);
564
+ const ops = this.opsUnder(`uipath.connector.${key}.`);
565
+ if (!ops.length)
566
+ return this.noConnector(key, where);
570
567
  const near = asked ? nearest(asked, ops) : undefined;
571
568
  const listed = ops.length <= 12
572
569
  ? `Operations: ${ops.join(', ')}.`
@@ -580,6 +577,86 @@ export class Library {
580
577
  + ' — `uip maestro case spec` reports the latter, and the two can differ.'
581
578
  + ' If the operation is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
582
579
  }
580
+ /** Every direct operation id under `prefix` (no further dot), across layers, sorted and de-duplicated. */
581
+ opsUnder(prefix) {
582
+ return [...new Set(this.layers.flatMap((l) => l.entries
583
+ .map((e) => String(e.nodeType ?? ''))
584
+ .filter((t) => t.startsWith(prefix))
585
+ .map((t) => t.slice(prefix.length))
586
+ .filter((op) => op !== '' && !op.includes('.'))))].sort();
587
+ }
588
+ /** Whether any layer indexes `nodeType` (at `version`, when one is asked for). */
589
+ carries(nodeType, version) {
590
+ return this.layers.some((l) => l.entries.some((e) => e.nodeType === nodeType && (!version || e.version === version)));
591
+ }
592
+ /** The connector key is in no entry at all. */
593
+ noConnector(key, where) {
594
+ const keys = [...new Set(this.layers.flatMap((l) => l.entries.map((e) => {
595
+ const p = String(e.nodeType ?? '').split('.');
596
+ return (p[2] === 'trigger' || p[2] === 'event' ? p[3] : p[2]) ?? '';
597
+ })))].filter(Boolean).sort();
598
+ const near = nearest(key, keys);
599
+ return ` No connector "${key}" in the library (${keys.length} indexed).`
600
+ + (near && near !== key ? ` Closest: ${near}.` : '')
601
+ + where
602
+ + ' If the connector is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
603
+ }
604
+ /**
605
+ * The event half of {@link describeMiss}: which of the connector's EVENTS the
606
+ * library carries, read from BOTH namespaces.
607
+ *
608
+ * Both, because the library stores almost every event in one form only: the
609
+ * baseline snapshot of 2026-09-23 (`2a4405dd…`) has 994 `.event.` entries and 4
610
+ * `.trigger.` ones, and a start trigger resolves from its `.event.` sibling
611
+ * ({@link resolveEvent}). Reading the asked namespace alone answered `No
612
+ * connector "uipath-uipath-dataservice"` for a typo'd `onEvent` event on a
613
+ * connector whose events were all right there, and the same words for a
614
+ * connector present with eight actions and no events at all (2026-09-23 eval,
615
+ * `skill-flow-datafabric-trigger-lifecycle`, v2 commands 45/46).
616
+ */
617
+ describeEventMiss(kind, key, asked, where, version, objectName) {
618
+ const starts = this.opsUnder(`uipath.connector.trigger.${key}.`);
619
+ const waits = this.opsUnder(`uipath.connector.event.${key}.`);
620
+ // A start trigger resolves from either form; a mid-flow wait needs the `.event.` form itself.
621
+ const usable = kind === 'trigger' ? [...new Set([...starts, ...waits])].sort() : waits;
622
+ const refresh = ' If the event is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
623
+ if (usable.length) {
624
+ if (usable.includes(asked)) {
625
+ // Only a version can make a carried event miss here: every other refusal
626
+ // of a carried entry is thrown by the entry itself.
627
+ return ` Connector "${key}" carries event "${asked}", but not at version ${version ?? '(any)'}.`
628
+ + ` Drop the version, or use one the library's index.json lists for it.` + where;
629
+ }
630
+ const onlyStart = kind === 'event' && starts.includes(asked);
631
+ const near = onlyStart ? undefined : nearest(asked, usable);
632
+ const listed = usable.length <= 12
633
+ ? `Events: ${usable.join(', ')}.`
634
+ : `Events (${usable.length}, first 12): ${usable.slice(0, 12).join(', ')}, … — `
635
+ + `the full list is every \`nodeType\` under "${key}" in the library's index.json.`;
636
+ return ` Connector "${key}" is present but carries no event "${asked}"`
637
+ + (onlyStart
638
+ ? ` that can be waited on: it is here only as a start trigger (\`uipath.connector.trigger.${key}.${asked}\`), `
639
+ + `and \`waitForEvent\` needs its \`uipath.connector.event.\` form.`
640
+ : '.')
641
+ + (near ? ` Closest: ${near}.` : '')
642
+ + ` ${listed}`
643
+ + where
644
+ + refresh;
645
+ }
646
+ if (starts.length) {
647
+ return ` Connector "${key}" carries only start triggers in this library (${starts.join(', ')}), `
648
+ + `and \`waitForEvent\` needs the \`uipath.connector.event.\` form.` + where + refresh;
649
+ }
650
+ const actions = this.opsUnder(`uipath.connector.${key}.`).length;
651
+ if (!actions)
652
+ return this.noConnector(key, where);
653
+ const prepare = prepareCommand(key, asked, ...(objectName ? ['--object', objectName] : []));
654
+ return ` Connector "${key}" is present (${actions} action${actions === 1 ? '' : 's'}) but this library carries `
655
+ + `no events for it.` + where
656
+ + ` Prepare the event against a live connection: ${prepare}. If prepare reports that the registry serves `
657
+ + `no event for "${key}" either (after \`uip maestro flow registry pull --force\`, if its catalog looks stale), `
658
+ + `refreshing the library will not add one: it is built from the same catalog.`;
659
+ }
583
660
  /**
584
661
  * Resolve the op WITHIN one layer, selecting the entry by `objectName` for a
585
662
  * GENERIC operation. Returns undefined when the layer does not carry the op;
@@ -217,6 +217,14 @@ export declare abstract class FlowResource extends FlowNode<never> {
217
217
  * compile reports an input the static library does not carry; use
218
218
  * {@link onEvent} for a connector event trigger.
219
219
  *
220
+ * **Prefer the typed factory** for a node type that has one: `check` warns
221
+ * (`RAW_NODE_TYPED_FAMILY`), because `validate` cannot see an input the
222
+ * registry definition does not declare. For a `core.datafabric.*` type, a key
223
+ * the product never reads (an invented `_sortOptions` in place of `_sort`) is
224
+ * an error at a version the SDK bundles (`RAW_NODE_UNKNOWN_INPUT`); the
225
+ * canvas's snapshots, such as the `_entityFields` a related-path filter
226
+ * needs, stay legal.
227
+ *
220
228
  * @param spec - The definition to compile against, plus the node's inputs.
221
229
  * @returns A node to pass to `.step(...)`.
222
230
  */
@@ -24,6 +24,7 @@
24
24
  * built-in rules with the same severities.
25
25
  */
26
26
  import { connectorRawNodeRefusal } from './connector-raw-node.js';
27
+ import { typedFamilyRawNodeWarning, dataFabricUnknownInputs } from './typed-raw-node.js';
27
28
  /**
28
29
  * The abstract root of every definition-backed node the builder can place.
29
30
  *
@@ -113,6 +114,14 @@ export class FlowResource extends FlowNode {
113
114
  * compile reports an input the static library does not carry; use
114
115
  * {@link onEvent} for a connector event trigger.
115
116
  *
117
+ * **Prefer the typed factory** for a node type that has one: `check` warns
118
+ * (`RAW_NODE_TYPED_FAMILY`), because `validate` cannot see an input the
119
+ * registry definition does not declare. For a `core.datafabric.*` type, a key
120
+ * the product never reads (an invented `_sortOptions` in place of `_sort`) is
121
+ * an error at a version the SDK bundles (`RAW_NODE_UNKNOWN_INPUT`); the
122
+ * canvas's snapshots, such as the `_entityFields` a related-path filter
123
+ * needs, stay legal.
124
+ *
116
125
  * @param spec - The definition to compile against, plus the node's inputs.
117
126
  * @returns A node to pass to `.step(...)`.
118
127
  */
@@ -126,14 +135,29 @@ class RawNode extends FlowAction {
126
135
  super();
127
136
  this.spec = spec;
128
137
  }
129
- /** `check` surfaces the connector refusal as an error; `compile` throws it (see {@link contribute}). */
138
+ /**
139
+ * `check` surfaces the connector refusal as an error, a typed family as a
140
+ * warning, and a Data Fabric key the product never reads as an error at a
141
+ * bundled version (a warning at any other); `compile` throws the errors
142
+ * (see {@link contribute}).
143
+ */
130
144
  diagnostics() {
131
145
  const refusal = connectorRawNodeRefusal(this.spec.nodeType);
132
- return refusal ? [{ code: 'RAW_NODE_CONNECTOR', severity: 'error', message: refusal }] : [];
146
+ if (refusal)
147
+ return [{ code: 'RAW_NODE_CONNECTOR', severity: 'error', message: refusal }];
148
+ const out = [];
149
+ const unknown = dataFabricUnknownInputs(this.spec.nodeType, this.spec.version, this.spec.inputs);
150
+ if (unknown)
151
+ out.push({ code: 'RAW_NODE_UNKNOWN_INPUT', ...unknown });
152
+ const typed = typedFamilyRawNodeWarning(this.spec.nodeType);
153
+ if (typed)
154
+ out.push({ code: 'RAW_NODE_TYPED_FAMILY', severity: 'warning', message: typed });
155
+ return out;
133
156
  }
134
157
  contribute(context) {
135
158
  // compile does not run diagnostics(); refuse here too so the escape hatch
136
- // cannot emit an unconfigured connector node.
159
+ // cannot emit an unconfigured connector node. (A Data Fabric key the
160
+ // product never reads is refused by the serializer, for every contribution.)
137
161
  const refusal = connectorRawNodeRefusal(this.spec.nodeType);
138
162
  if (refusal)
139
163
  throw new Error(refusal);
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Internal: what `check` and `compile` say about a `rawNode` for a node type
3
+ * the SDK has a typed factory for, and the Data Fabric input keys it may carry.
4
+ * Not re-exported from the package entry point.
5
+ */
6
+ /** The factory to use for one node type, and the versions of it the SDK bundles. */
7
+ export interface TypedFactory {
8
+ /** The typed spelling, as the refusal names it. */
9
+ factory: string;
10
+ /**
11
+ * Every `<version>` with a `<nodeType>@<version>` entry in `core-definitions.json`.
12
+ * Listed here rather than read from the file because this module sits under
13
+ * `node-classes.ts`, which must not import `node:*`; `raw-node-typed-family.test.ts`
14
+ * pins the two equal.
15
+ */
16
+ bundled: readonly string[];
17
+ }
18
+ /**
19
+ * Every node type the SDK authors as a STEP through a typed factory, with the
20
+ * factory to use. `raw-node-typed-family.test.ts` pins the key set to the
21
+ * `flow-coverage.json` rows with `support: 'typed'` and a `bundledKey`, so a
22
+ * family that gains a factory gains its row here too.
23
+ *
24
+ * Two groups of typed rows are left out, because neither is ever a step:
25
+ * triggers (`core.trigger.*`, placed by `.trigger()`) and agent resources
26
+ * (`uipath.agent.resource.*`, attached to an agent). `decompile` keeps an
27
+ * orphaned one as a `rawNode`, and a warning on that round trip would be noise.
28
+ *
29
+ * The Data Fabric verbs name their options, because the query and write keys
30
+ * an author reaches for by hand (`_filters`, `_sort`, `fieldValues`, …) each
31
+ * have a typed spelling.
32
+ */
33
+ export declare const TYPED_FACTORIES: Readonly<Record<string, TypedFactory>>;
34
+ /**
35
+ * Every `entityConfig` key the product reads or persists on a Data Fabric node,
36
+ * across the four verbs. flow-workbench develop `eed682f19`:
37
+ *
38
+ * - services `serialization/entity-vdo.ts` `interface EntityConfig` (:229-272):
39
+ * `entityName`, `resultMode`, `_recordLimit`, `_skip`, `_sort`,
40
+ * `_selectedFieldNames`, `_filters`, `_entityFields`, `_relatedFields`,
41
+ * `_folderKey`, `_entityKey`, `_resourceKey`, `_outputSchema`;
42
+ * `CreateEntityConfig` (:719) adds `fieldValues`; `UpdateEntityConfig` (:744)
43
+ * adds `recordSource`, `readEntityNodeId`, `recordId`, `fieldUpdates`.
44
+ * - canvas `properties-panel/EntityConfigField.tsx` `EntityConfigInputs`
45
+ * (:85-110) and `entity/useEntityPanelState.ts` `EntitySelectionPatch`
46
+ * (:248-270), which the panels persist on an entity pick:
47
+ * `_entityDisplayName`, `_choiceSets`, `_folderPath`, `_entitySubType`.
48
+ * - Create/Update/DeleteEntityConfigField.tsx declare nothing beyond these.
49
+ *
50
+ * One union rather than one set per verb: a key another verb reads is at worst
51
+ * ignored, and a per-verb split would refuse a canvas-written key on a verb
52
+ * whose panel happens not to declare it. The test pins every key the typed
53
+ * factories write to this set.
54
+ */
55
+ export declare const DATAFABRIC_ENTITY_CONFIG_KEYS: ReadonlySet<string>;
56
+ /**
57
+ * A reminder, as a `warning`, that a `rawNode` stands in for a typed factory,
58
+ * or `undefined` for a type with no factory.
59
+ *
60
+ * A warning and never an error: a factory does not always write every input its
61
+ * node reads (the Data Fabric verbs do not write `_entityFields`, which a
62
+ * related-path filter needs), and nothing checks factory coverage family by
63
+ * family. So the raw node keeps compiling at every version. The one failure
64
+ * the eval archive measured, an invented key the node never reads, is refused
65
+ * by {@link dataFabricUnknownInputs} instead.
66
+ *
67
+ * @param nodeType - The definition's `nodeType`, as passed to `rawNode`.
68
+ * @returns The warning, or `undefined` for a type with no typed factory.
69
+ */
70
+ export declare function typedFamilyRawNodeWarning(nodeType: string): string | undefined;
71
+ /**
72
+ * Why a `core.datafabric.*` node's inputs carry a key the product never reads,
73
+ * or `undefined` when they do not.
74
+ *
75
+ * This is where the eval failure became possible. A raw node renders its inputs
76
+ * verbatim, and `core.datafabric.read@1.4`'s registry definition declares only
77
+ * `entityName` and `resultMode`, so `validate` cannot see any other key. Measured
78
+ * on the eval archive (2026-09-23, skill-flow-datafabric-native-contract-intake-pipeline):
79
+ * the agent wrote `_sortOptions: [{ fieldName, isDescending }]` in place of
80
+ * `_sort`, `check` printed "no issues", `validate` said Valid, and the query
81
+ * would have run unsorted. The allowed set is what the product reads
82
+ * ({@link DATAFABRIC_ENTITY_CONFIG_KEYS}), so `_entityFields` and the other
83
+ * canvas snapshots stay legal.
84
+ *
85
+ * Severity follows the version: at a bundled `<nodeType>@<version>` the key set
86
+ * is known, so the key is refused (`error`); at a version this SDK has not
87
+ * synced, a newer node could read a new key, so it is reported (`warning`).
88
+ *
89
+ * @param nodeType - The node's type.
90
+ * @param version - The version the node is emitted at.
91
+ * @param inputs - The node's inputs, before rendering.
92
+ * @returns The diagnostic, or `undefined` for a clean node or a non-Data-Fabric type.
93
+ */
94
+ export declare function dataFabricUnknownInputs(nodeType: string, version: string, inputs: unknown): {
95
+ severity: 'error' | 'warning';
96
+ message: string;
97
+ } | undefined;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Internal: what `check` and `compile` say about a `rawNode` for a node type
3
+ * the SDK has a typed factory for, and the Data Fabric input keys it may carry.
4
+ * Not re-exported from the package entry point.
5
+ */
6
+ /**
7
+ * Every node type the SDK authors as a STEP through a typed factory, with the
8
+ * factory to use. `raw-node-typed-family.test.ts` pins the key set to the
9
+ * `flow-coverage.json` rows with `support: 'typed'` and a `bundledKey`, so a
10
+ * family that gains a factory gains its row here too.
11
+ *
12
+ * Two groups of typed rows are left out, because neither is ever a step:
13
+ * triggers (`core.trigger.*`, placed by `.trigger()`) and agent resources
14
+ * (`uipath.agent.resource.*`, attached to an agent). `decompile` keeps an
15
+ * orphaned one as a `rawNode`, and a warning on that round trip would be noise.
16
+ *
17
+ * The Data Fabric verbs name their options, because the query and write keys
18
+ * an author reaches for by hand (`_filters`, `_sort`, `fieldValues`, …) each
19
+ * have a typed spelling.
20
+ */
21
+ export const TYPED_FACTORIES = {
22
+ 'core.action.http': { factory: 'http({ managed: false })', bundled: ['1.0.0'] },
23
+ 'core.action.http.v2': { factory: 'http({ managed: true })', bundled: ['2.3'] },
24
+ 'core.action.queue.create': { factory: 'queueItem()', bundled: ['1.2'] },
25
+ 'core.action.queue.create-and-wait': { factory: 'queueItem({ wait: true })', bundled: ['1.2'] },
26
+ 'core.action.script': { factory: 'script()', bundled: ['1.0.0', '1.1'] },
27
+ 'core.action.transform': { factory: 'transform()', bundled: ['1.0.0', '1.1'] },
28
+ 'core.action.transform.filter': { factory: "transform({ variant: 'filter' })", bundled: ['1.0.0', '1.1'] },
29
+ 'core.action.transform.group-by': { factory: "transform({ variant: 'group-by' })", bundled: ['1.0.0', '1.1'] },
30
+ 'core.action.transform.map': { factory: "transform({ variant: 'map' })", bundled: ['1.0.0', '1.1'] },
31
+ 'core.control.end': { factory: '.return()', bundled: ['1.0.0', '1.0'] },
32
+ 'core.datafabric.read': {
33
+ factory: "dataFabricRead({ entity, resultMode: 'single' | 'multiple', filters: [{ field, operator, value, or }], "
34
+ + 'sort: { field, direction }, limit, skip, fields, folderKey })',
35
+ bundled: ['1.0', '1.4'],
36
+ },
37
+ 'core.datafabric.create': {
38
+ factory: 'dataFabricCreate({ entity, values: { <field>: <value> }, folderKey })',
39
+ bundled: ['1.3'],
40
+ },
41
+ 'core.datafabric.update': {
42
+ factory: 'dataFabricUpdate({ entity, record: { byId } | { fromRead }, set: { <field>: <value> }, folderKey })',
43
+ bundled: ['1.0', '1.3'],
44
+ },
45
+ 'core.datafabric.delete': {
46
+ factory: 'dataFabricDelete({ entity, record: { byId } | { fromRead }, folderKey })',
47
+ bundled: ['1.3'],
48
+ },
49
+ 'core.logic.decision': { factory: '.branch()', bundled: ['1.0.0', '1.3'] },
50
+ 'core.logic.delay': { factory: 'delay({ duration })', bundled: ['1.0.0', '1.0'] },
51
+ 'core.logic.dowhile': { factory: '.doWhile(name, condition, body, { limit, breakEnabled })', bundled: ['1.0'] },
52
+ 'core.logic.loop': { factory: '.loop()', bundled: ['1.0.0', '2.4'] },
53
+ 'core.logic.merge': { factory: '.parallel() rejoin', bundled: ['1.0.0', '1.0'] },
54
+ 'core.logic.mock': { factory: 'mock()', bundled: ['1.0.0', '1.0'] },
55
+ 'core.logic.switch': { factory: '.switch()', bundled: ['1.0.0', '1.1'] },
56
+ 'core.logic.terminate': { factory: '.terminate()', bundled: ['1.0.0', '1.0'] },
57
+ 'core.subflow': { factory: 'subflow(child, inputs)', bundled: ['1.0.0', '1.0'] },
58
+ 'uipath.agent.conversational': { factory: 'conversationalAgent()', bundled: ['1.6', '1.7'] },
59
+ 'uipath.agent.voice': { factory: 'voiceAgent()', bundled: ['1.0', '1.1'] },
60
+ 'uipath.conversational.get-conversation-context': { factory: 'conversationContext()', bundled: ['1.0'] },
61
+ 'uipath.conversational.send-message': { factory: 'sendMessage()', bundled: ['1.0', '1.1'] },
62
+ 'uipath.conversational.voice.create-outgoing-call': { factory: 'createOutgoingCall()', bundled: ['1.0'] },
63
+ 'uipath.conversational.voice.end-call': { factory: 'endCall()', bundled: ['1.0'] },
64
+ 'uipath.conversational.wait-for-message': { factory: 'waitForMessage()', bundled: ['1.0', '1.1'] },
65
+ 'uipath.document.classify': { factory: 'documentClassify()', bundled: ['1.0'] },
66
+ 'uipath.human-in-the-loop': { factory: 'hitl()', bundled: ['1.0', '1.1', '1.2'] },
67
+ 'uipath.human-in-the-loop.coded-action-app': {
68
+ factory: "hitl({ variant: 'action-app' })",
69
+ bundled: ['1.0', '1.1'],
70
+ },
71
+ 'uipath.human-in-the-loop.document-validation': {
72
+ factory: "hitl({ variant: 'document-validation' })",
73
+ bundled: ['1.0'],
74
+ },
75
+ 'uipath.human-in-the-loop.quick-form': { factory: "hitl({ variant: 'quick-form' })", bundled: ['1.0', '1.1'] },
76
+ 'uipath.pattern.batch-transform': { factory: 'batchTransform()', bundled: ['1.0'] },
77
+ 'uipath.pattern.deep-rag': { factory: 'summarize()', bundled: ['1.0'] },
78
+ 'uipath.ixp.extract-document-builder': { factory: 'dynamicExtract()', bundled: ['1.0.0'] },
79
+ };
80
+ /**
81
+ * Every `entityConfig` key the product reads or persists on a Data Fabric node,
82
+ * across the four verbs. flow-workbench develop `eed682f19`:
83
+ *
84
+ * - services `serialization/entity-vdo.ts` `interface EntityConfig` (:229-272):
85
+ * `entityName`, `resultMode`, `_recordLimit`, `_skip`, `_sort`,
86
+ * `_selectedFieldNames`, `_filters`, `_entityFields`, `_relatedFields`,
87
+ * `_folderKey`, `_entityKey`, `_resourceKey`, `_outputSchema`;
88
+ * `CreateEntityConfig` (:719) adds `fieldValues`; `UpdateEntityConfig` (:744)
89
+ * adds `recordSource`, `readEntityNodeId`, `recordId`, `fieldUpdates`.
90
+ * - canvas `properties-panel/EntityConfigField.tsx` `EntityConfigInputs`
91
+ * (:85-110) and `entity/useEntityPanelState.ts` `EntitySelectionPatch`
92
+ * (:248-270), which the panels persist on an entity pick:
93
+ * `_entityDisplayName`, `_choiceSets`, `_folderPath`, `_entitySubType`.
94
+ * - Create/Update/DeleteEntityConfigField.tsx declare nothing beyond these.
95
+ *
96
+ * One union rather than one set per verb: a key another verb reads is at worst
97
+ * ignored, and a per-verb split would refuse a canvas-written key on a verb
98
+ * whose panel happens not to declare it. The test pins every key the typed
99
+ * factories write to this set.
100
+ */
101
+ export const DATAFABRIC_ENTITY_CONFIG_KEYS = new Set([
102
+ 'entityName', 'resultMode',
103
+ '_recordLimit', '_skip', '_sort', '_selectedFieldNames', '_filters',
104
+ '_entityFields', '_relatedFields', '_choiceSets', '_outputSchema',
105
+ '_folderKey', '_folderPath', '_entityKey', '_resourceKey', '_entityDisplayName', '_entitySubType',
106
+ 'fieldValues', 'fieldUpdates', 'recordSource', 'readEntityNodeId', 'recordId',
107
+ ]);
108
+ /**
109
+ * What the Data Fabric factories write, and what they do not, as the typed-family
110
+ * warning states it. The factories do not author the canvas's column snapshots,
111
+ * so a related-path filter (`customer.Name`) still needs a raw node carrying
112
+ * `_entityFields` (core/actions.ts `DataFabricFilter.field`).
113
+ */
114
+ const DATAFABRIC_HINT = ' The factory writes the query and write keys: `filters` becomes `_filters` (flat rows; `or: true` '
115
+ + 'starts each OR alternative), `sort` is `_sort`, `limit`/`skip`/`fields` are '
116
+ + '`_recordLimit`/`_skip`/`_selectedFieldNames`, `values`/`set` are `fieldValues`/`fieldUpdates`, '
117
+ + 'and `record` is `recordSource`. It does not write the column snapshots `_entityFields`, '
118
+ + '`_relatedFields`, `_choiceSets` or `_outputSchema`; a related-path filter such as '
119
+ + '`customer.Name` needs `_entityFields`, so for that a raw node is still the way.';
120
+ /**
121
+ * A reminder, as a `warning`, that a `rawNode` stands in for a typed factory,
122
+ * or `undefined` for a type with no factory.
123
+ *
124
+ * A warning and never an error: a factory does not always write every input its
125
+ * node reads (the Data Fabric verbs do not write `_entityFields`, which a
126
+ * related-path filter needs), and nothing checks factory coverage family by
127
+ * family. So the raw node keeps compiling at every version. The one failure
128
+ * the eval archive measured, an invented key the node never reads, is refused
129
+ * by {@link dataFabricUnknownInputs} instead.
130
+ *
131
+ * @param nodeType - The definition's `nodeType`, as passed to `rawNode`.
132
+ * @returns The warning, or `undefined` for a type with no typed factory.
133
+ */
134
+ export function typedFamilyRawNodeWarning(nodeType) {
135
+ if (typeof nodeType !== 'string')
136
+ return undefined;
137
+ const entry = TYPED_FACTORIES[nodeType];
138
+ if (entry === undefined)
139
+ return undefined;
140
+ const hint = nodeType.startsWith('core.datafabric.') ? DATAFABRIC_HINT : '';
141
+ return `rawNode("${nodeType}"): this SDK has a typed factory, ${entry.factory}. A raw node keeps its `
142
+ + `inputs verbatim, and \`validate\` cannot see a key the registry definition does not declare, so a `
143
+ + `misspelled input compiles and is ignored at run time. Prefer the factory unless you need an input it `
144
+ + `does not write.${hint}`;
145
+ }
146
+ const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
147
+ /**
148
+ * Why a `core.datafabric.*` node's inputs carry a key the product never reads,
149
+ * or `undefined` when they do not.
150
+ *
151
+ * This is where the eval failure became possible. A raw node renders its inputs
152
+ * verbatim, and `core.datafabric.read@1.4`'s registry definition declares only
153
+ * `entityName` and `resultMode`, so `validate` cannot see any other key. Measured
154
+ * on the eval archive (2026-09-23, skill-flow-datafabric-native-contract-intake-pipeline):
155
+ * the agent wrote `_sortOptions: [{ fieldName, isDescending }]` in place of
156
+ * `_sort`, `check` printed "no issues", `validate` said Valid, and the query
157
+ * would have run unsorted. The allowed set is what the product reads
158
+ * ({@link DATAFABRIC_ENTITY_CONFIG_KEYS}), so `_entityFields` and the other
159
+ * canvas snapshots stay legal.
160
+ *
161
+ * Severity follows the version: at a bundled `<nodeType>@<version>` the key set
162
+ * is known, so the key is refused (`error`); at a version this SDK has not
163
+ * synced, a newer node could read a new key, so it is reported (`warning`).
164
+ *
165
+ * @param nodeType - The node's type.
166
+ * @param version - The version the node is emitted at.
167
+ * @param inputs - The node's inputs, before rendering.
168
+ * @returns The diagnostic, or `undefined` for a clean node or a non-Data-Fabric type.
169
+ */
170
+ export function dataFabricUnknownInputs(nodeType, version, inputs) {
171
+ if (typeof nodeType !== 'string' || !nodeType.startsWith('core.datafabric.'))
172
+ return undefined;
173
+ if (!isPlainObject(inputs))
174
+ return undefined;
175
+ const unknown = Object.keys(inputs).filter((k) => k !== 'entityConfig');
176
+ const cfg = inputs.entityConfig;
177
+ if (isPlainObject(cfg)) {
178
+ for (const k of Object.keys(cfg))
179
+ if (!DATAFABRIC_ENTITY_CONFIG_KEYS.has(k))
180
+ unknown.push(`entityConfig.${k}`);
181
+ }
182
+ if (unknown.length === 0)
183
+ return undefined;
184
+ const bundled = TYPED_FACTORIES[nodeType]?.bundled.includes(version) ?? false;
185
+ const named = unknown.map((k) => `\`${k}\``).join(', ');
186
+ const factory = TYPED_FACTORIES[nodeType]?.factory;
187
+ const base = `${nodeType}@${version} carries ${named}, which the platform never reads: Data Fabric `
188
+ + `nodes read only \`inputs.entityConfig\`, with the keys ${[...DATAFABRIC_ENTITY_CONFIG_KEYS].join(', ')} `
189
+ + `(flow-workbench entity-vdo.ts \`EntityConfig\` and the entity panels). \`validate\` cannot see `
190
+ + `the key, so it compiles and is ignored at run time; for example a sort is \`_sort: { field, direction }\`, `
191
+ + `not \`_sortOptions\`.${factory ? ` The typed factory, ${factory}, writes only keys the node reads.` : ''}`;
192
+ if (bundled)
193
+ return { severity: 'error', message: base };
194
+ return {
195
+ severity: 'warning',
196
+ message: `${base} This SDK does not bundle ${nodeType}@${version}, so a newer node may read the key; `
197
+ + 'it compiles as written.',
198
+ };
199
+ }
@@ -15,6 +15,7 @@ import { pyStr } from "../compat/pystr.js";
15
15
  import { mapConcurrent } from "../concurrency.js";
16
16
  import { Refusal, messageOf } from "../errors.js";
17
17
  import { registrySearchConnectors } from "../uip/commands.js";
18
+ import { failureReason } from "../uip/envelope.js";
18
19
  import { applyCuratedOverrides, extractCanonical, filterNodeTypesByTier, mergeConnectorRecord, nodeOf, scopedConnectorKey, selectPublishedConnectors, } from "./canonical.js";
19
20
  import { SEARCH_RESULTS_NAME, createBuildContext, isConnectorGet, registryCacheName, registryGet, } from "./context.js";
20
21
  import { enrichAll } from "./enrich.js";
@@ -53,7 +54,7 @@ export async function registrySearch(ctx) {
53
54
  ctx.reporter.info("Running registry search...");
54
55
  const result = await ctx.client.run(registrySearchConnectors(), ctx.timeoutMs === undefined ? undefined : { timeoutMs: ctx.timeoutMs });
55
56
  if (result.code !== 0)
56
- throw new Refusal(`Registry search failed: ${result.stderr}`, "REGISTRY_SEARCH_FAILED");
57
+ throw new Refusal(`Registry search failed: ${failureReason(result)}`, "REGISTRY_SEARCH_FAILED");
57
58
  ctx.caches.registry.write(SEARCH_RESULTS_NAME, undefined, result.stdout);
58
59
  }
59
60
  const data = ctx.caches.registry.read(SEARCH_RESULTS_NAME);
@@ -60,6 +60,32 @@ export declare function registryCacheName(nodeType: string): string;
60
60
  export declare const SEARCH_RESULTS_NAME = "_search_results.json";
61
61
  /** `json.dump(payload, f)`: cache bytes shaped as Python wrote them. */
62
62
  export declare function writeCache(cache: ResponseCache, name: string, payload: unknown): void;
63
+ /** A `registry get` that did not answer with the node, and why. */
64
+ export interface RegistryMiss {
65
+ nodeType: string;
66
+ /** The CLI's own reason ({@link failureReason}); never empty. */
67
+ reason: string;
68
+ /**
69
+ * True only for the CLI's "Node not found" answer — the node is absent. False
70
+ * for every failure of the LOOKUP (an error, a refusal, a timeout), which says
71
+ * nothing about whether the node exists.
72
+ */
73
+ notFound: boolean;
74
+ }
75
+ /** {@link registryProbe}'s answer: the cache name of the reply, or the miss. */
76
+ export type RegistryProbe = {
77
+ name: string;
78
+ } | {
79
+ miss: RegistryMiss;
80
+ };
81
+ /**
82
+ * Cached `uip maestro flow registry get <nodeType>`, reporting nothing.
83
+ *
84
+ * The caller gets the miss with its reason and decides what to say — a caller
85
+ * probing which namespace an operation lives in puts every probe's reason into
86
+ * one refusal instead of a WARN per probe.
87
+ */
88
+ export declare function registryProbe(ctx: BuildContext, nodeType: string): Promise<RegistryProbe>;
63
89
  /**
64
90
  * `registry_get`: cached `uip maestro flow registry get <nodeType>`.
65
91
  *
@@ -18,7 +18,7 @@ import { pyJsonStringify } from "../codecs/pyjson.js";
18
18
  import { sortedByKeys } from "../compat/compare.js";
19
19
  import { isConnectorsMetadata, isResourcesDescribeObject, isResourcesDescribeOperation, isResourcesList as isResourcesListArgv, registryGet as registryGetArgv, } from "../uip/commands.js";
20
20
  import { CACHED_FAILURE, isCachedFailure, safeCacheName } from "../uip/response-cache.js";
21
- import { pyStrip } from "../compat/pystr.js";
21
+ import { failureReason, isNodeNotFound } from "../uip/envelope.js";
22
22
  import { get, isRecord, pyOr, truthy } from "./py.js";
23
23
  export function createBuildContext(ports, scope = {}) {
24
24
  const parentFields = scope.parentFields instanceof Map
@@ -116,25 +116,38 @@ function envelope(payload) {
116
116
  return payload;
117
117
  }
118
118
  /**
119
- * `registry_get`: cached `uip maestro flow registry get <nodeType>`.
119
+ * Cached `uip maestro flow registry get <nodeType>`, reporting nothing.
120
120
  *
121
- * Returns the cache name the raw stdout lives under, or `undefined` when the
122
- * call failed. `quiet` suppresses the failure WARN — for a caller probing which
123
- * of several node-type namespaces an action lives in, where a miss is an
124
- * expected answer rather than a problem to report.
121
+ * The caller gets the miss with its reason and decides what to say — a caller
122
+ * probing which namespace an operation lives in puts every probe's reason into
123
+ * one refusal instead of a WARN per probe.
125
124
  */
126
- export async function registryGet(ctx, nodeType, quiet = false) {
125
+ export async function registryProbe(ctx, nodeType) {
127
126
  const name = registryCacheName(nodeType);
128
127
  if (ctx.caches.registry.has(name))
129
- return name;
128
+ return { name };
130
129
  const result = await runUip(ctx, registryGetArgv(nodeType, ctx.connectionId));
131
130
  if (result.code !== 0) {
132
- if (!quiet)
133
- ctx.reporter.warn(` WARN: registry get failed for ${nodeType}: ${pyStrip(result.stderr)}`);
134
- return undefined;
131
+ return { miss: { nodeType, reason: failureReason(result), notFound: isNodeNotFound(result) } };
135
132
  }
136
133
  ctx.caches.registry.write(name, undefined, result.stdout);
137
- return name;
134
+ return { name };
135
+ }
136
+ /**
137
+ * `registry_get`: cached `uip maestro flow registry get <nodeType>`.
138
+ *
139
+ * Returns the cache name the raw stdout lives under, or `undefined` when the
140
+ * call failed. `quiet` suppresses the failure WARN — for a caller probing which
141
+ * of several node-type namespaces an action lives in, where a miss is an
142
+ * expected answer rather than a problem to report.
143
+ */
144
+ export async function registryGet(ctx, nodeType, quiet = false) {
145
+ const probe = await registryProbe(ctx, nodeType);
146
+ if ("name" in probe)
147
+ return probe.name;
148
+ if (!quiet)
149
+ ctx.reporter.warn(` WARN: registry get failed for ${nodeType}: ${probe.miss.reason}`);
150
+ return undefined;
138
151
  }
139
152
  function noteFailedFieldDescribe(ctx, connectorKey, objectName, operation, message) {
140
153
  if (ctx.parentFields.size > 0) {