@uipath/maestro-builder-sdk 6.66.0 → 6.66.2

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.
@@ -80,7 +80,12 @@ export declare function processResourceLocation(spec: {
80
80
  export declare function processResourceBindings(spec: {
81
81
  name: string;
82
82
  folderPath: string;
83
- resourceSubType: ProcessResourceSubType;
83
+ /**
84
+ * The binding's resource kind. `'process'` (the default) for a process
85
+ * reference; `'app'` for an Action App, which carries no subtype.
86
+ */
87
+ resource?: 'process' | 'app';
88
+ resourceSubType?: ProcessResourceSubType;
84
89
  resourceKey?: string;
85
90
  }, existing: ReadonlyMap<string, any>): {
86
91
  ids: {
@@ -63,8 +63,9 @@ export function processResourceLocation(spec) {
63
63
  * process share one pair.
64
64
  */
65
65
  export function processResourceBindings(spec, existing) {
66
+ const resource = spec.resource ?? 'process';
66
67
  const resourceKey = spec.resourceKey ?? `${spec.folderPath}.${spec.name}`;
67
- const found = (attr) => [...existing.values()].find((b) => b.resource === 'process' && b.resourceKey === resourceKey && b.propertyAttribute === attr)?.id;
68
+ const found = (attr) => [...existing.values()].find((b) => b.resource === resource && b.resourceKey === resourceKey && b.propertyAttribute === attr)?.id;
68
69
  const already = { name: found('name'), folderPath: found('folderPath') };
69
70
  if (already.name && already.folderPath) {
70
71
  return { ids: { name: already.name, folderPath: already.folderPath }, entries: [] };
@@ -72,7 +73,7 @@ export function processResourceBindings(spec, existing) {
72
73
  // A readable, stable id per process. Two processes sharing a NAME across folders
73
74
  // would collide, so the id is suffixed until it is free — the queue emitter's
74
75
  // shape, which this is a port of.
75
- const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || 'Process';
76
+ const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || (resource === 'app' ? 'App' : 'Process');
76
77
  let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
77
78
  for (let n = 2; existing.has(`${base}Name`) || existing.has(`${base}FolderPath`); n++) {
78
79
  base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}${n}`;
@@ -83,21 +84,21 @@ export function processResourceBindings(spec, existing) {
83
84
  id: ids.name,
84
85
  name: 'name',
85
86
  type: 'string',
86
- resource: 'process',
87
+ resource,
87
88
  resourceKey,
88
89
  default: spec.name,
89
90
  propertyAttribute: 'name',
90
- resourceSubType: spec.resourceSubType,
91
+ ...(spec.resourceSubType !== undefined ? { resourceSubType: spec.resourceSubType } : {}),
91
92
  },
92
93
  {
93
94
  id: ids.folderPath,
94
95
  name: 'folderPath',
95
96
  type: 'string',
96
- resource: 'process',
97
+ resource,
97
98
  resourceKey,
98
99
  default: spec.folderPath,
99
100
  propertyAttribute: 'folderPath',
100
- resourceSubType: spec.resourceSubType,
101
+ ...(spec.resourceSubType !== undefined ? { resourceSubType: spec.resourceSubType } : {}),
101
102
  },
102
103
  ] };
103
104
  }
@@ -306,6 +306,7 @@ export declare const REFUSALS: {
306
306
  readonly HITL_APP_INCOMPLETE: "lint";
307
307
  readonly HITL_APP_INPUT_REQUIRED: "lint";
308
308
  readonly HITL_APP_INPUT_UNKNOWN: "lint";
309
+ readonly HITL_APP_LOCAL_HAS_FOLDER: "lint";
309
310
  readonly HITL_APP_MISSING: "lint";
310
311
  readonly HITL_APP_NO_SCHEMA: "lint";
311
312
  readonly HITL_APP_OUTCOME_UNKNOWN: "lint";
@@ -347,6 +347,7 @@ export const REFUSALS = {
347
347
  HITL_APP_INCOMPLETE: 'lint',
348
348
  HITL_APP_INPUT_REQUIRED: 'lint',
349
349
  HITL_APP_INPUT_UNKNOWN: 'lint',
350
+ HITL_APP_LOCAL_HAS_FOLDER: 'lint',
350
351
  HITL_APP_MISSING: 'lint',
351
352
  HITL_APP_NO_SCHEMA: 'lint',
352
353
  HITL_APP_OUTCOME_UNKNOWN: 'lint',
package/dist/merge.js CHANGED
@@ -454,6 +454,7 @@ function mergeFlowFindings(original, edited, options) {
454
454
  // not carried (#1133). ──
455
455
  result.bindings = mergeBindings(original.bindings, edited.bindings);
456
456
  result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal, fieldwise);
457
+ result.bindings = withoutSupersededAppRows(result.bindings, originalNodes, allNodes);
457
458
  // metadata / solutionId / projectId / layout / runtime were carried by the
458
459
  // initial clone of `original` and are intentionally left untouched, apart
459
460
  // from the `layout.nodes` entries of removed nodes (dropped above) and added
@@ -667,6 +668,36 @@ function mergeBindings(orig, edited) {
667
668
  }
668
669
  return out;
669
670
  }
671
+ /**
672
+ * The `app` rows a human task's app resolves through: keyed by the app's `key`
673
+ * once it has a folder, by its `name` while it has none — flow-v1's rule
674
+ * (`hitl-serialization.ts`, #3910), and `appResourceBindings`'.
675
+ */
676
+ function appRowKey(node) {
677
+ const app = node.inputs?.app;
678
+ if (!app || typeof app !== 'object')
679
+ return undefined;
680
+ const k = app.folderPath ? app.key : app.name;
681
+ return typeof k === 'string' && k !== '' ? k : undefined;
682
+ }
683
+ /**
684
+ * Drop the original's `app` rows that a human task's app resolved through and
685
+ * that no merged node resolves through any more. That happens when the edit
686
+ * moves an app between the two keys — the canvas's `'solution_folder'` folder
687
+ * (keyed by `key`) re-emitted as an in-solution app (folder `''`, keyed by
688
+ * `name`): the edit brings the new pair, and the union would keep the old one
689
+ * beside it, orphaned (builder-sdk#1486 review). Rows no original human task
690
+ * used (an agent escalation's, say) are not touched.
691
+ */
692
+ function withoutSupersededAppRows(rows, originalNodes, mergedNodes) {
693
+ if (!rows)
694
+ return rows;
695
+ const keysOf = (nodes) => new Set(nodes.map(appRowKey).filter((k) => k !== undefined));
696
+ const before = keysOf(originalNodes);
697
+ const after = keysOf(mergedNodes);
698
+ return rows.filter((b) => !(b.resource === 'app' && typeof b.resourceKey === 'string'
699
+ && before.has(b.resourceKey) && !after.has(b.resourceKey)));
700
+ }
670
701
  /**
671
702
  * Drop each added row that the no-edit recompile minted only for nodes the merge
672
703
  * kept from the original (#1133). A row qualifies when the original lacks it, the
@@ -25,6 +25,7 @@ export const HITL_DIAGNOSTICS = {
25
25
  HITL_APP_INPUT_UNKNOWN: { severity: 'error', summary: 'an app input the action schema does not declare' },
26
26
  HITL_APP_INPUT_REQUIRED: { severity: 'error', summary: 'a required app input left unbound' },
27
27
  HITL_APP_OUTCOME_UNKNOWN: { severity: 'error', summary: 'an outcome the app has no button for' },
28
+ HITL_APP_LOCAL_HAS_FOLDER: { severity: 'error', summary: 'an in-solution app with a folderPath' },
28
29
  HITL_NO_FIELDS: { severity: 'error', summary: 'no fields array' },
29
30
  HITL_FIELD_NO_ID: { severity: 'error', summary: 'a form field with no id' },
30
31
  HITL_DUP_FIELD: { severity: 'error', summary: 'a form field id declared twice' },
@@ -185,11 +186,23 @@ export function checkHitl(inputs, ctx) {
185
186
  + `App — but names no app, so nothing tells the platform which form to show.`, `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
186
187
  return;
187
188
  }
188
- for (const k of ['name', 'key', 'folderPath']) {
189
+ // An in-solution app has no folder to author. A published one does: with
190
+ // none it is serialized as an inline app, and on a serializer before
191
+ // flow-v1 #4076 the task loses its folderPath context and fails at run
192
+ // time (170004, "Invalid input for the HITL task").
193
+ const inSolution = app.location === 'in-solution';
194
+ for (const k of inSolution ? ['name', 'key'] : ['name', 'key', 'folderPath']) {
189
195
  if (typeof app[k] !== 'string' || !app[k].trim()) {
190
- at('HITL_APP_INCOMPLETE', `Human task "${step}" names an Action App with no ${k}. An app is resolved by its key plus `
191
- + `the folder it is deployed in, with the name shown to the reviewer — all three are needed `
192
- + `for the platform to find it.`, `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
196
+ at('HITL_APP_INCOMPLETE', inSolution
197
+ ? `Human task "${step}" names an in-solution Action App with no ${k}. An in-solution app is `
198
+ + `resolved by its key (\`resource.key\` in resources/solution_folder/app/codedAction/<App>.json), `
199
+ + `with the name shown to the reviewer — both are needed for the platform to find it.`
200
+ : `Human task "${step}" names an Action App with no ${k}. An app is resolved by its key plus `
201
+ + `the folder it is deployed in, with the name shown to the reviewer — all three are needed `
202
+ + `for the platform to find it. If the app is in the same solution as the flow, set `
203
+ + `\`location: 'in-solution'\` instead of a folder.`, inSolution
204
+ ? `app: { name: 'InvoiceReview', key: '<resource.key>', location: 'in-solution' }`
205
+ : `app: { name: 'Expense Review', key: '<guid>', folderPath: 'Shared' }`);
193
206
  return;
194
207
  }
195
208
  }
@@ -235,6 +248,12 @@ export function checkHitl(inputs, ctx) {
235
248
  }
236
249
  }
237
250
  }
251
+ if (inSolution && app.folderPath !== undefined && app.folderPath !== '') {
252
+ at('HITL_APP_LOCAL_HAS_FOLDER', `Human task "${step}" names an in-solution Action App with folderPath ${JSON.stringify(app.folderPath)}. `
253
+ + `An in-solution app has no folder — it is emitted with the empty folder the platform's app picker `
254
+ + `writes for an app inline to the solution — so the value would be dropped. Remove it, or drop `
255
+ + `\`location: 'in-solution'\` to call an app published to that folder.`, `Delete \`folderPath\`.`);
256
+ }
238
257
  // Outcomes still matter for an app task: the flow's own routing and its
239
258
  // `Action`/`status` outputs are derived from them, so fall through to the
240
259
  // outcome rules below rather than returning here.
@@ -22,6 +22,7 @@ import { wireExprBody } from '../../../core/wire-expr.js';
22
22
  import { appAnswerSchemas, appInputSchema, liftActionSchema, normalizeActionSchema } from './action-schema.js';
23
23
  import { checkHitl, checkHitlRead } from './check.js';
24
24
  import { CompileRefusal } from '../../../core/compile-refusal.js';
25
+ import { processResourceBindings } from '../../../core/process-resource.js';
25
26
  const BASE = 'uipath.human-in-the-loop';
26
27
  const QUICK_FORM = 'uipath.human-in-the-loop.quick-form';
27
28
  const ACTION_APP = 'uipath.human-in-the-loop.coded-action-app';
@@ -131,6 +132,14 @@ function hitlInputs(inputs, nodeType, ctx) {
131
132
  for (const [name, value] of Object.entries(app?.inputs ?? {})) {
132
133
  appInputBindings[name] = appBindingExpr(value, ctx);
133
134
  }
135
+ const emitsApp = (isApp || (nodeType === DOC_VALIDATION && inputs?.document?.render === 'custom')) && app !== undefined;
136
+ // An in-solution app has no folder: the platform's own picker writes `''`,
137
+ // and its serializer keeps that `''` in the task context (flow-v1 #4076).
138
+ const appFolder = app?.location === 'in-solution' ? '' : (app?.folderPath ?? '');
139
+ if (emitsApp) {
140
+ for (const b of appResourceBindings({ name: app.name, key: app.key, folderPath: appFolder }, ctx.bindings))
141
+ ctx.bind(b);
142
+ }
134
143
  // The author's recipient when given; the definition's own default otherwise
135
144
  // (unchanged — that default is what 203 of 233 deployed v1 tasks carry).
136
145
  // `1.0`: every human-task type's first bundled definition, which is the one
@@ -189,13 +198,13 @@ function hitlInputs(inputs, nodeType, ctx) {
189
198
  ...(inputs?.labels !== undefined ? { labels: inputs.labels } : {}),
190
199
  recipient,
191
200
  ...docvalInputs,
192
- ...((isApp || (nodeType === DOC_VALIDATION && doc?.render === 'custom')) && app
201
+ ...(emitsApp
193
202
  ? {
194
203
  app: {
195
204
  displayName: app.name,
196
205
  name: app.name,
197
206
  key: app.key,
198
- folderPath: app.folderPath,
207
+ folderPath: appFolder,
199
208
  ...(app.appSystemName !== undefined ? { appSystemName: app.appSystemName } : {}),
200
209
  // What types the task's arguments (see action-schema.ts): lowered
201
210
  // from the action schema, or the decompiled node's own, verbatim.
@@ -230,6 +239,34 @@ function hitlInputs(inputs, nodeType, ctx) {
230
239
  },
231
240
  };
232
241
  }
242
+ /**
243
+ * The canvas's legacy placeholder folder for an app inline to the solution. The
244
+ * platform's own app picker clears it to `''` (flow-v1 `HITLAppPicker.tsx`
245
+ * `INLINE_APP_FOLDER_TOKEN`, #4076): an inline app has no deploy-time
246
+ * overwrite, so the token would reach the engine literally and be refused
247
+ * (170000). Read back as `location: 'in-solution'`, never emitted.
248
+ */
249
+ const INLINE_APP_FOLDER_TOKEN = 'solution_folder';
250
+ /**
251
+ * The `app` binding pair for one Action App, through the shared
252
+ * {@link processResourceBindings}: one pair per app, keyed the way flow-v1's app
253
+ * picker creates them — by the app's `key` once it has a folder, by its `name`
254
+ * while it has none (an in-solution app) (`hitl-serialization.ts`,
255
+ * `HITLAppPicker.tsx`, flow-v1 #3910). The serializer resolves the task's
256
+ * `<bindings.name>` / `<bindings.folderPath>` context against them; without a
257
+ * row the placeholder text reaches the engine, and packaging leaves the app out
258
+ * of `bindings_v2.json`. Serializers before #3910 (flow-converter 0.69) key the
259
+ * app by name only and fall back to the FIRST row named `name`/`folderPath` of
260
+ * any resource.
261
+ */
262
+ function appResourceBindings(spec, existing) {
263
+ return processResourceBindings({
264
+ name: spec.name,
265
+ folderPath: spec.folderPath,
266
+ resource: 'app',
267
+ resourceKey: spec.folderPath ? spec.key : spec.name,
268
+ }, existing).entries;
269
+ }
233
270
  /**
234
271
  * One `appInputBindings` value: the expression sent to a deployed app's input.
235
272
  *
@@ -416,7 +453,10 @@ function liftHitl(node, typeName, ctx) {
416
453
  // `displayName` is serialize's echo of `name`; only `name` is authored.
417
454
  name: String(app.name ?? ''),
418
455
  key: String(app.key ?? ''),
419
- ...(typeof app.folderPath === 'string' && app.folderPath !== '' ? { folderPath: app.folderPath } : {}),
456
+ // No folder (or the canvas's legacy inline placeholder) is how the platform
457
+ // writes an app inline to the solution.
458
+ ...(typeof app.folderPath === 'string' && app.folderPath !== '' && app.folderPath !== INLINE_APP_FOLDER_TOKEN
459
+ ? { folderPath: app.folderPath } : { location: 'in-solution' }),
420
460
  ...(app.appSystemName === null ? { appSystemName: null }
421
461
  : typeof app.appSystemName === 'string' ? { appSystemName: app.appSystemName } : {}),
422
462
  ...(appInputs !== undefined ? { inputs: appInputs } : {}),
@@ -517,6 +557,7 @@ function printHitl({ inputs }, ctx) {
517
557
  const app = h.app === undefined ? undefined : obj([
518
558
  ['name', ctx.string(h.app.name)],
519
559
  ['key', ctx.string(h.app.key)],
560
+ ['location', h.app.location !== undefined ? ctx.string(h.app.location) : undefined],
520
561
  ['folderPath', h.app.folderPath !== undefined ? ctx.string(h.app.folderPath) : undefined],
521
562
  ['appSystemName', h.app.appSystemName === null ? 'null'
522
563
  : h.app.appSystemName !== undefined ? ctx.string(h.app.appSystemName) : undefined],
@@ -6,7 +6,7 @@
6
6
  * `core/actions.ts`: an agent's quick-form escalation shares them.
7
7
  */
8
8
  import type { Expr } from '../../../core/expr.js';
9
- import type { FormField } from '../../../core/actions.js';
9
+ import type { FormField, ProcessLocation } from '../../../core/actions.js';
10
10
  /**
11
11
  * Where the task is delivered. The four ids are the platform's own — including
12
12
  * the lowercase `'teams'`, which is how every deployed recipient spells
@@ -164,15 +164,44 @@ export type HitlVariant = 'quick-form' | 'action-app' | 'document-validation';
164
164
  * },
165
165
  * outcomes: ['Approve', 'Reject'],
166
166
  * })
167
+ *
168
+ * // A coded action app in the same solution as the flow:
169
+ * hitl({
170
+ * variant: 'action-app',
171
+ * app: { name: 'InvoiceReview', key: '<resource.key>', location: 'in-solution' },
172
+ * outcomes: ['Approve', 'Reject'],
173
+ * })
167
174
  * ```
168
175
  */
169
176
  export interface AppRef {
170
177
  /** The app's display title, e.g. `'Expense Review'`. */
171
178
  name: string;
172
- /** The app's key (a GUID) — how the platform resolves which app this is. */
179
+ /**
180
+ * The app's key (a GUID) — how the platform resolves which app this is.
181
+ *
182
+ * For an in-solution app it is `resource.key` from
183
+ * `resources/solution_folder/app/codedAction/<App>.json`.
184
+ */
173
185
  key: string;
174
- /** Fully-qualified folder the app is deployed in, e.g. `'Shared'`. */
175
- folderPath: string;
186
+ /**
187
+ * Where the app lives. `'published'` (the default) is an app deployed to
188
+ * `folderPath`. `'in-solution'` is a coded action app in the same solution as
189
+ * the flow: it takes no `folderPath` — it is emitted with the empty folder
190
+ * and name-keyed bindings the platform's own app picker writes for an app
191
+ * inline to the solution. Needs a `uip` CLI with flow-converter 0.100.3 or
192
+ * later (or a process engine with MST-15436); older ones fail at run time
193
+ * with 170004.
194
+ *
195
+ * @enforcedBy HITL_APP_LOCAL_HAS_FOLDER An in-solution app takes no folderPath.
196
+ */
197
+ location?: ProcessLocation;
198
+ /**
199
+ * Fully-qualified folder the app is deployed in, e.g. `'Shared'`. Required
200
+ * for a published app; omit it for an in-solution one.
201
+ *
202
+ * @enforcedBy HITL_APP_INCOMPLETE A published app needs its folder.
203
+ */
204
+ folderPath?: string;
176
205
  /**
177
206
  * The app's stable system name. `null` until the app's first deployment
178
207
  * assigns one, which is why it is optional and why `null` is a legal value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.66.0",
3
+ "version": "6.66.2",
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",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "5da76743a22cb278ab85acb47169367acbfe65d0"
103
+ "gitref": "f698c1379d7c348671e6bf0df5b211be77e8f1c6"
104
104
  }