@uipath/maestro-builder-sdk 6.36.0 → 6.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/api-index.md +28 -27
  2. package/dist/api-members.md +94 -93
  3. package/dist/bpmn/bpmn-sdk.d.ts +38 -9
  4. package/dist/bpmn/bpmn-sdk.js +104 -28
  5. package/dist/bpmn/check.js +64 -0
  6. package/dist/bpmn/connector.js +3 -0
  7. package/dist/bpmn/declared-vars.js +10 -3
  8. package/dist/bpmn/decompile.js +38 -10
  9. package/dist/bpmn/intsvc.d.ts +9 -10
  10. package/dist/bpmn/intsvc.js +31 -18
  11. package/dist/bpmn/registry-types.json +30 -24
  12. package/dist/bpmn/resource-key-pattern.d.ts +38 -0
  13. package/dist/bpmn/resource-key-pattern.js +47 -0
  14. package/dist/bpmn/serialize.js +22 -24
  15. package/dist/bpmn/typed-node.d.ts +17 -0
  16. package/dist/bpmn/typed-node.js +19 -0
  17. package/dist/cli-library.js +3 -1
  18. package/dist/core/cli-library.d.ts +8 -2
  19. package/dist/core/cli-library.js +6 -4
  20. package/dist/core/connector-resolve-error.d.ts +7 -1
  21. package/dist/core/event-checks.js +2 -2
  22. package/dist/core/lookups.d.ts +22 -0
  23. package/dist/core/lookups.js +34 -1
  24. package/dist/core/synthesized-definition.d.ts +15 -0
  25. package/dist/core/synthesized-definition.js +22 -0
  26. package/dist/registry/library-build/context.d.ts +16 -0
  27. package/dist/registry/library-build/context.js +32 -5
  28. package/dist/registry/prepare/actions.d.ts +15 -0
  29. package/dist/registry/prepare/actions.js +12 -1
  30. package/dist/registry/prepare/integration-service.d.ts +105 -0
  31. package/dist/registry/prepare/integration-service.js +218 -0
  32. package/dist/registry/prepare/messages.d.ts +41 -1
  33. package/dist/registry/prepare/messages.js +82 -1
  34. package/dist/registry/prepare/pipeline.js +73 -15
  35. package/dist/registry/uip/commands.d.ts +8 -0
  36. package/dist/registry/uip/commands.js +12 -0
  37. package/dist/serialize.js +11 -0
  38. package/package.json +2 -2
