@uipath/maestro-builder-sdk 6.8.5 → 6.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +1 -1
- package/dist/check-cli.js +13 -16
- package/dist/check.d.ts +20 -0
- package/dist/check.js +26 -8
- package/dist/cli/commands/flow.js +2 -0
- package/dist/cli-library.d.ts +21 -0
- package/dist/cli-library.js +56 -0
- package/dist/compile-cli.js +12 -11
- package/dist/core/library.d.ts +20 -0
- package/dist/core/library.js +97 -20
- package/dist/core/node-classes.d.ts +8 -0
- package/dist/core/node-classes.js +27 -3
- package/dist/core/typed-raw-node.d.ts +97 -0
- package/dist/core/typed-raw-node.js +199 -0
- package/dist/registry/library-build/build.js +2 -1
- package/dist/registry/library-build/context.d.ts +26 -0
- package/dist/registry/library-build/context.js +25 -12
- package/dist/registry/library-build/events.js +3 -2
- package/dist/registry/prepare/actions.d.ts +42 -5
- package/dist/registry/prepare/actions.js +90 -16
- package/dist/registry/prepare/connections.js +2 -3
- package/dist/registry/prepare/event.d.ts +15 -6
- package/dist/registry/prepare/event.js +19 -11
- package/dist/registry/prepare/messages.d.ts +32 -4
- package/dist/registry/prepare/messages.js +80 -15
- package/dist/registry/prepare/pipeline.js +15 -9
- package/dist/registry/prepare/resolve.js +2 -1
- package/dist/registry/uip/envelope.d.ts +44 -9
- package/dist/registry/uip/envelope.js +80 -0
- package/dist/serialize.js +15 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BuildContext } from "../library-build/context.js";
|
|
1
|
+
import { type BuildContext, type RegistryMiss } from "../library-build/context.js";
|
|
2
2
|
import { type EventFacts } from "../library-build/events.js";
|
|
3
3
|
import { type Dict } from "../library-build/py.js";
|
|
4
4
|
import type { LibraryEntry } from "../model/library.js";
|
|
@@ -87,10 +87,19 @@ export interface EventPrepareResult {
|
|
|
87
87
|
filterFields: unknown[];
|
|
88
88
|
definition: EventDefinition;
|
|
89
89
|
}
|
|
90
|
+
/** `<key> <action>` is not an event: the `.trigger.` probe missed, and why. */
|
|
91
|
+
export interface EventMiss {
|
|
92
|
+
missed: RegistryMiss;
|
|
93
|
+
}
|
|
90
94
|
/**
|
|
91
|
-
* `_prepare_event`. Returns
|
|
92
|
-
* either, so the caller can
|
|
93
|
-
* everything else is handled here,
|
|
94
|
-
* already be the connection: the
|
|
95
|
+
* `_prepare_event`. Returns an {@link EventMiss} when `<key> <action>` is not
|
|
96
|
+
* an event either — carrying the probe's reason, so the caller can put it in
|
|
97
|
+
* the one refusal that names both namespaces; everything else is handled here,
|
|
98
|
+
* refusals included. `ctx.connectionId` must already be the connection: the
|
|
99
|
+
* trigger definition only answers with one.
|
|
100
|
+
*
|
|
101
|
+
* The probe is quiet. The Python printed a `WARN: registry get failed for …`
|
|
102
|
+
* here, and the port printed it with an empty reason, because the CLI's reason
|
|
103
|
+
* is on stdout; the reason now travels in the result instead.
|
|
95
104
|
*/
|
|
96
|
-
export declare function prepareEvent(ctx: BuildContext, options: EventPrepareOptions): Promise<EventPrepareResult |
|
|
105
|
+
export declare function prepareEvent(ctx: BuildContext, options: EventPrepareOptions): Promise<EventPrepareResult | EventMiss>;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* through its reporter, in the Python's order.
|
|
21
21
|
*/
|
|
22
22
|
import { extractCanonical, nodeOf } from "../library-build/canonical.js";
|
|
23
|
-
import {
|
|
23
|
+
import { registryProbe } from "../library-build/context.js";
|
|
24
24
|
import { EventDiscoveryError, eventFacts, mergeDescribeIntoNode, triggersDescribe, triggersObjects, unionEventParameters, } from "../library-build/events.js";
|
|
25
25
|
import { get, isRecord } from "../library-build/py.js";
|
|
26
26
|
import { planLibrary } from "../library-build/plan.js";
|
|
@@ -114,10 +114,15 @@ function fieldsOf(section) {
|
|
|
114
114
|
return Array.isArray(fields) ? fields : [];
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* `_prepare_event`. Returns
|
|
118
|
-
* either, so the caller can
|
|
119
|
-
* everything else is handled here,
|
|
120
|
-
* already be the connection: the
|
|
117
|
+
* `_prepare_event`. Returns an {@link EventMiss} when `<key> <action>` is not
|
|
118
|
+
* an event either — carrying the probe's reason, so the caller can put it in
|
|
119
|
+
* the one refusal that names both namespaces; everything else is handled here,
|
|
120
|
+
* refusals included. `ctx.connectionId` must already be the connection: the
|
|
121
|
+
* trigger definition only answers with one.
|
|
122
|
+
*
|
|
123
|
+
* The probe is quiet. The Python printed a `WARN: registry get failed for …`
|
|
124
|
+
* here, and the port printed it with an empty reason, because the CLI's reason
|
|
125
|
+
* is on stdout; the reason now travels in the result instead.
|
|
121
126
|
*/
|
|
122
127
|
export async function prepareEvent(ctx, options) {
|
|
123
128
|
const { connectorKey, action, connectionId, out } = options;
|
|
@@ -125,13 +130,16 @@ export async function prepareEvent(ctx, options) {
|
|
|
125
130
|
const info = (line) => ctx.reporter.info(line);
|
|
126
131
|
const warn = (line) => ctx.reporter.warn(line);
|
|
127
132
|
const { trigger: triggerNodeType, event: eventNodeType } = eventNodeTypes(connectorKey, action);
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
return
|
|
131
|
-
const payload = ctx.caches.registry.read(
|
|
133
|
+
const probe = await registryProbe(ctx, triggerNodeType);
|
|
134
|
+
if ("miss" in probe)
|
|
135
|
+
return { missed: probe.miss };
|
|
136
|
+
const payload = ctx.caches.registry.read(probe.name);
|
|
132
137
|
const canonical = extractCanonical(nodeOf(payload));
|
|
133
|
-
if (canonical === undefined)
|
|
134
|
-
|
|
138
|
+
if (canonical === undefined) {
|
|
139
|
+
// An answer, but not one this code can read as a trigger definition. Not
|
|
140
|
+
// "absent", so the caller must not say it is.
|
|
141
|
+
return { missed: { nodeType: triggerNodeType, reason: "the registry answered, but not with a node definition", notFound: false } };
|
|
142
|
+
}
|
|
135
143
|
if (pyTruthy(options.fields))
|
|
136
144
|
throw new PrepareRefusal(eventParentFieldsMessage(), "EVENT_NO_PARENT_FIELDS");
|
|
137
145
|
if (options.allObjects)
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* `core/cli-spelling.ts`) without this module reading the environment.
|
|
22
22
|
*/
|
|
23
23
|
import { Refusal } from "../errors.js";
|
|
24
|
+
import { type RegistryCatalog } from "./actions.js";
|
|
24
25
|
export declare const PREFIX = "prepare-connector: ";
|
|
25
26
|
/** The spelling the Python hard-coded into every pasteable re-run. */
|
|
26
27
|
export declare const PREPARE_COMMAND = "npx flow-sdk registry prepare";
|
|
@@ -45,16 +46,43 @@ export declare function connectionLine(row: Record<string, unknown>, connectionI
|
|
|
45
46
|
export declare function bindingsLine(symbolicName: string, bindingsPath: string): string;
|
|
46
47
|
/** `_missing_generic_schema_message`: a Generic action that expanded to nothing. */
|
|
47
48
|
export declare function missingGenericSchemaMessage(operation: string, objectName?: string | null): string;
|
|
49
|
+
/** One `registry get` probe that missed, as a refusal quotes it. */
|
|
50
|
+
export interface ProbeMiss {
|
|
51
|
+
nodeType: string;
|
|
52
|
+
/** The CLI's reason, never empty (`failureReason`). */
|
|
53
|
+
reason: string;
|
|
54
|
+
}
|
|
48
55
|
/**
|
|
49
|
-
*
|
|
50
|
-
* registry
|
|
51
|
-
*
|
|
56
|
+
* What to do when `<key> <name>` is neither an action nor an event: both
|
|
57
|
+
* `registry get` probes answered "Node not found". Pure over its inputs.
|
|
58
|
+
*
|
|
59
|
+
* It names both probes, because the author's name may have meant either
|
|
60
|
+
* namespace and the SDK cannot know which (so the code is OPERATION_UNKNOWN,
|
|
61
|
+
* not ACTION_UNKNOWN), and it lists both catalogs `registry search <key>`
|
|
62
|
+
* served. Until 2026-09 this was an action-only message: it named the action
|
|
63
|
+
* node type, said "Re-run with a registry action id" and listed only actions,
|
|
64
|
+
* because `registryActionIds` removes event ids by design. For
|
|
65
|
+
* `uipath-uipath-dataservice record-created` — an EVENT the catalog had stopped
|
|
66
|
+
* serving — that sent the author after action ids (2026-09-23 eval,
|
|
67
|
+
* `skill-flow-datafabric-trigger-lifecycle`, v2 command 33).
|
|
52
68
|
*
|
|
53
69
|
* NOT auto-substituted, deliberately: the baked action id and the registry key
|
|
54
70
|
* are different namespaces, and guessing between them would emit a node both
|
|
55
71
|
* offline validators accept and the runtime dispatches to the wrong operation.
|
|
56
72
|
*/
|
|
57
|
-
export declare function
|
|
73
|
+
export declare function unresolvedOperationMessage(input: {
|
|
74
|
+
connectorKey: string;
|
|
75
|
+
name: string;
|
|
76
|
+
probes: readonly ProbeMiss[];
|
|
77
|
+
catalog: RegistryCatalog;
|
|
78
|
+
}): string;
|
|
79
|
+
/**
|
|
80
|
+
* A `registry get` probe failed for a reason other than "Node not found" — an
|
|
81
|
+
* error inside the CLI, a refusal, a timeout, a reply that is not JSON. That is
|
|
82
|
+
* not evidence the node is absent, so this refusal says so and carries the
|
|
83
|
+
* CLI's own reasons instead of listing alternatives.
|
|
84
|
+
*/
|
|
85
|
+
export declare function registryGetFailedMessage(connectorKey: string, name: string, probes: readonly ProbeMiss[]): string;
|
|
58
86
|
export declare function noCanonicalEntryMessage(nodeType: string): string;
|
|
59
87
|
/** The `-f` names do not match any api-type ObjectAction; `specs` are the actions' parent lists. */
|
|
60
88
|
export declare function parentFieldNamesMismatchMessage(given: readonly string[], specs: ReadonlyArray<readonly [string, readonly string[]]>): string;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `core/cli-spelling.ts`) without this module reading the environment.
|
|
22
22
|
*/
|
|
23
23
|
import { Refusal, messageOf } from "../errors.js";
|
|
24
|
-
import {
|
|
24
|
+
import { plausibleMatches } from "./actions.js";
|
|
25
25
|
import { pyGet, pyOr, pyRepr, pyStrAny, pyTruthy } from "./support.js";
|
|
26
26
|
export const PREFIX = "prepare-connector: ";
|
|
27
27
|
/** The spelling the Python hard-coded into every pasteable re-run. */
|
|
@@ -74,33 +74,98 @@ export function missingGenericSchemaMessage(operation, objectName) {
|
|
|
74
74
|
"not need connection-specific fields, use the baked connector descriptor " +
|
|
75
75
|
"without custom fields instead.");
|
|
76
76
|
}
|
|
77
|
+
function probeLines(probes) {
|
|
78
|
+
return probes.map((probe) => ` registry get ${probe.nodeType} → ${probe.reason}`);
|
|
79
|
+
}
|
|
80
|
+
/** `record-created (trigger, event)`: an event id with the forms the registry served it in. */
|
|
81
|
+
function eventLabel(event) {
|
|
82
|
+
return `${event.id} (${event.forms.join(", ")})`;
|
|
83
|
+
}
|
|
84
|
+
/** How many of `wanted`'s hyphen tokens `candidate` shares. */
|
|
85
|
+
function sharedTokens(wanted, candidate) {
|
|
86
|
+
const tokens = new Set(candidate.split("-"));
|
|
87
|
+
return [...new Set(wanted.split("-"))].filter((token) => tokens.has(token)).length;
|
|
88
|
+
}
|
|
77
89
|
/**
|
|
78
|
-
*
|
|
79
|
-
* registry
|
|
80
|
-
*
|
|
90
|
+
* What to do when `<key> <name>` is neither an action nor an event: both
|
|
91
|
+
* `registry get` probes answered "Node not found". Pure over its inputs.
|
|
92
|
+
*
|
|
93
|
+
* It names both probes, because the author's name may have meant either
|
|
94
|
+
* namespace and the SDK cannot know which (so the code is OPERATION_UNKNOWN,
|
|
95
|
+
* not ACTION_UNKNOWN), and it lists both catalogs `registry search <key>`
|
|
96
|
+
* served. Until 2026-09 this was an action-only message: it named the action
|
|
97
|
+
* node type, said "Re-run with a registry action id" and listed only actions,
|
|
98
|
+
* because `registryActionIds` removes event ids by design. For
|
|
99
|
+
* `uipath-uipath-dataservice record-created` — an EVENT the catalog had stopped
|
|
100
|
+
* serving — that sent the author after action ids (2026-09-23 eval,
|
|
101
|
+
* `skill-flow-datafabric-trigger-lifecycle`, v2 command 33).
|
|
81
102
|
*
|
|
82
103
|
* NOT auto-substituted, deliberately: the baked action id and the registry key
|
|
83
104
|
* are different namespaces, and guessing between them would emit a node both
|
|
84
105
|
* offline validators accept and the runtime dispatches to the wrong operation.
|
|
85
106
|
*/
|
|
86
|
-
export function
|
|
107
|
+
export function unresolvedOperationMessage(input) {
|
|
108
|
+
const { connectorKey: key, name, probes, catalog } = input;
|
|
87
109
|
const lines = [
|
|
88
|
-
`${PREFIX}
|
|
110
|
+
`${PREFIX}\`${key} ${name}\` is neither an action nor an event the registry serves:`,
|
|
111
|
+
...probeLines(probes),
|
|
89
112
|
"",
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
// flow-v1 `packages/flow-core/src/manifest/fetchers.ts:286`, `:336-337`: the
|
|
114
|
+
// node catalog is the typecache Search/Catalog, and a trigger node exists
|
|
115
|
+
// only where it serves one.
|
|
116
|
+
"A Flow node exists only if the tenant's Flow node catalog serves it. An event that",
|
|
117
|
+
"`uip is triggers objects` lists but this registry does not cannot be prepared or",
|
|
118
|
+
"compiled, and hand-writing its node JSON does not make it offered.",
|
|
93
119
|
];
|
|
94
|
-
if (
|
|
95
|
-
lines.push("", `\`registry search ${
|
|
120
|
+
if (catalog.failure !== undefined) {
|
|
121
|
+
lines.push("", `\`registry search ${key}\` failed: ${catalog.failure}`, "so what the registry does serve for it could not be listed.");
|
|
96
122
|
return lines.join("\n");
|
|
97
123
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
if (catalog.events.length > 0) {
|
|
125
|
+
const close = plausibleMatches(name, catalog.events.map((event) => event.id));
|
|
126
|
+
const byId = new Map(catalog.events.map((event) => [event.id, event]));
|
|
127
|
+
if (close.length > 0)
|
|
128
|
+
lines.push("", `Closest event to "${name}": ${close.slice(0, 3).map((id) => eventLabel(byId.get(id))).join(", ")}`);
|
|
129
|
+
lines.push("", `Events the registry serves for ${key}:`, ...catalog.events.map((event) => ` ${eventLabel(event)}`));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
lines.push("", `\`registry search ${key}\` lists no \`uipath.connector.{trigger,event}.${key}.*\` node: the`, `registry serves no event for ${key}.`);
|
|
133
|
+
}
|
|
134
|
+
if (catalog.actions.length > 0) {
|
|
135
|
+
const close = plausibleMatches(name, catalog.actions);
|
|
136
|
+
if (close.length > 0)
|
|
137
|
+
lines.push("", `Closest action to "${name}": ${close.slice(0, 3).join(", ")}`);
|
|
138
|
+
// The Generic explanation only where the name reads like one of these
|
|
139
|
+
// actions (most of its words shared): `list-all-records` against
|
|
140
|
+
// `list-records`, not `record-created` against `create-entity-record`,
|
|
141
|
+
// where it would point an event request at action ids again.
|
|
142
|
+
const wanted = new Set(name.split("-")).size;
|
|
143
|
+
if (close.length > 0 && sharedTokens(name, close[0]) * 2 > wanted) {
|
|
144
|
+
lines.push("The baked library's action id is not always the registry's key — a Generic", "op is `list-all-records` in the library and materializes per object, while", "the registry serves the un-expanded form. For an action, re-run with a registry action id.");
|
|
145
|
+
}
|
|
146
|
+
lines.push("", `All actions the registry serves for ${key}:`, ...catalog.actions.map((a) => ` ${a}`));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
lines.push("", `\`registry search ${key}\` lists no action for ${key} either.`);
|
|
150
|
+
}
|
|
151
|
+
lines.push("", "`registry get` and `registry search` answer from the CLI's local copy of the catalog.", "If it looks stale, refresh it first: `uip maestro flow registry pull --force`.");
|
|
102
152
|
return lines.join("\n");
|
|
103
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* A `registry get` probe failed for a reason other than "Node not found" — an
|
|
156
|
+
* error inside the CLI, a refusal, a timeout, a reply that is not JSON. That is
|
|
157
|
+
* not evidence the node is absent, so this refusal says so and carries the
|
|
158
|
+
* CLI's own reasons instead of listing alternatives.
|
|
159
|
+
*/
|
|
160
|
+
export function registryGetFailedMessage(connectorKey, name, probes) {
|
|
161
|
+
return [
|
|
162
|
+
`${PREFIX}could not tell whether \`${connectorKey} ${name}\` is an action or an event: a registry`,
|
|
163
|
+
"lookup failed, which does not mean the node is absent.",
|
|
164
|
+
...probeLines(probes),
|
|
165
|
+
"",
|
|
166
|
+
"Resolve the failure above and re-run prepare.",
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
104
169
|
export function noCanonicalEntryMessage(nodeType) {
|
|
105
170
|
return `${PREFIX}no canonical entry for ${nodeType} (unexpected registry shape).`;
|
|
106
171
|
}
|
|
@@ -28,17 +28,17 @@ import { resolve } from "node:path";
|
|
|
28
28
|
import { PACKAGE_NAME } from "../../package-name.js";
|
|
29
29
|
import { afterLast } from "../compat/pystr.js";
|
|
30
30
|
import { extractCanonical, isHttpPassthrough, nodeOf } from "../library-build/canonical.js";
|
|
31
|
-
import { createBuildContext, isResourcesList,
|
|
31
|
+
import { createBuildContext, isResourcesList, registryProbe } from "../library-build/context.js";
|
|
32
32
|
import { enrichEntry } from "../library-build/enrich.js";
|
|
33
33
|
import { expandGenericForConnector, genericTemplatesByOp } from "../library-build/generic-expand.js";
|
|
34
34
|
import { planLibrary } from "../library-build/plan.js";
|
|
35
35
|
import { MemoryResponseCache } from "../uip/response-cache.js";
|
|
36
|
-
import {
|
|
36
|
+
import { registryCatalog } from "./actions.js";
|
|
37
37
|
import { discoverConnection, lookupConnection, mergeBindings, restoreBindings, snapshotBindings, } from "./connections.js";
|
|
38
38
|
import { prepareEvent } from "./event.js";
|
|
39
39
|
import { descriptorUsage } from "./handoff.js";
|
|
40
40
|
import { ResolutionsExhausted, allParentsResolved, resolvedParentValues, writeResolutions } from "./ledger.js";
|
|
41
|
-
import { PREPARE_COMMAND, PrepareRefusal, bindingsLine, candidateLabel, connectionLine, generatedModulesLine, genericActionNeedsObjectMessage, inputFieldNote, keptSchemaMessage, lookupVocabularyLines, missingGenericSchemaMessage, missingParentsNote, nextLine, noCanonicalEntryMessage, objectFlagsOnlyForGenericMessage, objectNoteLine, objectRecordedLine, parentFieldNamesMismatchMessage, parentOnlyMessage, prefixed, preparedSummaryLine, rejectedParentFieldsMessage, rerunAgainstLine, resolvedParentsNote, tsNote,
|
|
41
|
+
import { PREPARE_COMMAND, PrepareRefusal, bindingsLine, candidateLabel, connectionLine, generatedModulesLine, genericActionNeedsObjectMessage, inputFieldNote, keptSchemaMessage, lookupVocabularyLines, missingGenericSchemaMessage, missingParentsNote, nextLine, noCanonicalEntryMessage, objectFlagsOnlyForGenericMessage, objectNoteLine, objectRecordedLine, parentFieldNamesMismatchMessage, parentOnlyMessage, prefixed, preparedSummaryLine, rejectedParentFieldsMessage, rerunAgainstLine, resolvedParentsNote, registryGetFailedMessage, tsNote, unresolvedOperationMessage, } from "./messages.js";
|
|
42
42
|
import { ObjectMatchRefusal, RESOLUTIONS_FILENAME, matchObject, recordObjectResolution } from "./objects.js";
|
|
43
43
|
import { countInputFields, keepPreparedSchema, lookupSpecsFromOverlay, mergeFilesInto, parentFields as overlayParentFields, schemaActionParents, } from "./overlay.js";
|
|
44
44
|
import { declaredParents, objectActionRequest, objectActionSpecs, parseFields } from "./parents.js";
|
|
@@ -207,8 +207,8 @@ async function prepareOnConnection(options, ports, connected) {
|
|
|
207
207
|
const { connectionId, bound, rollback } = connected;
|
|
208
208
|
const ctx = createBuildContext({ client: ports.client, caches: ports.caches ?? memoryCaches(), reporter: ports.reporter, concurrency: 1, timeoutMs: ports.timeoutMs }, { connectionId, parentFields });
|
|
209
209
|
const nodeType = `uipath.connector.${connectorKey}.${action}`;
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
210
|
+
const actionProbe = await registryProbe(ctx, nodeType);
|
|
211
|
+
if ("miss" in actionProbe) {
|
|
212
212
|
// Not an activity. Connector EVENTS live one namespace over, and their
|
|
213
213
|
// registry definition only answers with a connection, which this command
|
|
214
214
|
// has by now. Same loop, different twin pair.
|
|
@@ -230,13 +230,19 @@ async function prepareOnConnection(options, ports, connected) {
|
|
|
230
230
|
bindings: rollback,
|
|
231
231
|
prepareCommand: command,
|
|
232
232
|
});
|
|
233
|
-
if (
|
|
233
|
+
if (!("missed" in event)) {
|
|
234
234
|
return { kind: "event", nodeType: event.eventNodeType, connectionId, bound, objectName: event.objectName };
|
|
235
235
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
236
|
+
const probes = [actionProbe.miss, event.missed];
|
|
237
|
+
// Only the CLI's "Node not found" says a node is absent. Any other failure
|
|
238
|
+
// of either lookup is reported as the failure it is, never as a verdict.
|
|
239
|
+
if (probes.some((probe) => !probe.notFound)) {
|
|
240
|
+
throw new PrepareRefusal(registryGetFailedMessage(connectorKey, action, probes), "REGISTRY_GET_FAILED");
|
|
241
|
+
}
|
|
242
|
+
const catalog = await registryCatalog(ports.client, connectorKey, ports.timeoutMs);
|
|
243
|
+
throw new PrepareRefusal(unresolvedOperationMessage({ connectorKey, name: action, probes, catalog }), "OPERATION_UNKNOWN");
|
|
238
244
|
}
|
|
239
|
-
const payload = ctx.caches.registry.read(
|
|
245
|
+
const payload = ctx.caches.registry.read(actionProbe.name);
|
|
240
246
|
// The registry's own declaration of parent inputs, read from the same reply.
|
|
241
247
|
const declared = declaredParents(payload);
|
|
242
248
|
let entry = extractCanonical(nodeOf(payload));
|
|
@@ -22,6 +22,7 @@ import { pyFloatRepr } from "../codecs/pyjson.js";
|
|
|
22
22
|
import { pyStrip, pyStripChars, splitOnce } from "../compat/pystr.js";
|
|
23
23
|
import { Refusal } from "../errors.js";
|
|
24
24
|
import { isResourcesRunList } from "../uip/commands.js";
|
|
25
|
+
import { failureReason } from "../uip/envelope.js";
|
|
25
26
|
import { isPlainObject, notJsonReason, pyOr, pyRepr, pyStrAny, pyTruthy } from "./support.js";
|
|
26
27
|
/**
|
|
27
28
|
* A scan has to stop somewhere, and the caps must be set against the MEASURED
|
|
@@ -361,7 +362,7 @@ export function parseListingDocument(text) {
|
|
|
361
362
|
export async function listPage(client, connectorKey, collection, connectionId, query) {
|
|
362
363
|
const result = await client.run(isResourcesRunList(connectorKey, collection, connectionId, query));
|
|
363
364
|
if (result.code !== 0) {
|
|
364
|
-
throw new ResolutionRefusal(`listing ${collection} failed: ${
|
|
365
|
+
throw new ResolutionRefusal(`listing ${collection} failed: ${failureReason(result)}`, "RESOLVE_LISTING_FAILED");
|
|
365
366
|
}
|
|
366
367
|
const envelope = parseListingDocument(result.stdout);
|
|
367
368
|
if (!isPlainObject(envelope)) {
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Reading what `uip … --output json` prints.
|
|
3
|
-
*
|
|
4
|
-
* The CLI is chatty: an update banner, a tool-version warning and a
|
|
5
|
-
* `ManifestClient` diagnostic can precede the JSON document, and the document
|
|
6
|
-
* itself is a `{ Result, Data, Message, … }` envelope whose key casing has
|
|
7
|
-
* varied between tools. Three Python callers each had their own loop for
|
|
8
|
-
* finding the document; this is the one loop, and the one accessor.
|
|
9
|
-
*/
|
|
1
|
+
import type { UipResult } from "./client.js";
|
|
10
2
|
/** The shape every `uip` JSON response shares, read case-insensitively. */
|
|
11
3
|
export interface UipEnvelope {
|
|
12
4
|
Result?: unknown;
|
|
@@ -35,3 +27,46 @@ export declare function isSuccess(envelope: UipEnvelope | undefined): boolean;
|
|
|
35
27
|
export declare function dataItems(envelope: UipEnvelope | undefined): unknown[];
|
|
36
28
|
/** The last `limit` characters of a stream, for a message. */
|
|
37
29
|
export declare function tail(text: string, limit?: number): string;
|
|
30
|
+
/**
|
|
31
|
+
* Why a `uip` call failed, as text a message can quote after a colon. Never empty.
|
|
32
|
+
*
|
|
33
|
+
* `uip … --output json` prints its FAILURE envelope on stdout, not stderr:
|
|
34
|
+
* `OutputFormatter.error` hands it to `logOutput`, which writes through
|
|
35
|
+
* `sink.writeOut` (uipath-cli `packages/common/src/formatter.ts:1657-1688` and
|
|
36
|
+
* `:654-675`). For example, `registry get` of a node the catalog does not serve
|
|
37
|
+
* exits 1 with this on stdout:
|
|
38
|
+
*
|
|
39
|
+
* ```json
|
|
40
|
+
* { "Result": "Failure", "Message": "Node not found",
|
|
41
|
+
* "Instructions": "No node found with node type: uipath.connector.trigger.…" }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* and, on stderr, nothing, or a `[ManifestClient] fetchDynamicNodes ok: …`
|
|
45
|
+
* diagnostic, or a self-update banner. Callers that quoted stderr printed
|
|
46
|
+
* `registry get failed for <nodeType>: ` with nothing after the colon (2026-09-23
|
|
47
|
+
* eval, `skill-flow-datafabric-trigger-lifecycle`, v2 command 33).
|
|
48
|
+
*
|
|
49
|
+
* So the envelope is read first. The order is:
|
|
50
|
+
*
|
|
51
|
+
* 1. a call the client refused (`OfflineUipClient`): no process ran, and its
|
|
52
|
+
* stderr already says so (`offline: refused to run …`), so that text is kept;
|
|
53
|
+
* 2. a call killed by its timeout: `timed out`, whatever partial output it left;
|
|
54
|
+
* 3. a non-Success envelope on stdout (found past any banner, keys read
|
|
55
|
+
* case-insensitively): `<Message>: <Instructions>`, or whichever is present;
|
|
56
|
+
* 4. the end of stderr, then the end of stdout;
|
|
57
|
+
* 5. `no output`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function failureReason(result: UipResult): string;
|
|
60
|
+
/**
|
|
61
|
+
* True when a failed `registry get` answered the CLI's own "not in the catalog"
|
|
62
|
+
* envelope, `Message: "Node not found"` (uipath-cli
|
|
63
|
+
* `packages/maestro-sdk/src/registry/command.ts:595-600`).
|
|
64
|
+
*
|
|
65
|
+
* Only that answer says the node is ABSENT. Every other failure — `Error
|
|
66
|
+
* retrieving node` (`command.ts:586-591`, which carries the thrown error in
|
|
67
|
+
* `Instructions`), a refusal, a timeout, output that is not JSON — says the
|
|
68
|
+
* lookup did not work, which is a different fact with a different remedy. The
|
|
69
|
+
* `Message` is the discriminator because the envelope's `ErrorCode` is the
|
|
70
|
+
* generic `unknown_error` for both (`formatter.ts:1490-1502`).
|
|
71
|
+
*/
|
|
72
|
+
export declare function isNodeNotFound(result: UipResult): boolean;
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* itself is a `{ Result, Data, Message, … }` envelope whose key casing has
|
|
7
7
|
* varied between tools. Three Python callers each had their own loop for
|
|
8
8
|
* finding the document; this is the one loop, and the one accessor.
|
|
9
|
+
*
|
|
10
|
+
* It is also the one place that decides why a call FAILED ({@link failureReason}).
|
|
11
|
+
* Callers outside `registry/uip/` do not read a result's stderr themselves;
|
|
12
|
+
* `tests/uip-stderr-gate.test.ts` holds them to that.
|
|
9
13
|
*/
|
|
14
|
+
import { pyStrip } from "../compat/pystr.js";
|
|
10
15
|
/**
|
|
11
16
|
* The first JSON document in `text`, or `undefined`.
|
|
12
17
|
*
|
|
@@ -82,3 +87,78 @@ export function dataItems(envelope) {
|
|
|
82
87
|
export function tail(text, limit = 300) {
|
|
83
88
|
return text.length <= limit ? text : text.slice(text.length - limit);
|
|
84
89
|
}
|
|
90
|
+
/** A non-empty trimmed string, or `undefined`. */
|
|
91
|
+
function said(value) {
|
|
92
|
+
if (typeof value !== "string")
|
|
93
|
+
return undefined;
|
|
94
|
+
const text = pyStrip(value);
|
|
95
|
+
return text === "" ? undefined : text;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Why a `uip` call failed, as text a message can quote after a colon. Never empty.
|
|
99
|
+
*
|
|
100
|
+
* `uip … --output json` prints its FAILURE envelope on stdout, not stderr:
|
|
101
|
+
* `OutputFormatter.error` hands it to `logOutput`, which writes through
|
|
102
|
+
* `sink.writeOut` (uipath-cli `packages/common/src/formatter.ts:1657-1688` and
|
|
103
|
+
* `:654-675`). For example, `registry get` of a node the catalog does not serve
|
|
104
|
+
* exits 1 with this on stdout:
|
|
105
|
+
*
|
|
106
|
+
* ```json
|
|
107
|
+
* { "Result": "Failure", "Message": "Node not found",
|
|
108
|
+
* "Instructions": "No node found with node type: uipath.connector.trigger.…" }
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* and, on stderr, nothing, or a `[ManifestClient] fetchDynamicNodes ok: …`
|
|
112
|
+
* diagnostic, or a self-update banner. Callers that quoted stderr printed
|
|
113
|
+
* `registry get failed for <nodeType>: ` with nothing after the colon (2026-09-23
|
|
114
|
+
* eval, `skill-flow-datafabric-trigger-lifecycle`, v2 command 33).
|
|
115
|
+
*
|
|
116
|
+
* So the envelope is read first. The order is:
|
|
117
|
+
*
|
|
118
|
+
* 1. a call the client refused (`OfflineUipClient`): no process ran, and its
|
|
119
|
+
* stderr already says so (`offline: refused to run …`), so that text is kept;
|
|
120
|
+
* 2. a call killed by its timeout: `timed out`, whatever partial output it left;
|
|
121
|
+
* 3. a non-Success envelope on stdout (found past any banner, keys read
|
|
122
|
+
* case-insensitively): `<Message>: <Instructions>`, or whichever is present;
|
|
123
|
+
* 4. the end of stderr, then the end of stdout;
|
|
124
|
+
* 5. `no output`.
|
|
125
|
+
*/
|
|
126
|
+
export function failureReason(result) {
|
|
127
|
+
if (result.refused === true)
|
|
128
|
+
return said(result.stderr) ?? "not run (offline client refused the call)";
|
|
129
|
+
if (result.timedOut === true)
|
|
130
|
+
return "timed out (the call was killed before it answered)";
|
|
131
|
+
const envelope = parseEnvelope(result.stdout);
|
|
132
|
+
if (envelope !== undefined && !isSuccess(envelope)) {
|
|
133
|
+
const parts = [said(getCI(envelope, "Message")), said(getCI(envelope, "Instructions"))].filter((part) => part !== undefined);
|
|
134
|
+
if (parts.length > 0)
|
|
135
|
+
return parts.join(": ");
|
|
136
|
+
}
|
|
137
|
+
const stderr = said(result.stderr);
|
|
138
|
+
if (stderr !== undefined)
|
|
139
|
+
return tail(stderr);
|
|
140
|
+
const stdout = said(result.stdout);
|
|
141
|
+
if (stdout !== undefined)
|
|
142
|
+
return tail(stdout);
|
|
143
|
+
return "no output";
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* True when a failed `registry get` answered the CLI's own "not in the catalog"
|
|
147
|
+
* envelope, `Message: "Node not found"` (uipath-cli
|
|
148
|
+
* `packages/maestro-sdk/src/registry/command.ts:595-600`).
|
|
149
|
+
*
|
|
150
|
+
* Only that answer says the node is ABSENT. Every other failure — `Error
|
|
151
|
+
* retrieving node` (`command.ts:586-591`, which carries the thrown error in
|
|
152
|
+
* `Instructions`), a refusal, a timeout, output that is not JSON — says the
|
|
153
|
+
* lookup did not work, which is a different fact with a different remedy. The
|
|
154
|
+
* `Message` is the discriminator because the envelope's `ErrorCode` is the
|
|
155
|
+
* generic `unknown_error` for both (`formatter.ts:1490-1502`).
|
|
156
|
+
*/
|
|
157
|
+
export function isNodeNotFound(result) {
|
|
158
|
+
if (result.code === 0 || result.refused === true || result.timedOut === true)
|
|
159
|
+
return false;
|
|
160
|
+
const envelope = parseEnvelope(result.stdout);
|
|
161
|
+
if (envelope === undefined || isSuccess(envelope))
|
|
162
|
+
return false;
|
|
163
|
+
return said(getCI(envelope, "Message"))?.toLowerCase() === "node not found";
|
|
164
|
+
}
|
package/dist/serialize.js
CHANGED
|
@@ -13,6 +13,7 @@ import { dirname, join } from 'node:path';
|
|
|
13
13
|
import { Expr, toExpr, SCHEDULE_PRESETS, DELAY_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
|
|
14
14
|
import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
15
15
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
|
+
import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
|
|
16
17
|
import { bindingSelfNameMessage } from './core/binding-messages.js';
|
|
17
18
|
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
18
19
|
import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
@@ -3928,7 +3929,14 @@ export function serialize(built, opts = {}) {
|
|
|
3928
3929
|
display: { label, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
|
|
3929
3930
|
};
|
|
3930
3931
|
col++;
|
|
3931
|
-
|
|
3932
|
+
// A Data Fabric node gets no node-level `model`, whichever path emits it
|
|
3933
|
+
// (the typed verbs, or a rawNode through emitContribution). Studio Web
|
|
3934
|
+
// never writes one (flow-core node.ts: "`node.model` is never written";
|
|
3935
|
+
// canvas `nodeToInstance` omits it), and export seeds it from the
|
|
3936
|
+
// definition (services bpmn-to-xml.ts), so a copy here can only go stale.
|
|
3937
|
+
// `outputs` stay: the canvas persists them (`source: '=response'`), and
|
|
3938
|
+
// `variables.nodes[]` is derived from them.
|
|
3939
|
+
if (def?.model && !type.startsWith('core.datafabric.'))
|
|
3932
3940
|
node.model = subNodeId(clone(def.model), id);
|
|
3933
3941
|
if (def?.outputDefinition)
|
|
3934
3942
|
node.outputs = subNodeId(clone(def.outputDefinition), id);
|
|
@@ -4170,6 +4178,12 @@ export function serialize(built, opts = {}) {
|
|
|
4170
4178
|
const connectorRefusal = connectorRawNodeRefusal(def.nodeType);
|
|
4171
4179
|
if (connectorRefusal)
|
|
4172
4180
|
throw new Error(`${where}: ${connectorRefusal}`);
|
|
4181
|
+
// A Data Fabric key the product never reads (`_sortOptions`) is refused for
|
|
4182
|
+
// every contribution, whichever class or SDK copy made it: `validate`
|
|
4183
|
+
// cannot see it, and the node would ignore it at run time.
|
|
4184
|
+
const unknownInput = dataFabricUnknownInputs(def.nodeType, def.version, contribution.inputs);
|
|
4185
|
+
if (unknownInput?.severity === 'error')
|
|
4186
|
+
throw new Error(`${where}: ${unknownInput.message}`);
|
|
4173
4187
|
const id = stepUid(scope, stepName);
|
|
4174
4188
|
const node = makeNode(def.nodeType, id, stepName, { parentId, def, requestedVersion });
|
|
4175
4189
|
node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.6",
|
|
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",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"@types/node": "^22.7.0",
|
|
88
88
|
"esbuild": "^0.28.1"
|
|
89
89
|
},
|
|
90
|
-
"gitref": "
|
|
90
|
+
"gitref": "503ff8140144536c938e0b0ad0356b6ccb41b9f9"
|
|
91
91
|
}
|