@uipath/maestro-builder-sdk 6.14.3 → 6.14.5

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.
@@ -42,6 +42,7 @@
42
42
  * the connection rows.
43
43
  */
44
44
  import { existsSync, readFileSync } from 'node:fs';
45
+ import { BindingRefusal, connectionConnectorConflictMessage } from './core/binding-messages.js';
45
46
  /** The document version pack emits. */
46
47
  const DOCUMENT_VERSION = '2.0';
47
48
  /** The per-resource metadata version pack stamps. */
@@ -56,6 +57,93 @@ function str(value) {
56
57
  function isConnection(resource) {
57
58
  return typeof resource === 'string' && resource.toLowerCase() === 'connection';
58
59
  }
60
+ /** The managed-HTTP wrapper's own connector key — never the connection's type when a target is named. */
61
+ const MANAGED_HTTP_CONNECTOR = 'uipath-uipath-http';
62
+ function nodeDetail(node) {
63
+ return isRecord(node.inputs) && isRecord(node.inputs.detail) ? node.inputs.detail : undefined;
64
+ }
65
+ /** The connection a node runs on: `connectionId`, else `connectionResourceId`. */
66
+ function nodeConnectionKey(detail) {
67
+ return str(detail.connectionId) ?? str(detail.connectionResourceId);
68
+ }
69
+ /**
70
+ * The connector a node's CONNECTION belongs to — not the connector the node is.
71
+ *
72
+ * The two differ for managed HTTP: its `detail.connector` is always the wrapper,
73
+ * `uipath-uipath-http`, and the connection it authenticates with is the one
74
+ * `bodyParameters.targetConnector` names (`uipath-google-drive`,
75
+ * `uipath-salesforce-slack`, …). Stamping the wrapper made `uip solution
76
+ * resources refresh` refuse the connection — "Requested type
77
+ * 'uipath-uipath-http'; the resource is registered as type
78
+ * 'uipath-google-drive'" (flow-builder-sdk#866). Every other family's
79
+ * `detail.connector` is its connection's connector.
80
+ */
81
+ function nodeConnectionConnector(detail) {
82
+ const connector = str(detail.connector);
83
+ if (connector === MANAGED_HTTP_CONNECTOR && isRecord(detail.bodyParameters)) {
84
+ return str(detail.bodyParameters.targetConnector) ?? connector;
85
+ }
86
+ return connector;
87
+ }
88
+ /** Every node of the flow and of its inline subflows — they share one `bindings[]`. */
89
+ function allNodes(flow) {
90
+ const nodes = Array.isArray(flow.nodes) ? flow.nodes.filter(isRecord) : [];
91
+ if (isRecord(flow.subflows)) {
92
+ for (const sub of Object.values(flow.subflows)) {
93
+ if (isRecord(sub) && Array.isArray(sub.nodes))
94
+ nodes.push(...sub.nodes.filter(isRecord));
95
+ }
96
+ }
97
+ return nodes;
98
+ }
99
+ /**
100
+ * Which connector each bound connection belongs to, keyed by connection id —
101
+ * decided by EVERY node on the connection, so the answer does not depend on
102
+ * which step comes first.
103
+ *
104
+ * `bindings.json` cannot answer this: `prepare --connection-id` writes a label,
105
+ * a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
106
+ * (see `core/binding-messages.ts`). The nodes are the only record of it, so
107
+ * all of them must agree. Two that do not — a Drive step and a Slack step on
108
+ * one connection, or managed HTTP whose `targetConnector` is not the connection's
109
+ * connector — are refused with `CONNECTION_CONNECTOR_CONFLICT`: at most one of
110
+ * them can run, and picking the first silently mislabels the solution resource.
111
+ *
112
+ * @throws BindingRefusal when two nodes on one connection name different connectors.
113
+ */
114
+ export function connectionConnectors(flow) {
115
+ const out = new Map();
116
+ if (!isRecord(flow))
117
+ return out;
118
+ const bound = new Set();
119
+ for (const b of Array.isArray(flow.bindings) ? flow.bindings.filter(isRecord) : []) {
120
+ const key = str(b.resourceKey);
121
+ if (key && isConnection(b.resource) && str(b.propertyAttribute)?.toLowerCase() === 'connectionid')
122
+ bound.add(key);
123
+ }
124
+ const users = new Map();
125
+ for (const node of allNodes(flow)) {
126
+ const detail = nodeDetail(node);
127
+ const key = detail && nodeConnectionKey(detail);
128
+ if (!detail || !key || !bound.has(key))
129
+ continue;
130
+ const connector = nodeConnectionConnector(detail);
131
+ if (!connector)
132
+ continue;
133
+ const byConnector = users.get(key) ?? new Map();
134
+ users.set(key, byConnector);
135
+ const steps = byConnector.get(connector) ?? [];
136
+ steps.push((isRecord(node.display) ? str(node.display.label) : undefined) ?? str(node.id) ?? '?');
137
+ byConnector.set(connector, steps);
138
+ }
139
+ for (const [key, byConnector] of users) {
140
+ if (byConnector.size > 1) {
141
+ throw new BindingRefusal('CONNECTION_CONNECTOR_CONFLICT', connectionConnectorConflictMessage(key, byConnector));
142
+ }
143
+ out.set(key, [...byConnector.keys()][0]);
144
+ }
145
+ return out;
146
+ }
59
147
  /**
60
148
  * The `connection` resources a compiled flow declares — one per distinct
61
149
  * connection, in node order, attributed to the FIRST node that uses it.
@@ -63,6 +151,16 @@ function isConnection(resource) {
63
151
  * Both halves of that are pack's behaviour, not a choice made here: a flow with
64
152
  * two Outlook steps on one connection yields a single resource whose
65
153
  * `ActivityName` is the earlier step's.
154
+ *
155
+ * What the resource IS does not follow node order: its `Connector` comes from
156
+ * {@link connectionConnectors} (every node on the connection, managed HTTP read
157
+ * through its `targetConnector`). Pack stamps the same value — measured with
158
+ * `uip maestro flow pack` on managed HTTP → Slack alone, before a native Slack
159
+ * step, and after one: `uipath-salesforce-slack` all three times, where this
160
+ * module used to stamp `uipath-uipath-http` for the first two (#866;
161
+ * uip 1.202.0-dev, 2026-09-30).
162
+ *
163
+ * @throws BindingRefusal when two nodes on one connection name different connectors.
66
164
  */
67
165
  export function connectionResources(flow) {
68
166
  if (!isRecord(flow))
@@ -78,25 +176,31 @@ export function connectionResources(flow) {
78
176
  continue;
79
177
  if (str(binding.propertyAttribute)?.toLowerCase() !== 'connectionid')
80
178
  continue;
81
- if (!byKey.has(key))
82
- byKey.set(key, binding);
179
+ byKey.set(key, [...(byKey.get(key) ?? []), binding]);
83
180
  }
84
181
  if (byKey.size === 0)
85
182
  return [];
183
+ const connectors = connectionConnectors(flow);
86
184
  const nodes = Array.isArray(flow.nodes) ? flow.nodes.filter(isRecord) : [];
87
185
  const resources = [];
88
186
  const seen = new Set();
89
187
  for (const node of nodes) {
90
- const detail = isRecord(node.inputs) && isRecord(node.inputs.detail) ? node.inputs.detail : undefined;
188
+ const detail = nodeDetail(node);
91
189
  if (!detail)
92
190
  continue;
93
- const key = str(detail.connectionId) ?? str(detail.connectionResourceId);
191
+ const key = nodeConnectionKey(detail);
94
192
  if (!key || seen.has(key))
95
193
  continue;
96
- const binding = byKey.get(key);
97
- if (!binding)
194
+ const candidates = byKey.get(key);
195
+ if (!candidates)
98
196
  continue;
99
197
  seen.add(key);
198
+ const connector = connectors.get(key) ?? nodeConnectionConnector(detail) ?? '';
199
+ // The label is the FIRST ConnectionId binding on the connection, even when
200
+ // #865's fix emits one per referencing definition — measured against `uip
201
+ // maestro flow pack` (uip 1.202.0-dev) on both orders of the issue's repro: pack labels
202
+ // the resource after the earlier step's binding, like `ActivityName`.
203
+ const binding = candidates[0];
100
204
  const displayLabel = str(binding.name) ?? key;
101
205
  const label = isRecord(node.display) ? str(node.display.label) : undefined;
102
206
  resources.push({
@@ -116,7 +220,7 @@ export function connectionResources(flow) {
116
220
  BindingsVersion: BINDINGS_VERSION,
117
221
  DisplayLabel: displayLabel,
118
222
  UseConnectionService: 'true',
119
- Connector: str(detail.connector) ?? '',
223
+ Connector: connector,
120
224
  },
121
225
  });
122
226
  }
package/dist/check.js CHANGED
@@ -11,7 +11,7 @@ import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './core/l
11
11
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
12
12
  import { EVENT_FILTER_OPERATORS, readEventFilter, eventFilterProblem } from './event-filters.js';
13
13
  import { extractRefs } from './core/expr-check.js';
14
- import { bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
14
+ import { bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, undeclaredBinding } from './core/binding-messages.js';
15
15
  import { isAnswerField } from './core/hitl-answers.js';
16
16
  import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
17
17
  import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
@@ -20,7 +20,51 @@ import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js
20
20
  import { prepareCommand, resolveFlagArg } from './core/cli-spelling.js';
21
21
  import { EVENT_BYOA_UNVERIFIED, byoaUnverified } from './core/byoa.js';
22
22
  export function check(built, opts = {}) {
23
- return checkFlow(built, [], opts);
23
+ const outer = connectionUsage;
24
+ connectionUsage = [];
25
+ try {
26
+ const diags = checkFlow(built, [], opts);
27
+ diags.push(...connectionConnectorConflicts(connectionUsage, opts));
28
+ return diags;
29
+ }
30
+ finally {
31
+ connectionUsage = outer;
32
+ }
33
+ }
34
+ /**
35
+ * The connection uses {@link checkBindingLabels} saw during the current
36
+ * `check()` — the flow and every child flow, which share one `bindings[]`.
37
+ * Module state rather than a `CheckOpts` field because `check` is synchronous
38
+ * and this is not an input; `check()` saves and restores it, so a nested call
39
+ * cannot mix two flows.
40
+ */
41
+ let connectionUsage;
42
+ /**
43
+ * CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
44
+ * document (`connectionConnectors`, bindings-v2.ts), reported here with the same
45
+ * sentence so `check` does not print `✓ no issues` for a source `compile` then
46
+ * refuses. Labels are grouped by what they resolve to, so two labels bound to
47
+ * one connection id are one connection, as they are in the `.flow`.
48
+ */
49
+ function connectionConnectorConflicts(uses, opts) {
50
+ const byConnection = new Map();
51
+ for (const use of uses) {
52
+ const key = opts.bindings?.resolve(use.label) ?? use.label;
53
+ const byConnector = byConnection.get(key) ?? new Map();
54
+ byConnection.set(key, byConnector);
55
+ byConnector.set(use.connector, [...(byConnector.get(use.connector) ?? []), use.step]);
56
+ }
57
+ const out = [];
58
+ for (const [key, byConnector] of byConnection) {
59
+ if (byConnector.size < 2)
60
+ continue;
61
+ out.push({
62
+ level: 'error', code: 'CONNECTION_CONNECTOR_CONFLICT',
63
+ step: [...byConnector.values()][1][0],
64
+ message: connectionConnectorConflictMessage(key, byConnector),
65
+ });
66
+ }
67
+ return out;
24
68
  }
25
69
  /**
26
70
  * `ancestors` is the chain of child flows currently being checked, innermost
@@ -358,7 +402,10 @@ function checkFlow(built, ancestors, opts = {}) {
358
402
  const filled = (v) => typeof v === 'string' && v.trim() !== '';
359
403
  if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
360
404
  const httpStep = s.name;
361
- checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }));
405
+ const target = pair.targetConnector;
406
+ checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }), undefined,
407
+ // The connector its CONNECTION belongs to: the target, not the wrapper.
408
+ { step: httpStep, connector: typeof target === 'string' && target !== '' ? target : 'uipath-uipath-http' });
362
409
  }
363
410
  const seen = new Set();
364
411
  for (const b of s.spec.inputs.branches ?? []) {
@@ -450,7 +497,7 @@ function checkFlow(built, ancestors, opts = {}) {
450
497
  checkAgent(s.name, s.spec.inputs, diags);
451
498
  }
452
499
  if (s.spec.kind === 'inlineAgent') {
453
- checkInlineAgent(s.name, s.spec.inputs, diags);
500
+ checkInlineAgent(s.name, s.spec.inputs, diags, opts);
454
501
  }
455
502
  if (s.spec.kind === 'queueItem')
456
503
  checkQueueItem(s.name, s.spec.inputs, diags);
@@ -537,7 +584,7 @@ function checkFlow(built, ancestors, opts = {}) {
537
584
  });
538
585
  checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
539
586
  checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
540
- checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription));
587
+ checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription), { step: stepName, connector: eventTarget(s.spec.subscription)?.key });
541
588
  }
542
589
  if (s.spec.kind === 'subflow') {
543
590
  // Only a real child goes in the map: a missing one is reported by
@@ -1453,7 +1500,7 @@ function checkConnectorContract(step, spec, opts, diags) {
1453
1500
  if (inputErr) {
1454
1501
  diags.push({ level: 'error', code: 'CONNECTOR_INPUT', step, message: `Connector step "${step}": ${inputErr}` });
1455
1502
  }
1456
- checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action });
1503
+ checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action }, { step, connector: spec.key });
1457
1504
  }
1458
1505
  /** What `registry prepare` takes for an event subscription: its connector key and curated event name. */
1459
1506
  function eventTarget(sub) {
@@ -1472,7 +1519,13 @@ function eventTarget(sub) {
1472
1519
  * Silent without a bindings.json, and for a label that resolves to anything
1473
1520
  * other than its own name (stub values are compile's CONNECTION_STUB warning).
1474
1521
  */
1475
- function checkBindingLabels(where, labels, opts, report, target) {
1522
+ function checkBindingLabels(where, labels, opts, report, target,
1523
+ /** The step, and the connector its connection must belong to, for CONNECTION_CONNECTOR_CONFLICT. */
1524
+ use) {
1525
+ const label = labels?.connection;
1526
+ if (use?.connector && typeof label === 'string' && label !== '') {
1527
+ connectionUsage?.push({ step: use.step, label, connector: use.connector });
1528
+ }
1476
1529
  const bindings = opts.bindings;
1477
1530
  if (!bindings || !labels)
1478
1531
  return;
@@ -2285,7 +2338,7 @@ function checkTrigger(spec, diags, opts = {}) {
2285
2338
  const sub = spec.subscription;
2286
2339
  checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
2287
2340
  checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
2288
- checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub));
2341
+ checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub), { step: 'trigger', connector: eventTarget(sub)?.key });
2289
2342
  return;
2290
2343
  }
2291
2344
  // manual() is the explicit spelling of the default — nothing to validate.
@@ -3457,7 +3510,7 @@ export function jsonTextDirective(text, returnKeys) {
3457
3510
  * ALSO asks for JSON text makes the model pack its whole answer, serialized, into
3458
3511
  * one string field. See {@link jsonTextDirective}.
3459
3512
  */
3460
- function checkInlineAgent(step, inputs, diags) {
3513
+ function checkInlineAgent(step, inputs, diags, opts = {}) {
3461
3514
  const at = (code, message, suggestion) => {
3462
3515
  diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
3463
3516
  };
@@ -3740,6 +3793,24 @@ function checkInlineAgent(step, inputs, diags) {
3740
3793
  at('INLINE_AGENT_TOOL_NO_OPERATION', `Inline agent step "${step}" declares a connector tool without ${blank(c.connector) ? 'a `connector`' : 'an `operation`'}. `
3741
3794
  + `Both name the curated-library entry the tool is resolved from — the same pair \`connector(…)\` takes.`, `{ kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue' }`);
3742
3795
  }
3796
+ // A connector tool calls Integration Service on a CONNECTION, exactly as a
3797
+ // connector step does — and before #867 there was no way to give it one, so
3798
+ // the tool compiled with none anywhere (no binding, no resource.json row)
3799
+ // and could only fail at run time. Both halves, as `registry prepare` writes them.
3800
+ const labels = t;
3801
+ const missing = ['connection', 'folder'].filter((k) => blank(labels[k]));
3802
+ if (missing.length > 0) {
3803
+ const key = blank(c.connector) ? '<connector-key>' : String(c.connector);
3804
+ const op = blank(c.operation) ? '<operation>' : String(c.operation);
3805
+ at('INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION', `Inline agent step "${step}"'s connector tool "${key}/${op}" has no ${missing.map((m) => `\`${m}\``).join(' or ')}. `
3806
+ + `The tool calls Integration Service on a tenant connection: without one the agent's tool has no `
3807
+ + `connection binding, its resource.json carries none, and the solution's Resources never list it. `
3808
+ + `Name the bindings.json labels the same way \`connector()\` takes them — \`${prepareCommand(key, op)}\` `
3809
+ + `finds the connection and writes both entries.`, `{ kind: 'connector', connector: '${key}', operation: '${op}', connection: '<connection label>', folder: '<folder label>' }`);
3810
+ }
3811
+ else {
3812
+ checkBindingLabels(`Inline agent step "${step}"'s connector tool`, labels, opts, (code, message) => at(code, message), blank(c.connector) ? undefined : { key: String(c.connector), ...(blank(c.operation) ? {} : { action: String(c.operation) }) });
3813
+ }
3743
3814
  }
3744
3815
  else if (kind === 'ixp') {
3745
3816
  const x = t;
@@ -36,20 +36,24 @@ function opt(argv, name) {
36
36
  return i >= 0 ? argv[i + 1] : undefined;
37
37
  }
38
38
  /**
39
- * The deployed-resource tool `type` values `compile` writes. NOT every
40
- * `$resourceType: 'tool'`: `uip agent refresh` generates the `integration` ones
41
- * from the flow's connector nodes into the same directory, so pruning by
42
- * `$resourceType` alone would delete the generator's own output.
39
+ * The tool `type` values `compile` writes. NOT every `$resourceType: 'tool'`:
40
+ * the designer writes kinds the SDK cannot derive into the same directory, so
41
+ * pruning by `$resourceType` alone would delete theirs.
42
+ *
43
+ * `integration` (connector tools) is compile's since #867: compile writes the
44
+ * file under the tool node's own `source` id, the same id and content `uip agent
45
+ * refresh --inline-in-flow` derives from that node, so a file under any other id
46
+ * belongs to a tool node the flow no longer has.
43
47
  */
44
48
  const COMPILE_OWNED_TOOL_TYPES = new Set([
45
- 'process', 'api', 'processOrchestration', 'flow', 'agent', 'function',
49
+ 'process', 'api', 'processOrchestration', 'flow', 'agent', 'function', 'integration',
46
50
  ]);
47
51
  /**
48
52
  * Does `compile` own this resource file — i.e. would this run have written it,
49
53
  * so an unwritten one is stale rather than someone else's?
50
54
  *
51
- * Everything it says no to belongs to `uip agent refresh` (connector tools) or to
52
- * the designer (escalation, mcp, and the kinds the SDK cannot derive).
55
+ * Everything it says no to belongs to the designer (escalation, mcp, and the
56
+ * kinds the SDK cannot derive).
53
57
  */
54
58
  function compileOwnsResource(body) {
55
59
  const kind = body?.$resourceType;
@@ -236,7 +240,9 @@ export async function run(argv) {
236
240
  // fail on it. Keyed the way refresh keys it — an index by its name.
237
241
  const named = body?.indexName ?? body?.name;
238
242
  const key = typeof named === 'string' && named !== '' ? named : undefined;
239
- if (key !== undefined) {
243
+ // A connector tool's binding is a `connection` row, and `mergeBindingsV2`
244
+ // re-derives every connection row from the flow below — nothing to retract.
245
+ if (key !== undefined && body?.type !== 'integration') {
240
246
  // Keyed the way refresh keys it: an index by its name, a deployed-resource
241
247
  // tool under `process` whatever its own type says.
242
248
  staleBindings.push({ resource: resourceType === 'context' ? 'index' : 'process', key });
@@ -1806,6 +1806,25 @@ export interface ConnectorToolRef {
1806
1806
  object?: string;
1807
1807
  /** Display label; defaults to the library's own (e.g. `'Create Issue'`). */
1808
1808
  name?: string;
1809
+ /**
1810
+ * What the agent is told the tool does — the runtime hands it to the MODEL.
1811
+ * Defaults to the library's own operation description.
1812
+ */
1813
+ description?: string;
1814
+ /**
1815
+ * The connection's binding NAME in `bindings.json` — the same label
1816
+ * `connector()`'s `connection` option takes (e.g. `'genai'`), resolved to
1817
+ * the tenant connection id at compile. Required: a connector tool with no
1818
+ * connection has nothing to call Integration Service with, and `check`
1819
+ * reports `INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION`.
1820
+ */
1821
+ connection?: string;
1822
+ /**
1823
+ * The connection's folder binding NAME in `bindings.json` (a `FolderKey`
1824
+ * entry) — the same label `connector()`'s `folder` option takes. Required
1825
+ * with `connection`.
1826
+ */
1827
+ folder?: string;
1809
1828
  }
1810
1829
  /**
1811
1830
  * The tool kinds that wrap a DEPLOYED tenant resource. `kind` picks the
@@ -1,7 +1,11 @@
1
1
  /** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
2
2
  export declare const GUID_RE: RegExp;
3
3
  /** The diagnostic codes a label refusal carries, in `check` and on the error `serialize` throws. */
4
- export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED';
4
+ export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
5
+ /** An emitted definition's `<bindings.NAME>` placeholder has no `bindings[]` entry named NAME (#865). */
6
+ | 'BINDING_PLACEHOLDER_UNRESOLVED'
7
+ /** Two steps on one connection disagree on which connector it belongs to (#866). */
8
+ | 'CONNECTION_CONNECTOR_CONFLICT';
5
9
  /**
6
10
  * What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
7
11
  * message is the sentence `check` prints; the `code` lets `compile --no-check`
@@ -58,3 +62,14 @@ export interface PrepareTarget {
58
62
  * `prepare` command for this step's own connector and operation.
59
63
  */
60
64
  export declare function bindingUndeclaredMessage(where: string, name: string, attribute: 'connection' | 'folder', declared: readonly string[], target?: PrepareTarget): string;
65
+ /**
66
+ * Why compile refuses one connection used by steps of different connectors —
67
+ * shared by `serialize` (which throws it, via `connectionConnectors` in
68
+ * `bindings-v2.ts`) and `check`, so both print the same sentence.
69
+ *
70
+ * `users` maps each connector to the steps that use the connection as that
71
+ * connector. For managed HTTP the connector is its `targetConnector` — the
72
+ * connection it authenticates with — not the `uipath-uipath-http` wrapper
73
+ * (flow-builder-sdk#866).
74
+ */
75
+ export declare function connectionConnectorConflictMessage(connection: string, users: ReadonlyMap<string, readonly string[]>): string;
@@ -89,3 +89,24 @@ export function bindingUndeclaredMessage(where, name, attribute, declared, targe
89
89
  + `declares the label with a placeholder resourceKey instead, which compiles with a CONNECTION_STUB warning — `
90
90
  + `never an invented GUID.`;
91
91
  }
92
+ /**
93
+ * Why compile refuses one connection used by steps of different connectors —
94
+ * shared by `serialize` (which throws it, via `connectionConnectors` in
95
+ * `bindings-v2.ts`) and `check`, so both print the same sentence.
96
+ *
97
+ * `users` maps each connector to the steps that use the connection as that
98
+ * connector. For managed HTTP the connector is its `targetConnector` — the
99
+ * connection it authenticates with — not the `uipath-uipath-http` wrapper
100
+ * (flow-builder-sdk#866).
101
+ */
102
+ export function connectionConnectorConflictMessage(connection, users) {
103
+ const who = [...users]
104
+ .map(([connector, steps]) => `${steps.map((s) => `"${s}"`).join(', ')} as ${connector}`)
105
+ .join('; ');
106
+ return `connection "${connection}" is used by steps of different connectors (${who}). An Integration Service `
107
+ + `connection belongs to exactly one connector, so at most one of these steps can run on it, and the solution `
108
+ + `resource would be declared as whichever came first. Give each connector its own connection label in `
109
+ + `bindings.json (\`${prepareCommand('<connector-key>', '<action>')}\` binds one per connector). A managed `
110
+ + `\`http({ managed: true, … })\` step on another connector's connection must name that connector in `
111
+ + `\`targetConnector\`.`;
112
+ }
@@ -54,6 +54,34 @@ export declare function serializeWithSidecars(built: BuiltFlow, opts?: Serialize
54
54
  }>;
55
55
  };
56
56
  export declare function serialize(built: BuiltFlow, opts?: SerializeOpts): any;
57
+ /**
58
+ * Every `<bindings.NAME>` placeholder a node's definition carries in its
59
+ * `model.context` must resolve to a `bindings[]` entry for THAT node.
60
+ *
61
+ * "Resolve" is Studio Web's rule, not a looser one: flow-v1
62
+ * `matchContextBindings` + `getInstanceBindingKey`
63
+ * (flow-schema/src/packaging/resolve-context.ts) take the entries whose
64
+ * `resource` EQUALS the definition's `model.bindings.resource`, scope them to the
65
+ * node's `inputs.detail.connectionResourceId` when that resource is `connection`
66
+ * (or undeclared), and look the placeholder up there BY NAME. Any miss blocks
67
+ * publish (MST-15010), and one that slips through reaches Studio as the literal
68
+ * placeholder: `invalid GUID <bindings.uipath-salesforce-slack connection>`.
69
+ * `uip maestro flow validate` does not check this.
70
+ *
71
+ * That is how #865 shipped — a per-call-site dedupe that let the first step on a
72
+ * shared connection claim it — and UV-16774 before it (a `Connection` resource
73
+ * spelling). Checking the finished document catches that class for every family
74
+ * and every emission order, whichever call site produced it.
75
+ *
76
+ * It holds for real definitions: of the 5,102 in the local registry index
77
+ * (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
78
+ * declares that name in `model.bindings.values`, which is what the emitters
79
+ * close. Agent resource definitions are the one exemption (see below).
80
+ *
81
+ * @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
82
+ * placeholder and the step that carries it.
83
+ */
84
+ export declare function assertBindingPlaceholdersResolve(flow: any): void;
57
85
  /**
58
86
  * What `compile` would refuse these connector inputs with, or `undefined` when it
59
87
  * would accept them — the SAME code path `buildConnectorInputs` runs at emit time,