@@ -2454,6 +2454,17 @@ declare abstract class ScopeBuilder {
2454
2454
  * is left alone.
2455
2455
  */
2456
2456
  private bindRequiredContext;
2457
+ /**
2458
+ * A binding field's resource key from its `resourceKeyPattern`, with the declared
2459
+ * bindings the key was read through — or `undefined` when the pattern does not
2460
+ * resolve from this context (see `resolveResourceKeyPattern`), in which case the
2461
+ * caller keys the binding by the value, as it always has.
2462
+ *
2463
+ * @throws when the pattern resolves except for a `=bindings.<b>` reference that no
2464
+ * `.binding()` before this node declares with a value: that key would otherwise be
2465
+ * built, so the author is told what to declare rather than given a wrong pair.
2466
+ */
2467
+ private resourceKeyFromPattern;
2457
2468
  /** @internal Shared body of the Orchestrator process-invocation methods. */
2458
2469
  private orchestratorNode;
2459
2470
  /**
@@ -2530,12 +2541,23 @@ declare abstract class ScopeBuilder {
2530
2541
  *
2531
2542
  * @remarks
2532
2543
  * **Not a DMN decision table**, despite the element and the label. The registry spec
2533
- * is an Orchestrator process invocation — `releaseKey`/`folderId`/`folderPath`/`name`
2534
- * plus `JobArguments`, `BindingPattern: businessRule` over `Resource: process` —
2535
- * structurally identical to {@link ScopeBuilder.startProcess}. `process` names a
2536
- * package whose Orchestrator process type is `BusinessRules`; whatever decision logic
2537
- * exists lives INSIDE that package, not in the `.bpmn`. So this is the job-start
2538
- * family's sixth member, not a rules subsystem, and it shares every option with it.
2544
+ * is an Orchestrator process invocation — `name`/`folderPath` plus `JobArguments` —
2545
+ * structurally a {@link ScopeBuilder.startProcess}. `process` names a package whose
2546
+ * Orchestrator process type is `BusinessRules`; whatever decision logic exists lives
2547
+ * INSIDE that package, not in the `.bpmn`. So this is the job-start family's sixth
2548
+ * member, not a rules subsystem.
2549
+ *
2550
+ * Where it departs from the family, the registry says so and this follows it:
2551
+ *
2552
+ * - the task is `version="v3"` — below v3 the engine cannot run the `.uirule`
2553
+ * package a BusinessRules project deploys — so a rule is always addressed by
2554
+ * name, and `releaseKey` / `folderKey` are refused;
2555
+ * - `name` and `folderPath` are each bound to a `BusinessRule` resource
2556
+ * (`propertyAttribute` `name` / `folderPath`), both keyed `<folder>.<name>`
2557
+ * (`<name>` alone when the folder is empty), declared on your behalf as
2558
+ * `<id>_name` / `<id>_folderPath`; an unbound `_label` carries the rule's name;
2559
+ * - two output rows: `output` (`=result`, in `outputVar`) and `Error` (`=Error`,
2560
+ * in `<id>_Error`), both `jsonSchema`.
2539
2561
  *
2540
2562
  * Distinct from the Case SDK's `rule()`, which declares DNF lifecycle conditions
2541
2563
  * (`case-entered`, `required-tasks-completed`, …) deciding when a stage enters or
@@ -2555,7 +2577,7 @@ declare abstract class ScopeBuilder {
2555
2577
  * process: 'DiscountTier',
2556
2578
  * folder: 'Finance',
2557
2579
  * input: { amount: 4200 },
2558
- * outputs: { tier: '=response.tier' },
2580
+ * outputs: { tier: '=result.DiscountTier.tier' },
2559
2581
  * })
2560
2582
  * .endEvent('done')
2561
2583
  * .sequenceFlow('start', 'tier')
@@ -2564,9 +2586,16 @@ declare abstract class ScopeBuilder {
2564
2586
  * ```
2565
2587
  *
2566
2588
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
2589
+ *
2590
+ * **The result is keyed by decision name.** The platform's `output` row is the whole
2591
+ * result (`source="=result"`) and a decision's column reads as
2592
+ * `=result.<Decision>.<column>` — `vars.<id>_businessRuleResponse.<Decision>.<column>`
2593
+ * downstream — never `=result.<column>` (canvas spec `outputNotes`; `uip rules describe
2594
+ * <name>` lists the decisions and columns, and only a decision-keyed rule, one whose
2595
+ * `OutputArguments` carry `x-uipath-decision-keyed`, fits this row).
2567
2596
  * @param opts - Which rule package, folder, and input arguments. `outputVar` defaults
2568
- * to `<id>_businessRuleResponse` — see the note in the implementation for why this one
2569
- * type does not take its default from the spec.
2597
+ * to `<id>_businessRuleResponse`. `releaseKey` and `folderKey` are refused: a v3 rule
2598
+ * is addressed by name.
2570
2599
  * @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
2571
2600
  * @returns This builder, so calls chain.
2572
2601
  */
@@ -3,6 +3,7 @@ import { checkBpmnExpressions, BpmnBuildError } from './bpmn-expr-check.js';
3
3
  import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
4
4
  import { schemaVersionRefusal } from '../schema-version.js';
5
5
  import { bindingRequiredFields, REGISTRY_GAPS, registryType } from './typed-node.js';
6
+ import { resolveResourceKeyPattern } from './resource-key-pattern.js';
6
7
  import { DATA_SERVICE_ACTIVITY_TYPE, DATA_SERVICE_CONNECTOR_KEY, dataServiceWire } from './data-service.js';
7
8
  import { ownEntry } from '../core/own-entry.js';
8
9
  export { BpmnBuildError } from './bpmn-expr-check.js';
@@ -1213,22 +1214,75 @@ class ScopeBuilder {
1213
1214
  if (typeof value !== 'string' || value.startsWith('='))
1214
1215
  continue;
1215
1216
  const bindingId = `${id}_${field.name}`;
1217
+ // The key a pattern builds from the context values — a `=bindings.<b>` value as
1218
+ // the DECLARED value of `b`, since a key is never evaluated as an expression.
1219
+ // Resolved only for the fields the pattern names, and refused only when the
1220
+ // pattern would otherwise resolve: a pattern that names nothing the context has
1221
+ // (`${metadata.key}`, the live registry's pattern on a job start, whose context
1222
+ // has no `key`) leaves the key to the value, as before. This snapshot carries no
1223
+ // pattern for the job starts at all; `resource-key-pattern.ts` is tested on both.
1224
+ const keyed = this.resourceKeyFromPattern(id, type, field, context);
1216
1225
  this._bindingRegistry.declare({
1217
1226
  id: bindingId,
1218
1227
  name: field.displayName ?? field.name,
1219
1228
  resource: field.bindingInfo?.resource ?? 'process',
1220
1229
  propertyAttribute: field.bindingInfo?.propertyAttribute ?? field.name,
1221
- // A fact the registry omits and the validator enforces — see REGISTRY_GAPS.
1222
1230
  resourceSubType: ownEntry(REGISTRY_GAPS, type)?.resourceSubType,
1223
1231
  default: value,
1224
- resourceKey: value,
1232
+ resourceKey: keyed?.key ?? value,
1225
1233
  });
1234
+ // A declared binding the key was read from shares it when the author left its own
1235
+ // key unset, so the pair names one resource — only when the pattern resolved. One
1236
+ // that already carries a DIFFERENT key (a folder binding two rules share, or an
1237
+ // author's own key) would make this node's pair name two resources: refused, with
1238
+ // the binding to declare instead.
1239
+ if (keyed) {
1240
+ for (const decl of keyed.referenced) {
1241
+ if (decl.resourceKey === undefined)
1242
+ decl.resourceKey = keyed.key;
1243
+ else if (decl.resourceKey !== keyed.key) {
1244
+ const resource = field.bindingInfo?.resource ?? 'process';
1245
+ throw new Error(`${type} "${id}": its ${resource} key "${keyed.key}" is read through =bindings.${decl.id}, whose resource key is already ` +
1246
+ `"${decl.resourceKey}" (another node's pair, or the binding's own), so the pair would name two resources. ` +
1247
+ `A ${resource} binding names one: declare one per node, e.g. ` +
1248
+ `\`.binding('${decl.id}_${id}', { resource: '${decl.resource}', propertyAttribute: '${decl.propertyAttribute}', value: '${decl.default}' })\`.`);
1249
+ }
1250
+ }
1251
+ }
1226
1252
  out[field.name] = `=bindings.${bindingId}`;
1227
1253
  }
1228
1254
  return out;
1229
1255
  }
