@uipath/maestro-builder-sdk 6.15.5 → 6.16.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.
@@ -20,7 +20,7 @@
20
20
  * separate "tidy" step lays it out. See serialize.ts.
21
21
  */
22
22
  import type { TypeDesc } from '../core/expr.js';
23
- import { type ConnectorOpts } from '../core/actions.js';
23
+ import { type ConnectorOpts, type EventFilter } from '../core/actions.js';
24
24
  import type { ConnectorDescriptor } from '../core/connectors.js';
25
25
  import type { RegistryTypeSpec, TypedContextRow, TypedInputRow, TypedOutputRow } from './typed-node.js';
26
26
  import { type DataServiceOperation } from './data-service.js';
@@ -230,6 +230,8 @@ export type BpmnNode = {
230
230
  /** The `where` scope and extra event parameters, when the operation declares any. */
231
231
  filter?: Record<string, unknown>;
232
232
  parameters?: Record<string, unknown>;
233
+ /** Payload conditions — see {@link ConnectorEventOpts.filters}. */
234
+ filters?: EventFilter[];
233
235
  outputVar?: string;
234
236
  outputs?: Record<string, string>;
235
237
  skipCondition?: string;
@@ -841,9 +843,20 @@ export interface ScriptTaskOpts extends ActivityOpts {
841
843
  /** Display name the designer shows on the task. */
842
844
  name?: string;
843
845
  /**
844
- * The script body (Jint JavaScript). Each `inputs` key is available as a
845
- * top-level identifier (for example `inputs: { amount: '=vars.amount' }`
846
- * makes the script read `amount`, not `args.amount`).
846
+ * The script body (Jint JavaScript).
847
+ *
848
+ * @remarks
849
+ * **Read process data as `vars.<id>`, never as a bare identifier.** The runtime
850
+ * injects the product's fixed envelope into the script's scope — `vars` (every
851
+ * variable in scope, by id), `metadata`, and `iterator` inside a loop body — and
852
+ * nothing else. A script written `return amount * 2` with
853
+ * `inputs: { amount: '=vars.amount' }` passes `check`, `compile` and
854
+ * `uip maestro bpmn validate` and then faults on the platform with
855
+ * `amount is not defined`; the same script written `return vars.amount * 2`
856
+ * runs. `check` reports the first shape as `SCRIPT_BARE_INPUT_REF`.
857
+ *
858
+ * The serializer writes the envelope for you (see {@link ScriptTaskOpts.inputs}), so
859
+ * a script task that reads `vars.<id>` needs no `inputs` at all.
847
860
  */
848
861
  script: string;
849
862
  /** The script language marker. Defaults to `'JavaScript'` (Jint). */
@@ -862,7 +875,20 @@ export interface ScriptTaskOpts extends ActivityOpts {
862
875
  * `v3`, spelling an absent element as `'v1'`.
863
876
  */
864
877
  scriptVersion?: string;
865
- /** `uipath:input` rows: field name → `=`-expression read into the script. */
878
+ /**
879
+ * Extra keys for the `args` input, after the envelope: key → `=`-expression.
880
+ *
881
+ * @remarks
882
+ * The emitted `<uipath:input name="args" target="bodyField">` is always
883
+ * `{"vars":"=vars","metadata":"=metadata", …inputs}` — the shape every Studio Web
884
+ * export carries and the only one the platform runtime honours. The runtime does
885
+ * NOT make an `inputs` key readable as a bare identifier (see
886
+ * {@link ScriptTaskOpts.script}), so for ordinary authoring leave this off and read
887
+ * `vars.<id>` in the script. It exists for imports — the designer writes
888
+ * `"iterator[0]":"=iterator[0]"` to scope a loop body — and for a key the author
889
+ * knows the runtime reads. `vars` and `metadata` given here override the envelope's
890
+ * values, which is never useful but keeps a round trip byte-faithful.
891
+ */
866
892
  inputs?: Record<string, string>;
867
893
  /**
868
894
  * `uipath:output` rows: variable id → `=`-expression.
@@ -891,7 +917,14 @@ export interface ScriptTaskOpts extends ActivityOpts {
891
917
  outputRows?: TypedOutputRow[];
892
918
  /**
893
919
  * The `uipath:inputSchema` body inside the mapping's `uipath:context` — a JSON
894
- * Schema for the `args` the script receives. Real script tasks all carry one.
920
+ * Schema for the `args` the script receives.
921
+ *
922
+ * @remarks
923
+ * Real script tasks all carry one, so when this is absent the serializer writes the
924
+ * product's: `{"type":"object","properties":{"vars":{"type":"object"},"metadata":{"type":"object"}}}`,
925
+ * plus an untyped property per extra `inputs` key. Set it only on an import whose
926
+ * schema differs (the designer's carries `$schema`); `bpmn-decompile` drops the
927
+ * default and keeps anything else verbatim.
895
928
  */
896
929
  inputSchema?: unknown;
897
930
  /**
@@ -1103,10 +1136,57 @@ export interface HttpOpts extends ActivityOpts {
1103
1136
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
1104
1137
  /** Request headers — serialized to the node's json context field. */
1105
1138
  headers?: Record<string, unknown>;
1106
- /** Query parameters — serialized to the node's json context field. */
1139
+ /**
1140
+ * Query parameters — serialized to the node's json context field. In connector mode
1141
+ * (see {@link HttpOpts.connection}) they are appended to the URL, so the URL must be
1142
+ * a literal there.
1143
+ */
1107
1144
  parameters?: Record<string, unknown>;
1108
- /** Request body — serialized to the node's json context field. */
1145
+ /**
1146
+ * Request body — serialized to the node's json context field.
1147
+ *
1148
+ * @remarks
1149
+ * Leave it off for a request that has none: the row is written as `{}`, which the
1150
+ * runtime accepts. The registry declares the default as the text `null`, and the
1151
+ * runtime refuses that (`IntSvcUnifiedHttpRequestArgs`: `Body cannot be null`,
1152
+ * incident 102018) — every GET this SDK emitted before flow-v2-run-12 faulted on it.
1153
+ */
1109
1154
  body?: unknown;
1155
+ /**
1156
+ * Symbolic binding name of an Integration Service **connection** to send the
1157
+ * request through — which switches the node to CONNECTOR mode.
1158
+ *
1159
+ * @remarks
1160
+ * Without it the node is `Intsvc.UnifiedHttpRequest` in `mode="manual"`: the
1161
+ * process owns the URL, headers and auth, and nothing on the tenant is involved.
1162
+ * With it the node is the connector activity the v1 skill calls "connector HTTP" —
1163
+ * `Intsvc.ActivityExecution` on the generic HTTP connector (`uipath-uipath-http`,
1164
+ * object `http-request`), whose `body` carries the request
1165
+ * (`{"authentication":"connector","targetConnector":…,"connection":"=bindings.…","method":"GET","url":…,"path":…,"headers":…,"body":…}`)
1166
+ * and whose auth is the connection's. That is the shape for "call an endpoint the
1167
+ * connector catalog has no activity for, through an existing connection" — the
1168
+ * non-catalog HTTP fallback — and it was not expressible before: in flow-v2-run-12
1169
+ * both fallback cards (non-catalog-http-fallback, slack-http-fallback) authored a
1170
+ * connectionless node because this option did not exist, and the grader found
1171
+ * `connectorKey values seen: []`.
1172
+ *
1173
+ * Pair it with {@link HttpOpts.folder}; both resolve as `.dataService()`'s do — an
1174
+ * existing binding by id, else `<id>_connection` / `<id>_folder` declared for you
1175
+ * with the symbolic name as the default, which `bindings.json` fills with the real
1176
+ * connection and folder ids.
1177
+ * `connectorKey` names a connection of another connector type when the endpoint
1178
+ * belongs to one (see {@link HttpOpts.connectorKey}).
1179
+ */
1180
+ connection?: string;
1181
+ /** Symbolic binding name of the connection's folder — connector mode only. */
1182
+ folder?: string;
1183
+ /**
1184
+ * The connector the connection belongs to, connector mode only. Defaults to
1185
+ * `uipath-uipath-http`, the generic HTTP connector. Set it when the connection is a
1186
+ * specific connector's (a Slack connection calling a Slack endpoint the catalog does
1187
+ * not expose); the body's `targetConnector` follows it.
1188
+ */
1189
+ connectorKey?: string;
1110
1190
  /**
1111
1191
  * EXTRA output rows: variable id → `=`-expression read against the node's own result,
1112
1192
  * e.g. `{ runStatus: '=Status' }`.
@@ -1136,14 +1216,48 @@ export interface HttpOpts extends ActivityOpts {
1136
1216
  export interface OrchestratorOpts extends ActivityOpts {
1137
1217
  /** Display name the designer shows on the node. */
1138
1218
  name?: string;
1139
- /** The Orchestrator process to start, by name. */
1219
+ /**
1220
+ * The Orchestrator process to start, by name.
1221
+ *
1222
+ * @remarks
1223
+ * **How the runtime finds the process depends on the `uipath:type` version the node
1224
+ * carries, and the SDK picks it from what you pass.** With `process` (+ `folder`) and no
1225
+ * `releaseKey`, the node is emitted at `version="v2"`: the engine's v2 branch
1226
+ * (`OrchestratorRpaActivities`, `ExecuteApiWorkflowArgs`, `HermesJobActivities`) reads
1227
+ * the `name` and `folderPath` context and resolves the release itself. With
1228
+ * `releaseKey`, the node is emitted at `version="v1"`, whose branch reads `releaseKey`
1229
+ * and `folderKey` and ignores the name.
1230
+ *
1231
+ * Measured on flow-v2-run-12: a `v1` node carrying only `name`/`folderPath` — what this
1232
+ * SDK used to emit for every job start — passed `check`, `compile` and `validate` and
1233
+ * faulted on the cloud with `170009 Could not get value for key:ReleaseKey from context
1234
+ * in input`; the local engine resolves by name whatever the version says, so it ran
1235
+ * there. See docs/BPMN_EVAL_RUN12_FINDINGS.md §4 P0-3.
1236
+ */
1140
1237
  process: string;
1141
1238
  /** Folder path the process lives in. Omit for the personal/default folder. */
1142
1239
  folder?: string;
1143
1240
  /** Input arguments — serialized as the node's `JobArguments` body. */
1144
1241
  input?: Record<string, unknown>;
1145
- /** The release key, when a binding supplies it (`=bindings.<id>`). */
1242
+ /**
1243
+ * Address the release by KEY instead of by name — the deployed resource's `Key` from
1244
+ * `uip or processes list --folder-path <path> --output json`, or `=bindings.<id>` for a
1245
+ * `process` binding whose `propertyAttribute` is `Key`. Switches the node to
1246
+ * `version="v1"` (see {@link OrchestratorOpts.process}); a literal is turned into such
1247
+ * a binding for you. Pair it with {@link OrchestratorOpts.folderKey} — the v1 branch
1248
+ * does not read `folder`. For imports and for a release the tenant cannot resolve by
1249
+ * name; ordinary authoring passes `process` and `folder` and leaves this off.
1250
+ */
1146
1251
  releaseKey?: string;
1252
+ /**
1253
+ * The folder's `FolderKey` GUID (the same `uip or processes list` row carries it), for
1254
+ * a node addressed by {@link OrchestratorOpts.releaseKey}. The v1 branch reads this and
1255
+ * not `folderPath`: `Orchestrator.ExecuteApiWorkflowAsync` with a key and no
1256
+ * `folderKey` faults with `Could not get value for key:FolderKey from context in
1257
+ * input`, which `check` reports ahead of time as JOB_FOLDER_KEY_MISSING. Ignored when
1258
+ * the node is addressed by name.
1259
+ */
1260
+ folderKey?: string;
1147
1261
  /**
1148
1262
  * EXTRA output rows: variable id → `=`-expression read against the node's own result,
1149
1263
  * e.g. `{ runStatus: '=Status' }`.
@@ -1330,12 +1444,37 @@ export interface ConnectorEventOpts extends ActivityOpts {
1330
1444
  */
1331
1445
  object?: string;
1332
1446
  /**
1333
- * The `where` scope narrowing the subscription, when the operation declares
1334
- * parameters. `Library.eventParameterFields()` is the authority on which names an
1335
- * operation accepts — and note that "declares none" is a real answer for some
1336
- * operations (HTTP Webhook scopes by the connection), not a lookup failure.
1447
+ * The subscription's SCOPE — the operation's own event parameters, by equality
1448
+ * (`{ parentFolderId: '<mail folder id>' }` for Outlook `email-received`).
1449
+ * `Library.eventParameterFields()` is the authority on which names an operation
1450
+ * accepts — and "declares none" is a real answer for some (HTTP Webhook scopes by
1451
+ * the connection), not a lookup failure.
1452
+ *
1453
+ * @remarks
1454
+ * Emitted into the node's `body` row as both `queryParams` and the leading
1455
+ * `(k == 'v')` clauses of `filters.expression` — the one place the platform reads a
1456
+ * subscription filter (`PO.BpmnEngine` `GetEventFilters`). A payload field that is
1457
+ * not an event parameter (`subject`) does not belong here; it goes in
1458
+ * {@link ConnectorEventOpts.filters}, and `check` reports it here as
1459
+ * `EVENT_UNKNOWN_PARAM`.
1337
1460
  */
1338
1461
  filter?: Record<string, unknown>;
1462
+ /**
1463
+ * Conditions on the event PAYLOAD, AND-joined after the scope:
1464
+ * `[{ field: 'subject', contains: 'TestWaitFor' }]`. The same grammar and the same
1465
+ * lowering as the Flow builder's `waitForEvent`/`onEvent` — `contains(f,'v')`,
1466
+ * `f=='v'`, `starts_with(f,'v')`, a date compared through `to_number(...)` — written
1467
+ * into `body.filters.expression` behind the scope's clauses.
1468
+ *
1469
+ * @remarks
1470
+ * On flow-v2-run-12 this surface did not exist, so an author who needed
1471
+ * `contains(subject, 'TestWaitFor')` had nowhere to put it but `filter`, where it was
1472
+ * emitted as an untargeted input no handler reads; the wait validated and matched every
1473
+ * mail in the folder (outlook-waitfor-email 1.00 → 0.43). See {@link EventFilter} for
1474
+ * the operator set; `check` refuses a leaf with no operator or two, and warns on a
1475
+ * field the connector does not expose.
1476
+ */
1477
+ filters?: EventFilter[];
1339
1478
  /** Extra event parameters, for operations that declare them. */
1340
1479
  parameters?: Record<string, unknown>;
1341
1480
  /** Variable the event payload lands in. Defaults to `<id>_result`. */
@@ -2258,6 +2397,14 @@ declare abstract class ScopeBuilder {
2258
2397
  * @returns This builder, so calls chain.
2259
2398
  */
2260
2399
  http(id: string, opts: HttpOpts, body?: ActivityBodyFn): this;
2400
+ /**
2401
+ * @internal Connector-mode `.http()`: the request through an Integration Service
2402
+ * connection, as `Intsvc.ActivityExecution` on the generic HTTP connector's
2403
+ * `http-request` object — the shape the v1 arm wrote by hand on flow-v2-run-12
2404
+ * (non-catalog-http-fallback) and the live run completed. The connection and folder
2405
+ * resolve exactly as `.dataService()`'s do.
2406
+ */
2407
+ private connectorHttp;
2261
2408
  /**
2262
2409
  * A UiPath **Data Fabric** (Data Service) record or file operation — create,
2263
2410
  * update, get, query, delete a record, or download / upload / delete a record's
@@ -2358,12 +2505,17 @@ declare abstract class ScopeBuilder {
2358
2505
  */
2359
2506
  startCaseProcess(id: string, opts: OrchestratorAsyncOpts, body?: ActivityBodyFn): this;
2360
2507
  /**
2361
- * Execute an **API workflow**, fire-and-forget
2362
- * (`Orchestrator.ExecuteApiWorkflowAsync`).
2508
+ * Execute an **API workflow** and wait for it (`Orchestrator.ExecuteApiWorkflowAsync`).
2363
2509
  *
2364
2510
  * @remarks
2365
- * The registry offers only the async form, so there is nothing to wait on: the
2366
- * node maps back an error and nothing else.
2511
+ * The type's name says async and its designer label says `Start and wait for API
2512
+ * workflow`; the label is right. The registry offers only this one type for invoking
2513
+ * a published API workflow, it waits for completion, and the result is available to
2514
+ * later nodes. There is no fire-and-forget variant to model.
2515
+ *
2516
+ * Address it by `process` + `folder` (emitted at `uipath:type version="v2"`), or by
2517
+ * `releaseKey` + `folderKey` (v1) — see {@link OrchestratorOpts.process} for why the
2518
+ * version matters and what a v1 node without a key does on the platform.
2367
2519
  *
2368
2520
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
2369
2521
  * @param opts - Which workflow, folder, and input arguments.
@@ -980,6 +980,12 @@ class ScopeBuilder {
980
980
  */
981
981
  http(id, opts, body) {
982
982
  const { name, url, method, headers, parameters, body: requestBody, outputs, outputVar, skipCondition, retry, loop, errorMapping } = opts;
983
+ if (opts.connection !== undefined)
984
+ return this.connectorHttp(id, opts, body);
985
+ if (opts.folder !== undefined || opts.connectorKey !== undefined) {
986
+ throw new Error(`http "${id}": \`folder\` and \`connectorKey\` belong to connector mode, which \`connection\` selects — ` +
987
+ `pass the connection's binding name too, or drop them for a connectionless request.`);
988
+ }
983
989
  return this.pushActivity({
984
990
  kind: 'typed',
985
991
  id,
@@ -1006,6 +1012,89 @@ class ScopeBuilder {
1006
1012
  errorMapping,
1007
1013
  }, body);
1008
1014
  }
1015
+ /**
1016
+ * @internal Connector-mode `.http()`: the request through an Integration Service
1017
+ * connection, as `Intsvc.ActivityExecution` on the generic HTTP connector's
1018
+ * `http-request` object — the shape the v1 arm wrote by hand on flow-v2-run-12
1019
+ * (non-catalog-http-fallback) and the live run completed. The connection and folder
1020
+ * resolve exactly as `.dataService()`'s do.
1021
+ */
1022
+ connectorHttp(id, opts, body) {
1023
+ const connectorKey = opts.connectorKey ?? 'uipath-uipath-http';
1024
+ const connection = this.dataServiceBinding(id, 'connection', opts.connection, 'ConnectionId', `${connectorKey} connection`);
1025
+ const folder = opts.folder !== undefined
1026
+ ? this.dataServiceBinding(id, 'folder', opts.folder, 'folderKey', `${connectorKey} folder`, connection.key)
1027
+ : undefined;
1028
+ // The request's own URL, with `parameters` folded into its query string — the
1029
+ // connector body has no separate parameters slot — and its path for the `path`
1030
+ // field, which the v1 artifact carries beside the URL.
1031
+ let url = opts.url;
1032
+ let path;
1033
+ if (!url.startsWith('=')) {
1034
+ let parsed;
1035
+ try {
1036
+ parsed = new URL(url);
1037
+ }
1038
+ catch {
1039
+ throw new Error(`http "${id}": connector mode needs an absolute URL (got ${JSON.stringify(url)}).`);
1040
+ }
1041
+ for (const [k, v] of Object.entries(opts.parameters ?? {}))
1042
+ parsed.searchParams.set(k, String(v));
1043
+ url = parsed.toString();
1044
+ path = parsed.pathname;
1045
+ }
1046
+ else if (opts.parameters !== undefined) {
1047
+ throw new Error(`http "${id}": connector mode appends \`parameters\` to the URL, so the URL cannot be an expression when they are given — ` +
1048
+ `write them into the expression instead.`);
1049
+ }
1050
+ const responseVar = opts.outputVar ?? `${id}_response`;
1051
+ return this.pushActivity({
1052
+ kind: 'typed',
1053
+ id,
1054
+ name: opts.name,
1055
+ type: 'Intsvc.ActivityExecution',
1056
+ context: {
1057
+ activity: 'Create',
1058
+ connectorKey,
1059
+ connection: connection.ref,
1060
+ ...(folder ? { folderKey: folder.ref } : {}),
1061
+ operation: 'Create',
1062
+ objectName: 'http-request',
1063
+ method: 'POST',
1064
+ path: '/http-request',
1065
+ activityConfigurationVersion: 'v1',
1066
+ metadata: {},
1067
+ },
1068
+ inputs: {},
1069
+ inputRows: [
1070
+ {
1071
+ name: 'body',
1072
+ type: 'json',
1073
+ target: 'body',
1074
+ body: {
1075
+ authentication: 'connector',
1076
+ targetConnector: connectorKey,
1077
+ connection: connection.ref,
1078
+ method: opts.method ?? 'GET',
1079
+ url,
1080
+ ...(path !== undefined ? { path } : {}),
1081
+ headers: opts.headers ?? {},
1082
+ body: opts.body ?? null,
1083
+ },
1084
+ },
1085
+ ],
1086
+ outputRows: [
1087
+ { name: 'response', type: 'jsonSchema', source: '=response', var: responseVar },
1088
+ { name: 'Error', type: 'jsonSchema', source: '=Error', var: `${id}_Error`, schema: errorSchema },
1089
+ ],
1090
+ outputs: opts.outputs,
1091
+ skipCondition: opts.skipCondition,
1092
+ tags: opts.tags,
1093
+ retry: opts.retry,
1094
+ errorMapping: opts.errorMapping,
1095
+ loop: opts.loop,
1096
+ }, body);
1097
+ }
1009
1098
  /**
1010
1099
  * A UiPath **Data Fabric** (Data Service) record or file operation — create,
1011
1100
  * update, get, query, delete a record, or download / upload / delete a record's
@@ -1137,15 +1226,37 @@ class ScopeBuilder {
1137
1226
  }
1138
1227
  /** @internal Shared body of the Orchestrator process-invocation methods. */
1139
1228
  orchestratorNode(id, type, opts, body) {
1229
+ // The `uipath:type` version selects the engine's addressing branch — see
1230
+ // OrchestratorOpts.process. By name → v2 (`name` + `folderPath`); by key → v1
1231
+ // (`releaseKey` + `folderKey`). A literal release key becomes a `process`/`Key`
1232
+ // binding, which is the shape the registry's `bindingInfo` for the field describes
1233
+ // and the one `validate` wants; an `=` expression is the author's own binding.
1234
+ const byKey = opts.releaseKey !== undefined;
1235
+ let releaseKey = opts.releaseKey;
1236
+ if (releaseKey !== undefined && !releaseKey.startsWith('=')) {
1237
+ const bindingId = `${id}_releaseKey`;
1238
+ this._bindingRegistry.declare({
1239
+ id: bindingId,
1240
+ name: opts.process,
1241
+ resource: 'process',
1242
+ propertyAttribute: 'Key',
1243
+ resourceSubType: REGISTRY_GAPS[type]?.resourceSubType,
1244
+ default: releaseKey,
1245
+ resourceKey: releaseKey,
1246
+ });
1247
+ releaseKey = `=bindings.${bindingId}`;
1248
+ }
1140
1249
  return this.pushActivity({
1141
1250
  kind: 'typed',
1142
1251
  id,
1143
1252
  name: opts.name,
1144
1253
  type,
1254
+ typeVersion: byKey ? 'v1' : 'v2',
1145
1255
  context: this.bindRequiredContext(id, type, {
1146
1256
  name: opts.process,
1147
1257
  ...(opts.folder !== undefined ? { folderPath: opts.folder } : {}),
1148
- ...(opts.releaseKey !== undefined ? { releaseKey: opts.releaseKey } : {}),
1258
+ ...(releaseKey !== undefined ? { releaseKey } : {}),
1259
+ ...(byKey && opts.folderKey !== undefined ? { folderKey: opts.folderKey } : {}),
1149
1260
  }),
1150
1261
  inputs: opts.input ?? {},
1151
1262
  outputs: opts.outputs,
@@ -1215,12 +1326,17 @@ class ScopeBuilder {
1215
1326
  return this.orchestratorNode(id, opts.async ? 'Orchestrator.StartCaseMgmtProcessAsync' : 'Orchestrator.StartCaseMgmtProcess', opts, body);
1216
1327
  }
1217
1328
  /**
1218
- * Execute an **API workflow**, fire-and-forget
1219
- * (`Orchestrator.ExecuteApiWorkflowAsync`).
1329
+ * Execute an **API workflow** and wait for it (`Orchestrator.ExecuteApiWorkflowAsync`).
1220
1330
  *
1221
1331
  * @remarks
1222
- * The registry offers only the async form, so there is nothing to wait on: the
1223
- * node maps back an error and nothing else.
1332
+ * The type's name says async and its designer label says `Start and wait for API
1333
+ * workflow`; the label is right. The registry offers only this one type for invoking
1334
+ * a published API workflow, it waits for completion, and the result is available to
1335
+ * later nodes. There is no fire-and-forget variant to model.
1336
+ *
1337
+ * Address it by `process` + `folder` (emitted at `uipath:type version="v2"`), or by
1338
+ * `releaseKey` + `folderKey` (v1) — see {@link OrchestratorOpts.process} for why the
1339
+ * version matters and what a v1 node without a key does on the platform.
1224
1340
  *
1225
1341
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
1226
1342
  * @param opts - Which workflow, folder, and input arguments.
@@ -1518,6 +1634,7 @@ class ScopeBuilder {
1518
1634
  folder: opts.folder,
1519
1635
  folderPath: opts.folderPath,
1520
1636
  filter: opts.filter,
1637
+ filters: opts.filters,
1521
1638
  parameters: opts.parameters,
1522
1639
  outputVar: opts.outputVar,
1523
1640
  outputs: opts.outputs,
@@ -1564,6 +1681,7 @@ class ScopeBuilder {
1564
1681
  folder: opts.folder,
1565
1682
  folderPath: opts.folderPath,
1566
1683
  filter: opts.filter,
1684
+ filters: opts.filters,
1567
1685
  parameters: opts.parameters,
1568
1686
  outputVar: opts.outputVar,
1569
1687
  outputs: opts.outputs,
@@ -21,6 +21,20 @@ import { KNOWN_METHOD_SET } from './known-methods.js';
21
21
  import { EQUALS_EXPR_DIALECT, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, checkBindingLabels, } from '../core/connector-checks.js';
22
22
  import { checkEventResolution, checkEventSubscription, eventParamsFor, } from '../core/event-checks.js';
23
23
  import { bindingRequiredFields, missingRequiredContext, missingRequiredInputs, registryType, REGISTRY_GAPS, unsatisfiableRequiredInputs, } from './typed-node.js';
24
+ /**
25
+ * The job-start family — the types whose engine handler switches addressing on the
26
+ * `uipath:type` version: v2 reads `name`/`folderPath`, v1 reads `releaseKey`/`folderKey`.
27
+ */
28
+ const JOB_START_TYPES = new Set([
29
+ 'Orchestrator.StartJob',
30
+ 'Orchestrator.StartAgentJob',
31
+ 'Orchestrator.ExecuteApiWorkflowAsync',
32
+ 'Orchestrator.StartAgenticProcess',
33
+ 'Orchestrator.StartAgenticProcessAsync',
34
+ 'Orchestrator.StartCaseMgmtProcess',
35
+ 'Orchestrator.StartCaseMgmtProcessAsync',
36
+ 'Orchestrator.BusinessRules',
37
+ ]);
24
38
  /**
25
39
  * Service types the platform expects to name a concrete Orchestrator resource.
26
40
  *
@@ -109,12 +123,13 @@ function checkConnectors(built, opts, diags) {
109
123
  * object, version, connection }` — so the adapter is naming: BPMN calls the
110
124
  * connector `key`, the event `eventName`, and the subscription scope `filter`.
111
125
  *
112
- * `filters` is deliberately absent. Payload filtering is a Flow surface; a BPMN
113
- * connector event carries none, so the `EVENT_FILTER_*` family never fires here.
114
- * The rules that do are the ones about the SUBSCRIPTION — whether it resolves at
115
- * all, whether a generic event named its object, and whether its scope names
116
- * parameters the operation actually declares. `skill-bpmn-outlook-waitfor-email`
117
- * in `flow-v2-run-8` failed on that last one.
126
+ * `filters` is passed through as of flow-v2-run-12: a BPMN connector event carries
127
+ * payload conditions in `body.filters.expression` exactly as a Flow subscription does,
128
+ * so the `EVENT_FILTER_*` family fires here too. The subscription rules are the rest —
129
+ * whether it resolves at all, whether a generic event named its object, and whether
130
+ * its scope names parameters the operation actually declares.
131
+ * `skill-bpmn-outlook-waitfor-email` in `flow-v2-run-8` failed on that last one, and
132
+ * in run 12 on having no `filters` surface at all.
118
133
  */
119
134
  function checkConnectorEvents(built, opts, diags) {
120
135
  for (const n of allNodes(built.nodes)) {
@@ -127,6 +142,7 @@ function checkConnectorEvents(built, opts, diags) {
127
142
  ...(n.version !== undefined ? { version: n.version } : {}),
128
143
  ...(n.object !== undefined ? { object: n.object } : {}),
129
144
  ...(n.filter !== undefined ? { where: n.filter } : {}),
145
+ ...(n.filters !== undefined ? { filters: n.filters } : {}),
130
146
  ...(n.connection !== undefined ? { connection: n.connection } : {}),
131
147
  };
132
148
  const at = (level) => (code, message) => {
@@ -563,6 +579,21 @@ function isRuntimeDuration(v) {
563
579
  // "validates, then no runtime can parse it" trap the timer eval card exists for.
564
580
  return RUNTIME_DURATION.test(v) && /\d/.test(v);
565
581
  }
582
+ /** The `args` keys the platform itself writes and injects — not an author's inputs. */
583
+ const SCRIPT_ENVELOPE_KEYS = new Set(['vars', 'metadata', 'iterator']);
584
+ /**
585
+ * Whether a script body mentions `name` as a bare identifier — not after `.` (a property
586
+ * read such as `vars.amount`), not inside a string or comment. The body is otherwise not
587
+ * parsed: this is the one question about it the platform contract makes answerable.
588
+ */
589
+ function readsBareIdentifier(script, name) {
590
+ const stripped = script
591
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
592
+ .replace(/\/\/[^\n]*/g, ' ')
593
+ .replace(/"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
594
+ const re = new RegExp(`(?<![\\w$.])${name.replace(/\$/g, '\\$')}(?![\\w$])(?!\\s*:)`);
595
+ return re.test(stripped);
596
+ }
566
597
  /** Retry and loop options, checked per activity. */
567
598
  function checkActivityFields(n, diags) {
568
599
  // A script task whose body is missing or blank. The wire form of a script task IS its
@@ -584,6 +615,29 @@ function checkActivityFields(n, diags) {
584
615
  `Pass the Jint JavaScript as \`script\` (\`.scriptTask('${n.id}', { script: 'return { … };' })\`).`,
585
616
  where: n.id,
586
617
  });
618
+ // A script that reads an `inputs` key as a bare identifier. The platform injects only
619
+ // the `args` envelope — `vars`, `metadata`, `iterator` — into the script's scope, so
620
+ // `amount` with `inputs: { amount: '=vars.amount' }` is a ReferenceError at runtime
621
+ // (`amount is not defined`, incident 300501) that `validate` cannot see and the local
622
+ // engine used to hide. Measured on flow-v2-run-12: 3 of 3 cloud-executed script tasks
623
+ // written this way faulted; 0 that read `vars.<id>` did.
624
+ //
625
+ // A warning, not an error, because a brownfield `.bpmn` whose untouched script task
626
+ // carries the shape must still merge — refusing it would be the SDK refusing what
627
+ // `validate` accepts, on a node the edit did not target.
628
+ if (n.kind === 'scriptTask' && n.script) {
629
+ const bare = Object.keys(n.inputs).filter((k) => !SCRIPT_ENVELOPE_KEYS.has(k) && /^[A-Za-z_$][\w$]*$/.test(k) && readsBareIdentifier(n.script, k));
630
+ if (bare.length > 0)
631
+ diags.push({
632
+ level: 'warning',
633
+ code: 'SCRIPT_BARE_INPUT_REF',
634
+ message: `Script task "${n.id}" reads ${bare.map((k) => `\`${k}\``).join(', ')} as a bare identifier, but the platform ` +
635
+ `injects only \`vars\`, \`metadata\` and \`iterator\` into a script's scope — on the cloud this faults with ` +
636
+ `\`${bare[0]} is not defined\` after check, compile and validate have all passed. ` +
637
+ `Read the variable as \`vars.<id>\` (${bare.map((k) => `\`${n.inputs[k].replace(/^=/, '')}\``).join(', ')}) and drop the \`inputs\` entry.`,
638
+ where: n.id,
639
+ });
640
+ }
587
641
  if (!('retry' in n) && !('loop' in n))
588
642
  return;
589
643
  const retry = 'retry' in n ? n.retry : undefined;
@@ -931,6 +985,25 @@ inEventSubProcess = false) {
931
985
  const contextView = { ...n.context };
932
986
  for (const row of n.contextRows ?? [])
933
987
  contextView[row.name] = row.value ?? row.body ?? '';
988
+ // A job start addressed by key (`uipath:type version="v1"`) with no `folderKey`. The
989
+ // engine's v1 branch reads `releaseKey` and `folderKey`, not `folderPath`:
990
+ // `Orchestrator.ExecuteApiWorkflowAsync` so written faults with `Could not get value
991
+ // for key:FolderKey from context in input` (v1 skill, verified live). A node by NAME
992
+ // is emitted at v2 and never reaches that branch — see OrchestratorOpts.process.
993
+ if (JOB_START_TYPES.has(n.type) &&
994
+ (n.typeVersion ?? 'v1') === 'v1' &&
995
+ contextView['releaseKey'] !== undefined &&
996
+ contextView['folderKey'] === undefined)
997
+ diags.push({
998
+ level: 'warning',
999
+ code: 'JOB_FOLDER_KEY_MISSING',
1000
+ message: `"${n.id}" (${n.type}) is addressed by releaseKey (uipath:type version="v1") but carries no folderKey; ` +
1001
+ `the runtime's v1 branch reads folderKey, not folderPath, and faults with ` +
1002
+ `\`Could not get value for key:FolderKey from context in input\`. Pass \`folderKey\` (the folder's FolderKey ` +
1003
+ `GUID from \`uip or processes list --folder-path <path> --output json\`), or drop \`releaseKey\` and address the ` +
1004
+ `process by name, which the SDK emits at version="v2".`,
1005
+ where: n.id,
1006
+ });
934
1007
  for (const field of bindingRequiredFields(n.type, n.spec)) {
935
1008
  const value = contextView[field.name];
936
1009
  if (typeof value === 'string' && value.startsWith('=bindings.'))
@@ -1,7 +1,7 @@
1
1
  import { connectorOutputVars } from './bpmn-sdk.js';
2
2
  import { transportHttpMethod } from '../config.js';
3
3
  import { EQUALS_EXPR_DIALECT } from '../core/connector-checks.js';
4
- import { buildConnectorInputs } from '../core/connector-inputs.js';
4
+ import { buildConnectorInputs, nestConnectorBody } from '../core/connector-inputs.js';
5
5
  export function createConnectorTask(ctx, n) {
6
6
  const { m } = ctx;
7
7
  if (!ctx.library)
@@ -83,7 +83,10 @@ export function createConnectorTask(ctx, n) {
83
83
  if (part.value !== undefined)
84
84
  paramRows.push(paramInput(m, part.name, 'multipart', part.value));
85
85
  }
86
- const body = slots.bodyParameters ?? {};
86
+ // Nested, not the flat dotted map Flow emits: the BPMN runtime sends `body` as written
87
+ // (see `nestConnectorBody`) — a flat `fields.project.key` reached Jira as a literal
88
+ // property and drew a 400 on flow-v2-run-12.
89
+ const body = nestConnectorBody(slots.bodyParameters ?? {});
87
90
  // ── outputs — response schema + standard Error ──
88
91
  const responseSchema = rc.entry.outputSchema
89
92
  ? schemaFromFields(rc.entry.outputSchema.fields ?? [])
@@ -70,9 +70,17 @@ export type DataServiceOperation = {
70
70
  start?: number;
71
71
  /** Page size. Defaults to `100`. */
72
72
  limit?: number;
73
- /** Field to sort by. */
73
+ /**
74
+ * Field to sort by. Emitted as the body's `_sortFieldName` — the name the curated
75
+ * operation's `RequestFields` declare and the only place the connector reads it; a
76
+ * `sortBy` query parameter is ignored and the records come back in default order.
77
+ */
74
78
  sortBy?: string;
75
- /** Sort direction, when `sortBy` is set. */
79
+ /**
80
+ * Sort direction, when `sortBy` is set. Emitted as the `isAscending` query parameter.
81
+ * Set it explicitly: omitted, the query sorts ascending whatever the connector's
82
+ * default says.
83
+ */
76
84
  ascending?: boolean;
77
85
  /** How many levels of related records to expand into each result. */
78
86
  expansionLevel?: number;
@@ -43,9 +43,18 @@ export function dataServiceWire(entity, operation) {
43
43
  ...optionalRow('queryExpression', 'query', operation.where),
44
44
  scalarRow('start', 'query', operation.start ?? 0),
45
45
  scalarRow('limit', 'query', operation.limit ?? 100),
46
- ...optionalRow('sortBy', 'query', operation.sortBy),
47
46
  ...optionalRow('isAscending', 'query', operation.ascending),
48
47
  ...optionalRow('expansionLevel', 'query', operation.expansionLevel),
48
+ // The sort FIELD rides in the body as `_sortFieldName` — the name the curated
49
+ // operation's `RequestFields` declare (`uip is resources describe
50
+ // uipath-uipath-dataservice QueryEntityRecordsCurated --operation Create`), and
51
+ // the only place the connector reads it: as a query parameter it is ignored and
52
+ // the query comes back in default order. This used to write `sortBy` as a query
53
+ // row; on flow-v2-run-12 the Query node so built validated and failed
54
+ // `no Query node sorted by priority DESC` (datafabric-e2e-contract-intake-pipeline
55
+ // 1.00 → 0.45) while the v1 arm wrote `{"_sortFieldName":"priority"}` by hand.
56
+ // `isAscending` stays a query parameter, which is where the connector reads it.
57
+ ...(operation.sortBy !== undefined ? [bodyRow({ _sortFieldName: operation.sortBy })] : []),
49
58
  ],
50
59
  };
51
60
  case 'delete':