@uipath/maestro-builder-sdk 6.46.3 → 6.46.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.
@@ -491,6 +491,16 @@ export interface ActionField {
491
491
  label: string;
492
492
  }>;
493
493
  }
494
+ /** The Action App an action task opens. */
495
+ export interface ActionAppRef {
496
+ /** The app's deployment title, e.g. `SimpleApprovalApp`. */
497
+ name: string;
498
+ /**
499
+ * The app's deployment folder, e.g. `Shared/Approvals`, or `'solution_folder'`
500
+ * for an app built in this solution.
501
+ */
502
+ folder: string;
503
+ }
494
504
  export interface ActionSpecData {
495
505
  title?: string;
496
506
  priority?: 'Low' | 'Medium' | 'High' | 'Critical';
@@ -500,7 +510,11 @@ export interface ActionSpecData {
500
510
  };
501
511
  /** Action Center labels (persisted as a single `data.labels` string). */
502
512
  labels?: string;
503
- /** The Action Center action-app / catalog this task instantiates. */
513
+ /**
514
+ * An Action Center task catalog, resolved by name in the app's deployment
515
+ * folder. Not the app: a catalog only groups tasks. Omit it unless the catalog
516
+ * is known to exist there.
517
+ */
504
518
  actionCatalogName?: string;
505
519
  /** Read-only context fields the assignee sees (`data.inputs[]`). */
506
520
  inputs?: ActionField[];
@@ -544,6 +558,7 @@ export interface BuiltTask {
544
558
  /** Task key + display name (also the handle used by `selected-tasks-completed`). */
545
559
  name: string;
546
560
  kind: TaskKind;
561
+ /** The published resource the task binds: a reference task's process, agent, … or an action task's Action App. */
547
562
  ref?: TaskRef;
548
563
  /** business-rule task: the rule's decision-keyed result schema, when known. */
549
564
  businessRule?: BusinessRuleSpecData;
@@ -886,9 +901,13 @@ declare class TaskBuilder {
886
901
  * An Action Center human task. `recipient` may be an email (→ Type 2) or
887
902
  * `{ type, value }`. `inputs`/`outputs` declare the task's form fields — inputs
888
903
  * are read-only context the assignee sees, outputs are what they fill in.
889
- * `labels` and `actionCatalogName` tag the task and name its action app.
904
+ * `app` names the Action App the task opens (`{ name, folder }`); compile
905
+ * binds it through an `app` name/folderPath pair keyed `<folder>.<name>`.
906
+ * `labels` tag the task. `actionCatalogName` names a task catalog, which is
907
+ * resolved in the app's deployment folder; it is not the app, and a name that
908
+ * folder does not hold fails the task when it opens.
890
909
  *
891
- * @param spec - The human task: its `title`, `priority`, `recipient`, and the `inputs` / `outputs` its form shows and collects.
910
+ * @param spec - The human task: its `title`, `priority`, `recipient`, the Action `app`, and the `inputs` / `outputs` its form shows and collects.
892
911
  * @returns This builder, so calls chain.
893
912
  */
894
913
  action(spec?: {
@@ -899,6 +918,7 @@ declare class TaskBuilder {
899
918
  value: string;
900
919
  };
901
920
  labels?: string;
921
+ app?: ActionAppRef;
902
922
  actionCatalogName?: string;
903
923
  inputs?: ActionField[];
904
924
  outputs?: ActionField[];
@@ -332,13 +332,22 @@ class TaskBuilder {
332
332
  * An Action Center human task. `recipient` may be an email (→ Type 2) or
333
333
  * `{ type, value }`. `inputs`/`outputs` declare the task's form fields — inputs
334
334
  * are read-only context the assignee sees, outputs are what they fill in.
335
- * `labels` and `actionCatalogName` tag the task and name its action app.
335
+ * `app` names the Action App the task opens (`{ name, folder }`); compile
336
+ * binds it through an `app` name/folderPath pair keyed `<folder>.<name>`.
337
+ * `labels` tag the task. `actionCatalogName` names a task catalog, which is
338
+ * resolved in the app's deployment folder; it is not the app, and a name that
339
+ * folder does not hold fails the task when it opens.
336
340
  *
337
- * @param spec - The human task: its `title`, `priority`, `recipient`, and the `inputs` / `outputs` its form shows and collects.
341
+ * @param spec - The human task: its `title`, `priority`, `recipient`, the Action `app`, and the `inputs` / `outputs` its form shows and collects.
338
342
  * @returns This builder, so calls chain.
339
343
  */
340
344
  action(spec = {}) {
341
345
  this.setKind('action');
346
+ // The app is the task's resource, carried as its `ref` like every other
347
+ // bound resource, so the resource-ref rules in check and the binding pair
348
+ // in serialize both see it.
349
+ if (spec.app)
350
+ this._ref = { name: spec.app.name, folderPath: spec.app.folder };
342
351
  const recipient = typeof spec.recipient === 'string' ? { type: 2, value: spec.recipient } : spec.recipient;
343
352
  this._action = {
344
353
  title: spec.title,
@@ -5,6 +5,7 @@ import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js'
5
5
  import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
6
6
  import { checkEventResolution } from '../core/event-checks.js';
7
7
  import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT } from './external.js';
8
+ import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
8
9
  const TYPE_NAMES = new Set(Object.values(types));
9
10
  // audit-case-coverage: rule-slot table start
10
11
  // This runtime table must equal the committed matrix's rejected cells. The audit
@@ -257,7 +258,7 @@ export function check(built, opts = {}) {
257
258
  // positive that blocks emit (this gate runs before serialization), so it must be
258
259
  // re-derived from the serializer rather than from remembered probe results.
259
260
  for (const stage of built.stages) {
260
- checkStage(stage, diags, refStage, refPlacement);
261
+ checkStage(stage, diags, refStage, refPlacement, opts.bindings);
261
262
  stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
262
263
  }
263
264
  built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
@@ -394,7 +395,7 @@ function isNilOrEmpty(value) {
394
395
  return Object.keys(value).length === 0;
395
396
  return false;
396
397
  }
397
- function checkStage(stage, diags, refStage, refPlacement) {
398
+ function checkStage(stage, diags, refStage, refPlacement, bindings) {
398
399
  const where = `stage "${stage.label}"`;
399
400
  if (stage.exception && stage.required !== false)
400
401
  diags.push({
@@ -484,16 +485,15 @@ function checkStage(stage, diags, refStage, refPlacement) {
484
485
  where: t.name,
485
486
  });
486
487
  // The builder binds a resource task itself: it mints the name/folderPath
487
- // binding pair from the literal name and folder. A `=bindings.<id>` string
488
- // passed as either becomes part of the binding key, which then names no
489
- // resource (`=bindings.f.=bindings.n`) and dangles at deploy.
490
- if (t.ref && [t.ref.name, t.ref.folderPath].some((v) => v.trim().startsWith('=bindings.')))
491
- diags.push({
492
- level: 'error',
493
- code: 'RESOURCE_REF_IS_BINDING',
494
- message: `Task "${t.name}" passes a "=bindings.<id>" reference as its resource name or folder. Pass the resource's own name and its folder, e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' }); compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
495
- where: t.name,
496
- });
488
+ // binding pair from the literal name and folder. A `=bindings.<label>` name
489
+ // or folder is read through bindings.json first, the way a connector's
490
+ // `connection:` label is; one bindings.json cannot answer is refused with the
491
+ // same text serialize throws (`resolveResourceRef`).
492
+ if (t.ref) {
493
+ const resolved = resolveResourceRef(t.ref, bindings, `Task "${t.name}"`);
494
+ if (isResourceLabelRefusal(resolved))
495
+ diags.push({ level: 'error', code: resolved.code, message: resolved.message, where: t.name });
496
+ }
497
497
  if (t.kind === 'business-rule') {
498
498
  if (!t.ref || !t.ref.name.trim() || !t.ref.folderPath.trim())
499
499
  diags.push({
@@ -644,6 +644,7 @@ function emitTaskKind(task, refs, library) {
644
644
  ['priority', data.priority && data.priority !== 'Medium' ? tsLiteral(data.priority) : undefined],
645
645
  ['recipient', recipient],
646
646
  ['labels', data.labels !== undefined ? tsLiteral(data.labels) : undefined],
647
+ ['app', actionApp(data, refs, task)],
647
648
  ['actionCatalogName', data.actionCatalogName !== undefined ? tsLiteral(data.actionCatalogName) : undefined],
648
649
  ['inputs', actionFields(data.inputs)],
649
650
  ['outputs', actionFields(data.outputs)],
@@ -679,6 +680,21 @@ function emitTaskKind(task, refs, library) {
679
680
  });
680
681
  return `.__UNRESOLVED_TASK_KIND__(${tsLiteral(kind)}, ${tsLiteral(data)})`;
681
682
  }
683
+ /**
684
+ * The `app: { name, folder }` of an action task bound to its Action App, or
685
+ * undefined for one with no app. Dropping it lost the app binding on every
686
+ * round trip of a Studio Web plan (#1240).
687
+ */
688
+ function actionApp(data, refs, task) {
689
+ if (data.name === undefined && data.folderPath === undefined)
690
+ return undefined;
691
+ const owner = `action task "${task.displayName ?? task.id ?? '?'}"`;
692
+ const name = bindingDefault(data.name, refs, owner, 'name');
693
+ const folder = bindingFolder(data.folderPath, refs, owner);
694
+ if (typeof name !== 'string' || !name || typeof folder !== 'string')
695
+ throw new Error(`${owner} names its app by neither a binding nor a literal name and folder.`);
696
+ return opts([['name', tsLiteral(name)], ['folder', tsLiteral(folder)]]);
697
+ }
682
698
  /** A business-rule task `.businessRule()` can rebuild: v3 only. */
683
699
  function isAuthorableBusinessRule(task) {
684
700
  return task.type === 'business-rule' && task.data?.version === 'v3';
@@ -23,6 +23,7 @@
23
23
  * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
24
  * check.
25
25
  */
26
+ import { type BindingsView } from '../core/binding-messages.js';
26
27
  export declare const SOLUTION_FOLDER = "solution_folder";
27
28
  export interface ResourceIdentity {
28
29
  /** The `resourceKey` both rows of the name/folderPath binding pair carry. */
@@ -42,3 +43,47 @@ export interface ResourceIdentity {
42
43
  export declare function resourceIdentity(name: string, folder: string): ResourceIdentity;
43
44
  /** True for the key of a resource built inside the same solution. */
44
45
  export declare function isInSolutionKey(resourceKey: string): boolean;
46
+ /**
47
+ * Why a resource task's `=bindings.<label>` name or folder cannot be resolved.
48
+ *
49
+ * - `RESOURCE_REF_IS_BINDING`: there is no bindings.json (or it declares nothing),
50
+ * so the label names nothing compile can read.
51
+ * - `RESOURCE_BINDING_UNDECLARED`: bindings.json does not declare the label, or
52
+ * declares it with no value.
53
+ */
54
+ export interface ResourceLabelRefusal {
55
+ code: 'RESOURCE_REF_IS_BINDING' | 'RESOURCE_BINDING_UNDECLARED';
56
+ message: string;
57
+ }
58
+ /**
59
+ * The literal name and folder of a resource task, with any `=bindings.<label>`
60
+ * resolved through the project's bindings.json, the way a connector task's
61
+ * `connection:` label is.
62
+ *
63
+ * bindings.json { "name": "agentName", "default": "Agent" }
64
+ * { "name": "agentFolder", "default": "Shared/Golden" }
65
+ * .agent('=bindings.agentName', { folder: '=bindings.agentFolder' })
66
+ * -> { name: 'Agent', folderPath: 'Shared/Golden' }
67
+ *
68
+ * The task then compiles exactly as `.agent('Agent', { folder: 'Shared/Golden' })`
69
+ * does: compile mints the name/folderPath binding pair keyed `<folder>.<name>`.
70
+ * Passing the label straight through instead keyed the pair
71
+ * `=bindings.f.=bindings.n`, which names no resource and dangles at deploy.
72
+ *
73
+ * @param ref - The authored name and folder.
74
+ * @param bindings - The project's bindings.json, if any.
75
+ * @param where - The task, as a diagnostic names it.
76
+ * @returns The literal pair, or the refusal.
77
+ */
78
+ export declare function resolveResourceRef(ref: {
79
+ name: string;
80
+ folderPath: string;
81
+ }, bindings: BindingsView | undefined, where: string): {
82
+ name: string;
83
+ folderPath: string;
84
+ } | ResourceLabelRefusal;
85
+ /** True when {@link resolveResourceRef} refused. */
86
+ export declare function isResourceLabelRefusal(r: {
87
+ name: string;
88
+ folderPath: string;
89
+ } | ResourceLabelRefusal): r is ResourceLabelRefusal;
@@ -23,6 +23,7 @@
23
23
  * `case-validate-service.ts` exempts that prefix from its key/folder agreement
24
24
  * check.
25
25
  */
26
+ import { GUID_RE } from '../core/binding-messages.js';
26
27
  export const SOLUTION_FOLDER = 'solution_folder';
27
28
  const SOLUTION_FOLDER_KEY_PREFIX = `${SOLUTION_FOLDER}.`;
28
29
  /**
@@ -43,3 +44,72 @@ export function resourceIdentity(name, folder) {
43
44
  export function isInSolutionKey(resourceKey) {
44
45
  return resourceKey.startsWith(SOLUTION_FOLDER_KEY_PREFIX);
45
46
  }
47
+ /** How a resource task's name or folder spells a bindings.json label. */
48
+ const BINDING_LABEL_PREFIX = '=bindings.';
49
+ /** The literal value bindings.json declares for `label`, or `undefined`. */
50
+ function declaredValue(bindings, label) {
51
+ const entry = bindings?.entry?.(label);
52
+ // `default` is the value a person reads (the resource's name, the folder
53
+ // path); `resourceKey` can be the resource's GUID. A map-form bindings.json
54
+ // puts the value in both. A GUID-shaped `resourceKey` is never a name or a
55
+ // folder: taking it would key the pair `<folder>.<guid>`, which dangles at
56
+ // deploy, so the label falls through to RESOURCE_BINDING_UNDECLARED.
57
+ const fallback = typeof entry?.resourceKey === 'string' && GUID_RE.test(entry.resourceKey.trim()) ? undefined : entry?.resourceKey;
58
+ const value = entry ? (entry.default ?? fallback) : bindings?.resolve(label);
59
+ return typeof value === 'string' && value.trim() && !value.trim().startsWith(BINDING_LABEL_PREFIX)
60
+ ? value
61
+ : undefined;
62
+ }
63
+ /**
64
+ * The literal name and folder of a resource task, with any `=bindings.<label>`
65
+ * resolved through the project's bindings.json, the way a connector task's
66
+ * `connection:` label is.
67
+ *
68
+ * bindings.json { "name": "agentName", "default": "Agent" }
69
+ * { "name": "agentFolder", "default": "Shared/Golden" }
70
+ * .agent('=bindings.agentName', { folder: '=bindings.agentFolder' })
71
+ * -> { name: 'Agent', folderPath: 'Shared/Golden' }
72
+ *
73
+ * The task then compiles exactly as `.agent('Agent', { folder: 'Shared/Golden' })`
74
+ * does: compile mints the name/folderPath binding pair keyed `<folder>.<name>`.
75
+ * Passing the label straight through instead keyed the pair
76
+ * `=bindings.f.=bindings.n`, which names no resource and dangles at deploy.
77
+ *
78
+ * @param ref - The authored name and folder.
79
+ * @param bindings - The project's bindings.json, if any.
80
+ * @param where - The task, as a diagnostic names it.
81
+ * @returns The literal pair, or the refusal.
82
+ */
83
+ export function resolveResourceRef(ref, bindings, where) {
84
+ const out = { name: ref.name, folderPath: ref.folderPath };
85
+ for (const attribute of ['name', 'folderPath']) {
86
+ const authored = ref[attribute].trim();
87
+ if (!authored.startsWith(BINDING_LABEL_PREFIX))
88
+ continue;
89
+ const label = authored.slice(BINDING_LABEL_PREFIX.length);
90
+ const what = attribute === 'name' ? 'resource name' : 'folder';
91
+ const literal = `e.g. .agent('Contract Analysis', { folder: 'Shared/Legal' })`;
92
+ const declared = bindings?.declared?.() ?? [];
93
+ if (declared.length === 0)
94
+ return {
95
+ code: 'RESOURCE_REF_IS_BINDING',
96
+ message: `${where} passes "${authored}" as its ${what}, and there is no bindings.json declaring "${label}". `
97
+ + `Declare it in bindings.json ({ "name": "${label}", "default": "<the ${what}>" }), or pass the ${what} itself, ${literal}; `
98
+ + `compile writes the name/folderPath binding pair keyed '<folder>.<name>' and points the task at it.`,
99
+ };
100
+ const value = declaredValue(bindings, label);
101
+ if (value === undefined)
102
+ return {
103
+ code: 'RESOURCE_BINDING_UNDECLARED',
104
+ message: `${where} passes "${authored}" as its ${what}, but bindings.json does not declare "${label}" with a value `
105
+ + `(it declares: ${declared.join(', ')}). Use one of those labels, add "${label}" with its ${what} as "default", `
106
+ + `or pass the ${what} itself, ${literal}.`,
107
+ };
108
+ out[attribute] = value;
109
+ }
110
+ return out;
111
+ }
112
+ /** True when {@link resolveResourceRef} refused. */
113
+ export function isResourceLabelRefusal(r) {
114
+ return 'code' in r;
115
+ }
@@ -18,7 +18,7 @@ import { resolveEventWhere } from '../core/lookups.js';
18
18
  import { resolveCaseSubscription } from './subscription.js';
19
19
  import { externalDescriptorRefusal } from './external.js';
20
20
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
21
- import { resourceIdentity } from './resource-identity.js';
21
+ import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './resource-identity.js';
22
22
  import { ownEntry } from '../core/own-entry.js';
23
23
  import { resolveSchemaVersion } from '../schema-version.js';
24
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
@@ -914,7 +914,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
914
914
  // never literals: `validate` refuses a literal (CASE_MGMT_RESOURCE_TASK_
915
915
  // LITERAL_BINDING), and deploy re-targets a resource only through its
916
916
  // binding (#1240).
917
- const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
917
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids, task.name);
918
918
  const data = { name: nameRef, folderPath: folderRef };
919
919
  const elementId = `${stageId}-${taskId}`;
920
920
  if (task.inputs?.length)
@@ -937,6 +937,13 @@ function taskData(task, resolver, taskId, stageId, ids) {
937
937
  }
938
938
  if (a.labels)
939
939
  data.labels = a.labels;
940
+ // The Action App the task opens, bound like any resource: an `app`
941
+ // name/folderPath pair keyed `<deployment folder>.<app>` (#1240).
942
+ if (task.ref) {
943
+ const { nameRef, folderRef } = referenceBindings('action', task.ref, resolver, ids, task.name);
944
+ data.name = nameRef;
945
+ data.folderPath = folderRef;
946
+ }
940
947
  if (a.actionCatalogName)
941
948
  data.actionCatalogName = a.actionCatalogName;
942
949
  // HITL form fields: inputs are read-only context the assignee sees, outputs
@@ -1268,11 +1275,20 @@ function resourceBinding(resolver, ids, binding, resourceKey, propertyAttribute,
1268
1275
  });
1269
1276
  return id;
1270
1277
  }
1278
+ /** The binding `resource` an action task's Action App is bound by (no subtype). */
1279
+ const ACTION_APP_RESOURCE = 'app';
1271
1280
  /** The `=bindings.<id>` name/folderPath references of a published-resource task. */
1272
- function referenceBindings(kind, ref, resolver, ids) {
1281
+ function referenceBindings(kind, authored, resolver, ids, taskName) {
1282
+ // A `=bindings.<label>` name or folder reads through bindings.json to its
1283
+ // literal value, so the task binds exactly as the literal form does.
1284
+ const ref = resolveResourceRef(authored, resolver.bindings, `Task "${taskName}"`);
1285
+ if (isResourceLabelRefusal(ref))
1286
+ throw new Error(`[${ref.code}] ${ref.message}`);
1273
1287
  const binding = kind === 'business-rule'
1274
1288
  ? { resource: BUSINESS_RULE_RESOURCE }
1275
- : ownEntry(REFERENCE_BINDING, kind);
1289
+ : kind === 'action'
1290
+ ? { resource: ACTION_APP_RESOURCE }
1291
+ : ownEntry(REFERENCE_BINDING, kind);
1276
1292
  if (!binding)
1277
1293
  throw new Error(`task kind "${kind}" is not a published-resource task.`);
1278
1294
  // An in-solution resource (`folder: 'solution_folder'`) keeps the sentinel
@@ -1297,7 +1313,7 @@ function referenceBindings(kind, ref, resolver, ids) {
1297
1313
  * (`=<Decision>.<column>`).
1298
1314
  */
1299
1315
  function businessRuleTaskData(task, resolver, elementId, ids) {
1300
- const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
1316
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids, task.name);
1301
1317
  const ownRow = (name, source, seed, extra = {}) => {
1302
1318
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1303
1319
  return { name, type: 'jsonSchema', id: slot, var: slot, value: slot, source, target: `=${slot}`, elementId, ...extra };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.46.3",
3
+ "version": "6.46.5",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "b763fc50a8d6430ce9a6e3e3e6b79b7c8645a4b1"
96
+ "gitref": "0edb50d70d9787a64a6c66ce37b5e1eed45d2a0d"
97
97
  }