1256
+ /**
1257
+ * A binding field's resource key from its `resourceKeyPattern`, with the declared
1258
+ * bindings the key was read through — or `undefined` when the pattern does not
1259
+ * resolve from this context (see `resolveResourceKeyPattern`), in which case the
1260
+ * caller keys the binding by the value, as it always has.
1261
+ *
1262
+ * @throws when the pattern resolves except for a `=bindings.<b>` reference that no
1263
+ * `.binding()` before this node declares with a value: that key would otherwise be
1264
+ * built, so the author is told what to declare rather than given a wrong pair.
1265
+ */
1266
+ resourceKeyFromPattern(id, type, field, context) {
1267
+ const pattern = field.bindingInfo?.resourceKeyPattern;
1268
+ const byId = (bindingId) => this._bindingRegistry.bindings.find((b) => b.id === bindingId);
1269
+ const resolved = resolveResourceKeyPattern(pattern, context, byId);
1270
+ if (resolved === undefined)
1271
+ return undefined;
1272
+ if ('blocked' in resolved) {
1273
+ const { field: blockedField, ref } = resolved.blocked;
1274
+ const refId = ref.slice('=bindings.'.length);
1275
+ const resource = field.bindingInfo?.resource ?? 'process';
1276
+ throw new Error(`${type} "${id}": ${blockedField} reads ${ref}, which no .binding() declares with a value before this node, ` +
1277
+ `so the ${resource} key \`${pattern}\` it is paired under cannot be built. Declare ` +
1278
+ `\`.binding('${refId}', { resource: '${resource}', propertyAttribute: '${blockedField}', value: '…' })\` first, or pass the value itself.`);
1279
+ }
1280
+ return { key: resolved.key, referenced: resolved.referenced.map((bid) => byId(bid)).filter((d) => d !== undefined) };
1281
+ }
1230
1282
  /** @internal Shared body of the Orchestrator process-invocation methods. */
1231
- orchestratorNode(id, type, opts, body) {
1283
+ orchestratorNode(id, type, opts, body,
1284
+ /** A member's own departures from the family: the business rule's v3 stamp and `_label`. */
1285
+ own = {}) {
1232
1286
  // The `uipath:type` version selects the engine's addressing branch — see
1233
1287
  // OrchestratorOpts.process. By name → v2 (`name` + `folderPath`); by key → v1
1234
1288
  // (`releaseKey` + `folderKey`). A literal release key becomes a `process`/`Key`
@@ -1254,12 +1308,13 @@ class ScopeBuilder {
1254
1308
  id,
1255
1309
  name: opts.name,
1256
1310
  type,
1257
- typeVersion: byKey ? 'v1' : 'v2',
1311
+ typeVersion: own.typeVersion ?? (byKey ? 'v1' : 'v2'),
1258
1312
  context: this.bindRequiredContext(id, type, {
1259
1313
  name: opts.process,
1260
1314
  ...(opts.folder !== undefined ? { folderPath: opts.folder } : {}),
1261
1315
  ...(releaseKey !== undefined ? { releaseKey } : {}),
1262
1316
  ...(byKey && opts.folderKey !== undefined ? { folderKey: opts.folderKey } : {}),
1317
+ ...(own.context ?? {}),
1263
1318
  }),
1264
1319
  inputs: opts.input ?? {},
1265
1320
  outputs: opts.outputs,
@@ -1355,12 +1410,23 @@ class ScopeBuilder {
1355
1410
  *
1356
1411
  * @remarks
1357
1412
  * **Not a DMN decision table**, despite the element and the label. The registry spec
1358
- * is an Orchestrator process invocation — `releaseKey`/`folderId`/`folderPath`/`name`
1359
- * plus `JobArguments`, `BindingPattern: businessRule` over `Resource: process` —
1360
- * structurally identical to {@link ScopeBuilder.startProcess}. `process` names a
1361
- * package whose Orchestrator process type is `BusinessRules`; whatever decision logic
1362
- * exists lives INSIDE that package, not in the `.bpmn`. So this is the job-start
1363
- * family's sixth member, not a rules subsystem, and it shares every option with it.
1413
+ * is an Orchestrator process invocation — `name`/`folderPath` plus `JobArguments` —
1414
+ * structurally a {@link ScopeBuilder.startProcess}. `process` names a package whose
1415
+ * Orchestrator process type is `BusinessRules`; whatever decision logic exists lives
1416
+ * INSIDE that package, not in the `.bpmn`. So this is the job-start family's sixth
1417
+ * member, not a rules subsystem.
1418
+ *
1419
+ * Where it departs from the family, the registry says so and this follows it:
1420
+ *
1421
+ * - the task is `version="v3"` — below v3 the engine cannot run the `.uirule`
1422
+ * package a BusinessRules project deploys — so a rule is always addressed by
1423
+ * name, and `releaseKey` / `folderKey` are refused;
1424
+ * - `name` and `folderPath` are each bound to a `BusinessRule` resource
1425
+ * (`propertyAttribute` `name` / `folderPath`), both keyed `<folder>.<name>`
1426
+ * (`<name>` alone when the folder is empty), declared on your behalf as
1427
+ * `<id>_name` / `<id>_folderPath`; an unbound `_label` carries the rule's name;
1428
+ * - two output rows: `output` (`=result`, in `outputVar`) and `Error` (`=Error`,
1429
+ * in `<id>_Error`), both `jsonSchema`.
1364
1430
  *
1365
1431
  * Distinct from the Case SDK's `rule()`, which declares DNF lifecycle conditions
1366
1432
  * (`case-entered`, `required-tasks-completed`, …) deciding when a stage enters or
@@ -1380,7 +1446,7 @@ class ScopeBuilder {
1380
1446
  * process: 'DiscountTier',
1381
1447
  * folder: 'Finance',
1382
1448
  * input: { amount: 4200 },
1383
- * outputs: { tier: '=response.tier' },
1449
+ * outputs: { tier: '=result.DiscountTier.tier' },
1384
1450
  * })
1385
1451
  * .endEvent('done')
1386
1452
  * .sequenceFlow('start', 'tier')
@@ -1389,28 +1455,38 @@ class ScopeBuilder {
1389
1455
  * ```
1390
1456
  *
1391
1457
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
1458
+ *
1459
+ * **The result is keyed by decision name.** The platform's `output` row is the whole
1460
+ * result (`source="=result"`) and a decision's column reads as
1461
+ * `=result.<Decision>.<column>` — `vars.<id>_businessRuleResponse.<Decision>.<column>`
1462
+ * downstream — never `=result.<column>` (canvas spec `outputNotes`; `uip rules describe
1463
+ * <name>` lists the decisions and columns, and only a decision-keyed rule, one whose
1464
+ * `OutputArguments` carry `x-uipath-decision-keyed`, fits this row).
1392
1465
  * @param opts - Which rule package, folder, and input arguments. `outputVar` defaults
1393
- * to `<id>_businessRuleResponse` — see the note in the implementation for why this one
1394
- * type does not take its default from the spec.
1466
+ * to `<id>_businessRuleResponse`. `releaseKey` and `folderKey` are refused: a v3 rule
1467
+ * is addressed by name.
1395
1468
  * @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
1396
1469
  * @returns This builder, so calls chain.
1397
1470
  */
1398
1471
  businessRule(id, opts, body) {
1399
- // The ONE place this type departs from its family, and deliberately.
1400
- //
1401
- // Every other member takes its default output variable from the registry's
1402
- // `OutputName` (`Process response` → `<id>_processResponse`). This type's
1403
- // `OutputName` is literally its designer LABEL, `Execute business rule [Preview]`,
1404
- // so the derived default would be `<id>_executeBusinessRulePreview`: unreadable,
1405
- // and — worse — it encodes the preview marker in a name authors write expressions
1406
- // against. When `[Preview]` drops the label becomes `Execute business rule`, the
1407
- // derived default silently becomes `<id>_executeBusinessRule`, and every
1408
- // `vars.<id>_executeBusinessRulePreview` reference in every authored process starts
1409
- // resolving to undefined — a silent wrong-answer change from a cosmetic spec edit.
1410
- //
1411
- // Pinning it here makes the default independent of the label. The output ROW still
1412
- // carries the registry's own `name`, so the artifact is unchanged.
1413
- return this.orchestratorNode(id, 'Orchestrator.BusinessRules', { ...opts, outputVar: opts.outputVar ?? `${id}_businessRuleResponse` }, body);
1472
+ if (opts.releaseKey !== undefined || opts.folderKey !== undefined) {
1473
+ throw new Error(`businessRule('${id}'): a business rule is addressed by name and folder (version v3 binds the ` +
1474
+ 'BusinessRule resource by `name` + `folderPath`); drop `releaseKey` / `folderKey`.');
1475
+ }
1476
+ // The default output variable is pinned rather than derived. Derived, it would come
1477
+ // from the registry's `OutputName`, which was once the designer LABEL, `Execute
1478
+ // business rule [Preview]` — a `[Preview]` marker in a name authors write expressions
1479
+ // against, and one that changed when the marker dropped. Pinning keeps every
1480
+ // `vars.<id>_businessRuleResponse` reference valid across that kind of spec edit.
1481
+ return this.orchestratorNode(id, 'Orchestrator.BusinessRules',
1482
+ // `folderPath` is a REQUIRED binding of the type: a rule in the running job's own
1483
+ // folder is bound with an empty path, so the pair is keyed by the name alone
1484
+ // (`uip maestro bpmn validate` accepts it); leaving the field out instead drew
1485
+ // BINDING_REQUIRED and EMPTY_REQUIRED_FIELD from `check`.
1486
+ { ...opts, folder: opts.folder ?? '', outputVar: opts.outputVar ?? `${id}_businessRuleResponse` }, body,
1487
+ // `_label` is the one unbound context field: the rule's name, which the canvas
1488
+ // shows while `name` itself is a binding reference.
1489
+ { typeVersion: 'v3', context: { _label: opts.process } });
1414
1490
  }
1415
1491
  /**
1416
1492
  * Add an item to an Orchestrator **queue** (`Orchestrator.CreateQueueItem`, or
@@ -69,6 +69,7 @@ export function check(built, opts = {}) {
69
69
  checkFolderBindings(built, diags);
70
70
  checkEntryPointId(built, diags);
71
71
  checkMethodParentheses(built, diags);
72
+ checkConnectorRows(built, diags);
72
73
  checkConnectors(built, opts, diags);
73
74
  checkConnectorEvents(built, opts, diags);
74
75
  return diags;
@@ -219,6 +220,69 @@ function checkMethodParentheses(built, diags) {
219
220
  };
220
221
  walk(built.nodes, built.flows);
221
222
  }
223
+ /** The Integration Service activity types, whose payload rows the platform reads only by `target`. */
224
+ const CONNECTOR_ACTIVITY_TYPES = new Set(['Intsvc.ActivityExecution', 'Intsvc.AsyncExecution']);
225
+ /**
226
+ * The targets the platform's reader knows (`UiPathExtensionsProvider`): anything else —
227
+ * an empty string included — falls through its switch to `Body`, and the row becomes the
228
+ * whole request body. `pathParameters` / `queryParameters` are the aggregate forms.
229
+ */
230
+ const PLATFORM_ROW_TARGETS = new Set(['body', 'bodyField', 'header', 'path', 'query', 'file', 'pathParameters', 'queryParameters']);
231
+ /**
232
+ * A connector activity's payload rows, as the platform reads them.
233
+ *
234
+ * Integration Service reads a connector's payload rows by target only, and its reader
235
+ * maps a missing, empty or unknown target to `Body` — so such a row does not vanish,
236
+ * it REPLACES the request body. The 2026-10-16 eval run's agent wrote
237
+ * `inputRows: [{ name: 'script', type: 'string', value }]` for a JDBC query: it
238
+ * validated, and faulted live with `Value for required parameter 'script' not found`,
239
+ * while the v1 arm's `<uipath:input name="body" target="body">{"script":"…"}</uipath:input>`
240
+ * ran. `inputs: { script }` folds a request field into the body row, so the remedy is
241
+ * the plain record for a body field, or the row's own target when it is a real slot.
242
+ * And because an `inputRows` list REPLACES `inputs` at emission (typed-node.ts), a
243
+ * node that sets both silently drops every `inputs` field — the same live fault.
244
+ */
245
+ function checkConnectorRows(built, diags) {
246
+ for (const n of allNodes(built.nodes)) {
247
+ if (n.kind !== 'typed' || !CONNECTOR_ACTIVITY_TYPES.has(n.type))
248
+ continue;
249
+ if (n.inputRows && Object.keys(n.inputs ?? {}).length > 0) {
250
+ diags.push({
251
+ level: 'error',
252
+ code: 'CONNECTOR_INPUTS_SHADOWED',
253
+ where: n.id,
254
+ message: `Connector activity "${n.id}" sets both \`inputs\` (${Object.keys(n.inputs).map((k) => `"${k}"`).join(', ')}) and \`inputRows\`; ` +
255
+ `an \`inputRows\` list replaces \`inputs\` entirely at emission, so those fields are never sent. Spell them as rows too ` +
256
+ `(a body field as \`{ name: 'body', type: 'json', target: 'body', body: { … } }\`), or drop \`inputRows\` and write every field in \`inputs\`.`,
257
+ });
258
+ }
259
+ for (const row of n.inputRows ?? []) {
260
+ const target = row.target ?? '';
261
+ if (target === '') {
262
+ diags.push({
263
+ level: 'error',
264
+ code: 'CONNECTOR_INPUT_ROW_NO_TARGET',
265
+ where: n.id,
266
+ message: `Connector activity "${n.id}" input row "${row.name}" has no target. Integration Service reads a connector's ` +
267
+ `payload rows by target, and its reader maps a missing or empty one to the request body — so this row validates and ` +
268
+ `then becomes the WHOLE body, replacing the body row. Give the row its target — \`target: 'body'\` folds it into ` +
269
+ `the request body, \`path\`/\`query\`/\`file\` are the other slots — or move EVERY row to \`inputs: { ${row.name}: … }\` ` +
270
+ `and drop \`inputRows\`: an \`inputRows\` list, when present, replaces \`inputs\` entirely.`,
271
+ });
272
+ }
273
+ else if (!PLATFORM_ROW_TARGETS.has(target)) {
274
+ diags.push({
275
+ level: 'error',
276
+ code: 'CONNECTOR_INPUT_ROW_TARGET_UNKNOWN',
277
+ where: n.id,
278
+ message: `Connector activity "${n.id}" input row "${row.name}" has target "${target}", which the platform's reader does not know; ` +
279
+ `it falls through to the request body, so this row would replace the body. Use one of ` +
280
+ `${[...PLATFORM_ROW_TARGETS].map((t) => `'${t}'`).join(', ')}.`,
281
+ });
282
+ }
283
+ }
284
+ }
285
+ }
222
286
  const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
223
287
  /**
224
288
  * `.metadata({ entryPointId })` must be a GUID.
@@ -26,6 +26,9 @@ export function createConnectorTask(ctx, n) {
26
26
  // authored for rather than an inferred one.
27
27
  ctxInput(m, 'operation', 'string', op.name ?? op.objectName ?? n.action),
28
28
  ctxInput(m, 'connectorKey', 'string', rc.connectorKey),
29
+ // The registry template's own value, and the one the CLI's V1 packaging path keys
30
+ // on; V2 (`Data.V2`) is tracked in https://github.com/UiPath/builder-sdk/issues/1298, blocked on UiPath/cli#5209.
31
+ ctxInput(m, 'activityConfigurationVersion', 'string', 'v1'),
29
32
  ];
30
33
  const connRef = bindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
31
34
  if (connRef) {
@@ -1,4 +1,4 @@
1
- import { typedNodeOutputVar } from './typed-node.js';
1
+ import { typedNodeAdditionalOutputVars, typedNodeOutputVar } from './typed-node.js';
2
2
  /**
3
3
  * The variables a connector task's output rows land in.
4
4
  *
@@ -38,7 +38,7 @@ export function typedNodeDeclaredVars(n) {
38
38
  if (n.outputRows)
39
39
  return n.outputRows.map((r) => r.var).filter((v) => v !== undefined);
40
40
  const v = typedNodeOutputVar(n.id, n.type, n.outputVar, n.spec);
41
- return [...(v ? [v] : []), ...Object.keys(n.outputs ?? {})];
41
+ return [...(v ? [v] : []), ...typedNodeAdditionalOutputVars(n.id, n.type, n.spec), ...Object.keys(n.outputs ?? {})];
42
42
  }
43
43
  /**
44
44
  * Every variable name a node DECLARES by writing to it — the platform counts a
@@ -58,8 +58,15 @@ export function nodeDeclaredVars(n) {
58
58
  }
59
59
  if (n.kind === 'typed')
60
60
  return typedNodeDeclaredVars(n);
61
- if (n.kind === 'scriptTask')
61
+ // Spelled-out rows REPLACE the derived ones, so they are the whole answer — the
62
+ // same rule as `typedNodeDeclaredVars`. Reading only `outputs` here left a script
63
+ // task authored with `outputRows` (the form decompile writes) declaring nothing,
64
+ // so every `vars.<row.var>` downstream was EXPR_UNDECLARED_REF.
65
+ if (n.kind === 'scriptTask') {
66
+ if (n.outputRows)
67
+ return n.outputRows.map((r) => r.var).filter((v) => v !== undefined);
62
68
  return Object.keys(n.outputs);
69
+ }
63
70
  if (n.kind === 'task')
64
71
  return Object.keys(n.set);
65
72
  return [];
@@ -419,18 +419,32 @@ function outputsOf(payload, _id, type) {
419
419
  const rows = payload?.output ?? [];
420
420
  const extras = {};
421
421
  let outputVar;
422
- const specName = type ? registryType(type).output?.name : undefined;
422
+ const spec = type ? registryType(type) : undefined;
423
+ const specName = spec?.output?.name;
424
+ // A row the registry declares beside the main one (a business rule's `Error`) is
425
+ // re-emitted by the builder, so lifting it as an extra would write it twice on the
426
+ // way back. Matched by NAME (and source): an export that binds it to a variable
427
+ // other than the derived `<id>_<name>` is spelled out verbatim instead
428
+ // (`outputRowsNeedSpellingOut`), which carries that variable through exactly.
429
+ const declaredBeside = new Map((spec?.additionalOutputs ?? []).map((o) => [o.name, o.source ?? undefined]));
423
430
  for (const row of rows) {
424
431
  if (specName !== undefined && row.name === specName) {
425
432
  // Only worth emitting when it differs from the default the builder derives.
426
433
  outputVar = row.var;
427
434
  continue;
428
435
  }
436
+ if (declaredBeside.has(row.name) && (declaredBeside.get(row.name) === undefined || declaredBeside.get(row.name) === row.source))
437
+ continue;
429
438
  if (row.source !== undefined)
430
439
  extras[row.var ?? row.name] = row.source;
431
440
  }
432
441
  return { outputVar, extras };
433
442
  }
443
+ /** The variable the builder derives for each row the registry declares beside the main one. */
444
+ function derivedExtraVars(id, type) {
445
+ const spec = type ? registryType(type) : undefined;
446
+ return new Map((spec?.additionalOutputs ?? []).map((o) => [o.name, `${id}_${o.name}`]));
447
+ }
434
448
  /**
435
449
  * Whether these rows can be reproduced by the compact `outputVar` + `outputs` pair,
436
450
  * or need spelling out.
@@ -445,10 +459,16 @@ function outputsOf(payload, _id, type) {
445
459
  * form stays in use wherever it is exact, so the 30 other extension types decompile
446
460
  * to exactly the source they did before, and only a row that genuinely needs the
447
461
  * long form gets it.
462
+ *
463
+ * A row the registry declares beside the main one (`derivedBeside`: a business rule's
464
+ * `Error`) that an export binds to a variable other than the derived `<id>_<name>`
465
+ * needs it too: the compact form cannot rename that row, and lifting it as an extra
466
+ * would have the builder add its own beside it.
448
467
  */
449
- function outputRowsNeedSpellingOut(payload) {
468
+ function outputRowsNeedSpellingOut(payload, derivedBeside = new Map()) {
450
469
  const rows = payload?.output ?? [];
451
- return rows.some((row) => row.body !== undefined ||
470
+ return rows.some((row) => (derivedBeside.has(row.name) && row.var !== undefined && row.var !== derivedBeside.get(row.name)) ||
471
+ row.body !== undefined ||
452
472
  row.custom !== undefined ||
453
473
  row.description !== undefined ||
454
474
  row.target !== undefined ||
@@ -522,7 +542,7 @@ function emitTypedNode(chain, el, payload, type) {
522
542
  const verbatimContext = contextRowsNeedSpellingOut(payload, type) ? emitContextRows(payload) : undefined;
523
543
  const { outputVar, extras } = outputsOf(payload, id, type);
524
544
  const defaultVar = spec.output ? `${id}_${identifierize(spec.output.name)}` : undefined;
525
- const verbatimRows = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
545
+ const verbatimRows = outputRowsNeedSpellingOut(payload, derivedExtraVars(id, type)) ? emitOutputRows(payload) : undefined;
526
546
  chain.add(`.activity(${tsText(id)}, ${tsText(type)}${optionalArg(opts([
527
547
  ['name', el.name !== undefined ? tsText(el.name) : undefined],
528
548
  // Only when it differs from the registry's own template, so an
@@ -2012,15 +2032,23 @@ rehome) {
2012
2032
  // a bodied declaration through: ten `error<n>` declarations owned by boundary
2013
2033
  // events in `claims-triage` were skipped as derived and then nobody derived them
2014
2034
  // — they vanished from the recompiled process.
2015
- if (payload && type && type !== 'BPMN.Variables' && typeDescribesElement(localName(el.$type), type)) {
2035
+ // A script task's default payload type is `BPMN.Variables` — the same type a
2036
+ // variable-assignment task carries, which derives nothing. The script task with
2037
+ // spelled-out rows is admitted by its element, not its type: those rows are the
2038
+ // declaration on the way back in (`implicitNodeVars`' script arm), so they are
2039
+ // skipped here too, or the source redeclares every one of them in `.vars()`.
2040
+ const spelledOutScriptRows = localName(el.$type) === 'scriptTask' && type === 'BPMN.Variables' && outputRowsNeedSpellingOut(payload);
2041
+ if (payload && type && (spelledOutScriptRows || (type !== 'BPMN.Variables' && typeDescribesElement(localName(el.$type), type)))) {
2016
2042
  for (const row of payload.output ?? []) {
2017
2043
  if (!row.var)
2018
2044
  continue;
2019
- // A script task's rows are NOT spelled-out `outputRows` on the way back —
2020
- // `implicitNodeVars` derives its declarations from `outputs` keys, so they
2021
- // carry the id as the name and that arm's own wire type. Every other typed
2022
- // node's rows go through `rowDeclaration`, the same call the serializer makes.
2023
- candidates.set(row.var, type === 'BPMN.ScriptTask'
2045
+ // A script task's rows come back as `outputs` when that form reproduces
2046
+ // them, and `implicitNodeVars` then derives their declarations from the
2047
+ // keys: id as the name, that arm's own wire type. Rows that need spelling
2048
+ // out (`emitScriptTask` writes `outputRows` exactly when this holds) are
2049
+ // declared through `rowDeclaration` on the way back in, like every other
2050
+ // typed node's, so they are matched the same way here.
2051
+ candidates.set(row.var, type === 'BPMN.ScriptTask' && !outputRowsNeedSpellingOut(payload)
2024
2052
  ? { owner: el.id, wire: 'string' }
2025
2053
  : { owner: el.id, ...rowDeclaration({ name: row.name, type: row.type, schema: row.body }) });
2026
2054
  }
@@ -35,11 +35,11 @@ type ExternalNode = Extract<BpmnNode, {
35
35
  /**
36
36
  * The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
37
37
  *
38
- * The emitters below take a connector event's type from the library's
39
- * `runtime.serviceType`, which is the authority. This is the same answer derived
40
- * from the node alone, so the serializer can declare the node's output variable
41
- * before it has resolved anything — and the emitter asserts the two agree, so the
42
- * shortcut cannot silently diverge.
38
+ * The method the author called decides the placement — `.eventTrigger()` a start
39
+ * event, `.waitForEvent()` a receive task — so the type is known from the node alone,
40
+ * and the serializer can declare the node's output variable before it has resolved
41
+ * anything. The emitter uses this same value; the library only has to say the
42
+ * operation is a connector event.
43
43
  */
44
44
  export declare function intsvcServiceType(n: EventNode | ExternalNode): string;
45
45
  /**
@@ -55,11 +55,10 @@ export declare function intsvcOutputVar(n: EventNode | ExternalNode): string | u
55
55
  /**
56
56
  * A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
57
57
  *
58
- * The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
59
- * method the author called. That is deliberate: the descriptor is the authority on
60
- * which placement it describes, so a `trigger` descriptor reached through
61
- * `.waitForEvent()` is caught here rather than emitting a start-event type onto a
62
- * receive task — a mismatch both offline validators accept.
58
+ * The extension type comes from the method the author called; the library only has
59
+ * to say the operation is a connector event (`runtime.serviceType` is one of the
60
+ * event types). A prepared descriptor carries the START form's runtime whichever
61
+ * method reaches it, so the placement, not the descriptor, decides the type.
63
62
  *
64
63
  * @param ctx - Serializer context: moddle, library, bindings, binding sink.
65
64
  * @param n - The authored node.
@@ -1,6 +1,7 @@
1
1
  import { createTypedNode, typedNodeOutputVar } from './typed-node.js';
2
2
  import { connectorBindingRef } from './connector.js';
3
3
  import { eventFilterJmes, eventFilterProblem, jmesString, jmesStringProblem, readEventFilter } from '../core/event-filters.js';
4
+ import { resolveEventWhere } from '../core/lookups.js';
4
5
  /**
5
6
  * Which extension type an external invocation lowers to.
6
7
  *
@@ -19,11 +20,11 @@ const EVENT_SERVICE_TYPE = { trigger: 'Intsvc.EventTrigger', event: 'Intsvc.Wait
19
20
  /**
20
21
  * The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
21
22
  *
22
- * The emitters below take a connector event's type from the library's
23
- * `runtime.serviceType`, which is the authority. This is the same answer derived
24
- * from the node alone, so the serializer can declare the node's output variable
25
- * before it has resolved anything — and the emitter asserts the two agree, so the
26
- * shortcut cannot silently diverge.
23
+ * The method the author called decides the placement — `.eventTrigger()` a start
24
+ * event, `.waitForEvent()` a receive task — so the type is known from the node alone,
25
+ * and the serializer can declare the node's output variable before it has resolved
26
+ * anything. The emitter uses this same value; the library only has to say the
27
+ * operation is a connector event.
27
28
  */
28
29
  export function intsvcServiceType(n) {
29
30
  return n.kind === 'connectorEvent'
@@ -45,11 +46,10 @@ export function intsvcOutputVar(n) {
45
46
  /**
46
47
  * A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
47
48
  *
48
- * The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
49
- * method the author called. That is deliberate: the descriptor is the authority on
50
- * which placement it describes, so a `trigger` descriptor reached through
51
- * `.waitForEvent()` is caught here rather than emitting a start-event type onto a
52
- * receive task — a mismatch both offline validators accept.
49
+ * The extension type comes from the method the author called; the library only has
50
+ * to say the operation is a connector event (`runtime.serviceType` is one of the
51
+ * event types). A prepared descriptor carries the START form's runtime whichever
52
+ * method reaches it, so the placement, not the descriptor, decides the type.
53
53
  *
54
54
  * @param ctx - Serializer context: moddle, library, bindings, binding sink.
55
55
  * @param n - The authored node.
@@ -67,14 +67,20 @@ export function createConnectorEvent(ctx, n) {
67
67
  throw new Error(`connector event ${n.key}/${n.eventName} has no runtime.serviceType in the library; ` +
68
68
  `it cannot be authored as a BPMN node.`);
69
69
  }
70
- // The library is the authority, but the serializer has already declared this
71
- // node's output variable from the placement alone (`intsvcServiceType`). If the
72
- // two ever disagree, that variable is wrong — so say so rather than emit it.
70
+ // The PLACEMENT decides the extension type — `.eventTrigger()` starts the
71
+ // process (`Intsvc.EventTrigger`), `.waitForEvent()` pauses it (`Intsvc.WaitForEvent`)
72
+ // — and the serializer has already declared this node's output variable from it
73
+ // (`intsvcServiceType`). The library's `runtime.serviceType` only has to agree
74
+ // that the operation IS a connector event: the platform ships one operation for
75
+ // both placements, the baked library carries the wait form under `.event.`, and
76
+ // `prepare` writes the same entry with the START form's runtime block (measured
77
+ // on `uipath-microsoft-outlook365/email-received`, 2026-10-16) — so reading the
78
+ // placement off that field refused every prepared `.waitForEvent()` with "use
79
+ // .eventTrigger() instead", while the unprepared same node compiled.
73
80
  const expected = intsvcServiceType(n);
74
- if (type !== expected) {
81
+ if (type !== 'Intsvc.EventTrigger' && type !== 'Intsvc.WaitForEvent') {
75
82
  throw new Error(`connector event "${n.id}" was authored as a ${n.subscribe} (${expected}), but the library ` +
76
- `describes ${n.key}/${n.eventName} as ${type}. Use ` +
77
- `${type === 'Intsvc.EventTrigger' ? '.eventTrigger()' : '.waitForEvent()'} instead.`);
83
+ `describes ${n.key}/${n.eventName} as ${type}, which is not a connector event.`);
78
84
  }
79
85
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
80
86
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
@@ -108,10 +114,17 @@ export function createConnectorEvent(ctx, n) {
108
114
  // The expression is the scope's equality clauses AND-joined with the authored
109
115
  // `filters`, each lowered by the designer's own grammar (`core/event-filters.ts`) — the
110
116
  // same lowering the Flow serializer does for `onEvent`/`waitForEvent`.
111
- inputs.body = eventFilterBody(n.id, n.filter, n.filters);
117
+ // A `lookup()` token in the scope is replaced by the id `prepare --resolve`
118
+ // recorded, as Flow's and Case's serializers do; an unrecorded one is refused
119
+ // with the command that records it. Stringified, a token was `[object Object]`.
120
+ const scope = resolveEventWhere(n.filter, ctx.library.lookupResolutions, `connector event "${n.id}"`);
121
+ inputs.body = eventFilterBody(n.id, scope, n.filters);
112
122
  if (n.parameters !== undefined)
113
123
  inputs.parameters = n.parameters;
114
- return createTypedNode(ctx.m, n.id, type, {
124
+ return createTypedNode(ctx.m, n.id,
125
+ // The placement's type, so a prepared `.event.` entry carrying the start form's
126
+ // runtime cannot turn a wait into a start event (see the check above).
127
+ expected, {
115
128
  name: n.name ?? rc.entry.display?.label ?? n.eventName,
116
129
  inputs,
117
130
  outputVar: n.outputVar,