@uipath/maestro-builder-sdk 6.40.1 → 6.41.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.
@@ -0,0 +1,444 @@
1
+ import { AGENTIC_PROCESS_FORMS } from '../../../core/agentic-process-forms.js';
2
+ import { GUID_RE } from '../../../core/binding-messages.js';
3
+ import { nearestName } from '../../../core/edit-distance.js';
4
+ import { ownEntry } from '../../../core/own-entry.js';
5
+ import { renamedFieldMessage } from '../../../core/process-resource.js';
6
+ export const PROCESS_DIAGNOSTICS = {
7
+ AGENTIC_EMPTY_RETURNS: { severity: 'warning', summary: 'an agentic process step with returns: {}' },
8
+ AGENTIC_KEY_NOT_KEY: { severity: 'error', summary: 'an agentic process step whose key is not a GUID' },
9
+ AGENTIC_LOCAL_FORM_UNVERIFIED: { severity: 'error', summary: 'an agentic process step in-solution, with a form other than flow' },
10
+ AGENTIC_LOCAL_HAS_FOLDER: { severity: 'error', summary: 'an agentic process step in-solution, with a folderPath' },
11
+ AGENTIC_LOCAL_NO_PROJECT_ID: { severity: 'error', summary: 'an agentic process step in-solution, with no projectId' },
12
+ AGENTIC_LOCAL_PROJECT_ID_NOT_GUID: { severity: 'error', summary: 'an agentic process step in-solution, with a projectId that is not a GUID' },
13
+ AGENTIC_NAME_IS_PATH: { severity: 'error', summary: 'an agentic process step whose name looks like a folder path' },
14
+ AGENTIC_NO_FOLDER: { severity: 'error', summary: 'an agentic process step with no folderPath' },
15
+ AGENTIC_NO_KEY: { severity: 'error', summary: 'an agentic process step with no key' },
16
+ AGENTIC_NO_NAME: { severity: 'error', summary: 'an agentic process step with no name' },
17
+ AGENTIC_READ_NOT_OUTPUT: { severity: 'error', summary: 'an agentic process step read outside output' },
18
+ AGENTIC_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'an agentic process step read of a field returns does not declare' },
19
+ AGENTIC_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'an agentic process step field read with no returns' },
20
+ AGENTIC_RESERVED_INPUT: { severity: 'error', summary: 'an agentic process step with the reserved fireAndForget input' },
21
+ APIWF_EMPTY_RETURNS: { severity: 'warning', summary: 'an API workflow step with returns: {}' },
22
+ APIWF_KEY_NOT_KEY: { severity: 'error', summary: 'an API workflow step whose key is not a GUID' },
23
+ APIWF_NAME_IS_PATH: { severity: 'error', summary: 'an API workflow step whose name looks like a folder path' },
24
+ APIWF_NO_FOLDER: { severity: 'error', summary: 'an API workflow step with no folderPath' },
25
+ APIWF_NO_KEY: { severity: 'error', summary: 'an API workflow step with no key' },
26
+ APIWF_NO_NAME: { severity: 'error', summary: 'an API workflow step with no name' },
27
+ APIWF_READ_NOT_OUTPUT: { severity: 'error', summary: 'an API workflow step read outside output' },
28
+ APIWF_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'an API workflow step read of a field returns does not declare' },
29
+ APIWF_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'an API workflow step field read with no returns' },
30
+ FUNCTION_EMPTY_RETURNS: { severity: 'warning', summary: 'a function step with returns: {}' },
31
+ FUNCTION_KEY_NOT_KEY: { severity: 'error', summary: 'a function step whose key is not a GUID' },
32
+ FUNCTION_NAME_IS_PATH: { severity: 'error', summary: 'a function step whose name looks like a folder path' },
33
+ FUNCTION_NO_FOLDER: { severity: 'error', summary: 'a function step with no folderPath' },
34
+ FUNCTION_NO_KEY: { severity: 'error', summary: 'a function step with no key' },
35
+ FUNCTION_NO_NAME: { severity: 'error', summary: 'a function step with no name' },
36
+ FUNCTION_READ_NOT_OUTPUT: { severity: 'error', summary: 'a function step read outside output' },
37
+ FUNCTION_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'a function step read of a field returns does not declare' },
38
+ FUNCTION_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'a function step field read with no returns' },
39
+ RPA_KEY_NOT_KEY: { severity: 'error', summary: 'an RPA step whose key is not a GUID' },
40
+ RPA_LOCAL_HAS_FOLDER: { severity: 'error', summary: 'an RPA step in-solution, with a folderPath' },
41
+ RPA_LOCAL_NO_PROJECT_ID: { severity: 'error', summary: 'an RPA step in-solution, with no projectId' },
42
+ RPA_LOCAL_PROJECT_ID_NOT_GUID: { severity: 'error', summary: 'an RPA step in-solution, with a projectId that is not a GUID' },
43
+ RPA_NAME_IS_PATH: { severity: 'error', summary: 'an RPA step whose name looks like a folder path' },
44
+ RPA_NO_FOLDER: { severity: 'error', summary: 'an RPA step with no folderPath' },
45
+ RPA_NO_KEY: { severity: 'error', summary: 'an RPA step with no key' },
46
+ RPA_NO_NAME: { severity: 'error', summary: 'an RPA step with no name' },
47
+ RPA_READ_NOT_OUTPUT: { severity: 'error', summary: 'an RPA step read outside output' },
48
+ RPA_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'an RPA step read of a field returns does not declare' },
49
+ RPA_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'an RPA step field read with no returns' },
50
+ };
51
+ /**
52
+ * Check an `rpaWorkflow(...)` call.
53
+ *
54
+ * WHAT THESE RULES CAN AND CANNOT SEE. Everything about a deployed process that
55
+ * matters — which arguments it takes, what it returns, whether it exists —
56
+ * lives on the tenant. So these rules cover exactly the mistakes that are
57
+ * decidable from the call itself, and the doc says plainly that the rest shows
58
+ * up as a faulted job on a live run. What is decidable:
59
+ *
60
+ * - a missing/blank `key`, `name` or `folderPath`. Any of the three missing
61
+ * is fatal in a way no later rung reports usefully: without `key` the node
62
+ * has no type suffix, and without the other two nothing can resolve the node's
63
+ * `resourceBindings.name` — which reads as an engine error rather than an
64
+ * authoring one.
65
+ * - a `key` that is not a GUID. The commonest form of this is passing the
66
+ * process's NAME where its release key belongs, which produces a node type
67
+ * the platform cannot resolve to anything.
68
+ * - a `name` that looks like a path (contains "/"). The two are swappable, and
69
+ * the resulting `resourceKey` is wrong in a way that only surfaces as a 400
70
+ * from Orchestrator ("Folder does not exist"). That is v1's own 2026-06-26
71
+ * failure mode on this task. A single-segment `folderPath` is NOT decidable:
72
+ * root folders and personal workspaces are one segment, so it is not flagged.
73
+ */
74
+ export function checkRpaWorkflow(inputs, ctx) {
75
+ const step = ctx.step;
76
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
77
+ const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
78
+ if (blank(inputs?.key) && !blank(inputs?.process)) {
79
+ at('RPA_NO_KEY', renamedFieldMessage('RPA', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
80
+ }
81
+ else if (blank(inputs?.key)) {
82
+ at('RPA_NO_KEY', `RPA step "${step}" has no \`key\`. That is the published process's release key (a GUID), and `
83
+ + `it becomes part of the node's type — without it there is no process to start. Find it with `
84
+ + `\`uip or processes list --all-folders --process-type Process\` (the row's \`Key\`).`, `key: '486edc26-0658-4ac1-92c9-1ef953927151'`);
85
+ }
86
+ else if (!GUID_RE.test(String(inputs.key).trim())) {
87
+ at('RPA_KEY_NOT_KEY', `RPA step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a release key. `
88
+ + `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
89
+ + `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
90
+ }
91
+ if (blank(inputs?.name)) {
92
+ at('RPA_NO_NAME', `RPA step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by the key in `
93
+ + `the node type, so without it the process cannot be resolved and the run fails before dispatch.`, `name: 'RPA Workflow'`);
94
+ }
95
+ const inSolution = inputs?.location === 'in-solution';
96
+ if (!inSolution && blank(inputs?.folderPath)) {
97
+ at('RPA_NO_FOLDER', `RPA step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so the `
98
+ + `Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-maestro-flow/ProjectEuler RPA'`);
99
+ }
100
+ if (inSolution && blank(inputs?.projectId)) {
101
+ at('RPA_LOCAL_NO_PROJECT_ID', `In-solution RPA step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
102
+ + `\`resources/solution_folder/process/process/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
103
+ }
104
+ else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
105
+ at('RPA_LOCAL_PROJECT_ID_NOT_GUID', `In-solution RPA step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
106
+ + `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
107
+ }
108
+ if (inSolution && !blank(inputs?.folderPath)) {
109
+ at('RPA_LOCAL_HAS_FOLDER', `In-solution RPA step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
110
+ + `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
111
+ + `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
112
+ + `call the published copy.`);
113
+ }
114
+ // The two are swappable by shape, and swapping them is invisible until
115
+ // Orchestrator answers "Folder does not exist or the user does not have access".
116
+ const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
117
+ if (name.includes('/')) {
118
+ at('RPA_NAME_IS_PATH', `RPA step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
119
+ + `the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. Together they `
120
+ + `form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
121
+ }
122
+ }
123
+ /**
124
+ * Check an `apiWorkflow(...)` call.
125
+ *
126
+ * Same three-identifier shape as `rpaWorkflow`, so the same three mistakes are
127
+ * decidable from the call itself (a blank field, a `key` that is not a key, and
128
+ * the swappable `name`/`folderPath` pair). Two things differ, and both come from
129
+ * the corpus rather than from the surface:
130
+ *
131
+ * - the node type's GUID is the workflow's PROCESS key — the `Key` field of
132
+ * `uip or processes list`, whose `ProcessKey` for an API workflow reads
133
+ * `<name>.api.<name>` — so the command to name in the diagnostic is that one;
134
+ * - `returns` matters MORE here than for rpa, because this task's whole answer is
135
+ * a field of the job's output. A read with no `returns` has no schema to check (and
136
+ * a read of an undeclared field is `undefined` everywhere), so both are said at
137
+ * rung 1 instead — see `checkApiWorkflowRead`.
138
+ *
139
+ * What no rule here can see stays what it is: whether the workflow exists, whether
140
+ * it takes these argument names, whether it returns these fields. Only the tenant
141
+ * knows, and a wrong one is a faulted job on a live run — which is what the `job`
142
+ * rung is for.
143
+ */
144
+ export function checkApiWorkflow(inputs, ctx) {
145
+ const step = ctx.step;
146
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
147
+ const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
148
+ if (blank(inputs?.key) && !blank(inputs?.workflow)) {
149
+ at('APIWF_NO_KEY', renamedFieldMessage('API workflow', step, 'workflow'), `key: ${JSON.stringify(inputs.workflow)}`);
150
+ }
151
+ else if (blank(inputs?.key)) {
152
+ at('APIWF_NO_KEY', `API workflow step "${step}" has no \`key\`. That is the published workflow's key (a GUID), and `
153
+ + `it becomes part of the node's type — without it there is no workflow to invoke. Find it with `
154
+ + `\`uip or processes list --all-folders\` (an API workflow's ProcessKey reads "<name>.api.<name>").`, `key: 'ce857908-ee1d-4392-b552-38bcea0be29c'`);
155
+ }
156
+ else if (!GUID_RE.test(String(inputs.key).trim())) {
157
+ at('APIWF_KEY_NOT_KEY', `API workflow step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
158
+ + `\`key\` is the GUID the tenant gives the published workflow (it becomes the node type's suffix); `
159
+ + `the workflow's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
160
+ }
161
+ if (blank(inputs?.name)) {
162
+ at('APIWF_NO_NAME', `API workflow step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by the `
163
+ + `key in the node type, so without it the workflow cannot be resolved and the run fails before dispatch.`, `name: 'NameToAgeFixed'`);
164
+ }
165
+ if (blank(inputs?.folderPath)) {
166
+ at('APIWF_NO_FOLDER', `API workflow step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so the `
167
+ + `Orchestrator folder holding the workflow is required.`, `folderPath: 'Shared'`);
168
+ }
169
+ const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
170
+ if (name.includes('/')) {
171
+ at('APIWF_NAME_IS_PATH', `API workflow step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
172
+ + `\`name\` is the workflow's own name in Orchestrator; the path it lives in goes in \`folderPath\`. `
173
+ + `Together they form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
174
+ }
175
+ // An empty `returns: {}` is a statement that the workflow returns nothing, which
176
+ // is legal — but it is almost always a half-finished edit, and the difference
177
+ // between it and a missing `returns` is invisible in the emitted file.
178
+ if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
179
+ ctx.report('APIWF_EMPTY_RETURNS', `API workflow step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
180
+ + `schema at all and so reads exactly like omitting it. If the workflow returns nothing, drop the `
181
+ + `key; if it returns something, name the fields.`);
182
+ }
183
+ }
184
+ /**
185
+ * A published FUNCTION step (Phase 5B).
186
+ *
187
+ * The same four identity mistakes as its published siblings, in this family's
188
+ * own vocabulary — and one of its own: a function is usually deployed into a
189
+ * folder of its own name, so `folderPath: 'Shared'` when the tenant says
190
+ * `Shared/<name>` points the binding's resourceKey at nothing.
191
+ */
192
+ export function checkPublishedFunction(inputs, ctx) {
193
+ const step = ctx.step;
194
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
195
+ const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
196
+ if (blank(inputs?.key)) {
197
+ at('FUNCTION_NO_KEY', `Function step "${step}" has no \`key\`. That is the published function's key (a GUID), and it becomes `
198
+ + `part of the node's type — without it there is no function to invoke. Find it with `
199
+ + `\`uip maestro flow registry search "uipath.core.function"\`.`, `key: '7059bdb5-fdd7-4e13-9d7b-1748aaeb129d'`);
200
+ }
201
+ else if (!GUID_RE.test(String(inputs.key).trim())) {
202
+ at('FUNCTION_KEY_NOT_KEY', `Function step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. \`key\` is the `
203
+ + `GUID the tenant gives the published function (it becomes the node type's suffix); the function's NAME `
204
+ + `goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
205
+ }
206
+ if (blank(inputs?.name)) {
207
+ at('FUNCTION_NO_NAME', `Function step "${step}" has no \`name\`. The runtime resolves the function by name + folder, not by the `
208
+ + `key in the node type, so without it the dispatch fails before it starts.`, `name: 'acme-echo'`);
209
+ }
210
+ if (blank(inputs?.folderPath)) {
211
+ at('FUNCTION_NO_FOLDER', `Function step "${step}" has no \`folderPath\`. The runtime resolves the function by name + folder — and a `
212
+ + `function is often deployed into a folder of its OWN name, so read the folder from the tenant rather `
213
+ + `than assuming 'Shared'.`, `folderPath: 'Shared/acme-echo'`);
214
+ }
215
+ const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
216
+ if (name.includes('/')) {
217
+ at('FUNCTION_NAME_IS_PATH', `Function step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
218
+ + `the function's own name; the path it lives in goes in \`folderPath\`. Together they form the `
219
+ + `resourceKey "<folderPath>.<name>", so swapping them points the dispatch at nothing.`);
220
+ }
221
+ if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
222
+ ctx.report('FUNCTION_EMPTY_RETURNS', `Function step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
223
+ + `schema and so reads exactly like omitting it. If the function returns nothing, drop the key.`);
224
+ }
225
+ }
226
+ /**
227
+ * Check an `agenticProcess(...)` call.
228
+ *
229
+ * The same three-identifier shape as `rpaWorkflow`/`apiWorkflow`, so the same
230
+ * mistakes are decidable from the call itself. Two things are this family's own:
231
+ *
232
+ * - the node type's GUID is the process's own key — the `Key` field of
233
+ * `uip or processes list`, whose `ProcessKey` for an agentic process reads
234
+ * `<name>.agentic.<name>` — so that is the command the diagnostic names. And it
235
+ * names the pagination, because `--all-folders` returns 50 rows with
236
+ * `Pagination.HasMore: true` and no warning: the tenant this was written against
237
+ * has 98 processes, and the agentic ones are not all in the first page.
238
+ * - `fireAndForget` is a RESERVED input. It is the platform's design-time switch
239
+ * (flow-v1's `FIRE_AND_FORGET_INPUT`), and its serializer destructures it OUT of
240
+ * the node's inputs before emitting, so a process argument that happens to be
241
+ * called `fireAndForget` never reaches the process — silently, with a green run
242
+ * locally, because a runner that passes the whole envelope through sees nothing
243
+ * wrong.
244
+ *
245
+ * What no rule here can see stays what it is: whether the process exists, whether
246
+ * it takes these argument names, whether it returns these fields. Only the tenant
247
+ * knows, and a wrong one is a faulted job on a live run — which is what the `job`
248
+ * rung is for.
249
+ */
250
+ export function checkAgenticProcess(inputs, ctx) {
251
+ const step = ctx.step;
252
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
253
+ const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
254
+ if (blank(inputs?.key) && !blank(inputs?.process)) {
255
+ at('AGENTIC_NO_KEY', renamedFieldMessage('Agentic process', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
256
+ }
257
+ else if (blank(inputs?.key)) {
258
+ at('AGENTIC_NO_KEY', `Agentic process step "${step}" has no \`key\`. That is the published process's key (a GUID), `
259
+ + `and it becomes part of the node's type — without it there is no process to start. Find it with `
260
+ + `\`uip or processes list --all-folders --limit 100\` (an agentic process's ProcessKey reads `
261
+ + `"<name>.agentic.<name>"; the listing paginates at 50, so pass a limit or scope it to a folder).`, `key: '4fc450ab-89be-4462-8fc8-21ac4c1d6fb9'`);
262
+ }
263
+ else if (!GUID_RE.test(String(inputs.key).trim())) {
264
+ at('AGENTIC_KEY_NOT_KEY', `Agentic process step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
265
+ + `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
266
+ + `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
267
+ }
268
+ if (blank(inputs?.name)) {
269
+ at('AGENTIC_NO_NAME', `Agentic process step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by `
270
+ + `the key in the node type, so without it the process cannot be resolved and the run fails before `
271
+ + `dispatch.`, `name: 'ProcurementProcess'`);
272
+ }
273
+ const inSolution = inputs?.location === 'in-solution';
274
+ if (!inSolution && blank(inputs?.folderPath)) {
275
+ // `registry list --local` shows an in-solution Flow sibling with folderPath: "".
276
+ // An explicit `form` other than 'flow' keeps the published hint: switching it would change the node type.
277
+ const meantInSolution = inputs?.form === 'flow'
278
+ || (typeof inputs?.folderPath === 'string' && inputs?.form === undefined);
279
+ if (meantInSolution) {
280
+ const formPart = inputs?.form === 'flow' ? '' : `, form: 'flow'`;
281
+ at('AGENTIC_NO_FOLDER', `Agentic process step "${step}" has an empty \`folderPath\`. If the process is an unpublished sibling `
282
+ + `Flow in this solution, call it in-solution: set \`location: 'in-solution'\`${formPart} and \`projectId\` `
283
+ + `(the sibling's \`resource.projectKey\` in \`resources/solution_folder/process/flow/<project>.json\`), and `
284
+ + `drop \`folderPath\`. If it is published, set \`folderPath\` to the Orchestrator folder that holds it — `
285
+ + `the runtime starts the job by name + folder.`, `location: 'in-solution'${formPart}, projectId: '<resource.projectKey>'`);
286
+ }
287
+ else {
288
+ at('AGENTIC_NO_FOLDER', `Agentic process step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so `
289
+ + `the Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-agents/ProcurementProcess'`);
290
+ }
291
+ }
292
+ const form = inputs?.form ?? 'bpmn';
293
+ if (inSolution && form !== 'flow') {
294
+ at('AGENTIC_LOCAL_FORM_UNVERIFIED', `In-solution agentic process step "${step}" has \`form: ${JSON.stringify(form)}\`. Only \`form: 'flow'\` `
295
+ + `has been run against an in-solution sibling; the ${JSON.stringify(form)} shape is unproven. Call the `
296
+ + `published process instead (drop \`location\` and \`projectId\`, set \`folderPath\`), or use `
297
+ + `\`form: 'flow'\` if the sibling is a Flow.`, `location: 'published', folderPath: 'Shared/<folder>'`);
298
+ }
299
+ if (inSolution && blank(inputs?.projectId)) {
300
+ const folder = (ownEntry(AGENTIC_PROCESS_FORMS, inputs?.form ?? 'bpmn') ?? AGENTIC_PROCESS_FORMS.bpmn).solutionFolder;
301
+ at('AGENTIC_LOCAL_NO_PROJECT_ID', `In-solution agentic process step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
302
+ + `\`resources/solution_folder/process/${folder}/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
303
+ }
304
+ else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
305
+ at('AGENTIC_LOCAL_PROJECT_ID_NOT_GUID', `In-solution agentic process step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
306
+ + `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
307
+ }
308
+ if (inSolution && !blank(inputs?.folderPath)) {
309
+ at('AGENTIC_LOCAL_HAS_FOLDER', `In-solution agentic process step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
310
+ + `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
311
+ + `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
312
+ + `call the published copy.`);
313
+ }
314
+ const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
315
+ if (name.includes('/')) {
316
+ at('AGENTIC_NAME_IS_PATH', `Agentic process step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
317
+ + `\`name\` is the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. `
318
+ + `Together they form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
319
+ }
320
+ // The platform's own design-time input, which its serializer strips before the
321
+ // node is emitted — so a process argument by this name is dropped on the way to
322
+ // the tenant and nothing local notices.
323
+ if (inputs?.inputs && Object.prototype.hasOwnProperty.call(inputs.inputs, 'fireAndForget')) {
324
+ at('AGENTIC_RESERVED_INPUT', `Agentic process step "${step}" passes an input called \`fireAndForget\`. That name is RESERVED: it is `
325
+ + `the platform's design-time switch for starting the process without waiting, and its serializer `
326
+ + `removes the key from the node's inputs before emitting — so a process argument of that name never `
327
+ + `arrives. Locally it would appear to work, because this engine passes the whole envelope through. `
328
+ + `Rename the argument, and note that this surface cannot author fire-and-forget at all (the engine `
329
+ + `always waits for the job).`);
330
+ }
331
+ // An empty `returns: {}` is a statement that the process returns nothing, which is
332
+ // legal — but it is almost always a half-finished edit, and the difference between
333
+ // it and a missing `returns` is invisible in the emitted file.
334
+ if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
335
+ ctx.report('AGENTIC_EMPTY_RETURNS', `Agentic process step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
336
+ + `schema at all and so reads exactly like omitting it. If the process returns nothing, drop the key; `
337
+ + `if it returns something, name the fields.`);
338
+ }
339
+ }
340
+ /**
341
+ * How an RPA step's result read is worded — the shared `<PREFIX>_READ_*` rule
342
+ * the pipeline's `fields` reader runs (`checkDeclaredOutputRead`).
343
+ */
344
+ export function rpaWorkflowRead(spec) {
345
+ return {
346
+ codePrefix: 'RPA',
347
+ label: 'RPA process',
348
+ whenUndeclared: `The process's output arguments live on the tenant; declare the ones you read. `
349
+ + `\`uip maestro flow registry get uipath.core.rpa-workflow.${spec?.key}\` shows what the platform `
350
+ + `synthesizes for it.`,
351
+ };
352
+ }
353
+ /** A read of a published function's result — the api-workflow rules, this family's codes. */
354
+ export function checkPublishedFunctionRead({ node: name, field, rest, reader: step }, spec, report) {
355
+ if (field !== undefined && field !== 'output') {
356
+ report('FUNCTION_READ_NOT_OUTPUT', `Function "${name}" publishes its result under "output", but "$vars.${name}.${field}" `
357
+ + `(read in "${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', '<field>')`);
358
+ return;
359
+ }
360
+ const declared = Object.keys(spec.returns ?? {});
361
+ const wanted = rest.replace(/^\./, '').split('.')[0];
362
+ if (!wanted)
363
+ return; // `$vars.<name>.output` — the whole object.
364
+ if (declared.length === 0) {
365
+ report('FUNCTION_READ_WITHOUT_RETURNS', `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of function "${name}"'s `
366
+ + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing can `
367
+ + `tell a real field from a typo. The function's output arguments live on the tenant; declare the ones `
368
+ + `you read. \`uip maestro flow registry get uipath.core.function.${spec.key}\` shows what the platform `
369
+ + `synthesizes for it.`, `returns: { ${wanted}: 'string' }`);
370
+ return;
371
+ }
372
+ if (declared.includes(wanted))
373
+ return;
374
+ report('FUNCTION_READ_UNKNOWN_FIELD', `"$vars.${name}.output.${wanted}" (read in "${step}") is not one of function "${name}"'s declared `
375
+ + `returns (${declared.map((d) => `"${d}"`).join(', ')}), so it resolves to undefined.`, `returns: { ${wanted}: 'string' }`);
376
+ }
377
+ export function checkApiWorkflowRead({ node: name, field, rest, reader: step }, spec, report) {
378
+ if (field === undefined)
379
+ return; // `out('call')` — the whole output object.
380
+ if (field === 'error')
381
+ return; // the error envelope every process node declares.
382
+ if (field !== 'output') {
383
+ report('APIWF_READ_NOT_OUTPUT', `API workflow "${name}" publishes the job's output arguments under "output", but `
384
+ + `"$vars.${name}.${field}" (read in "${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', '<field>')`);
385
+ return;
386
+ }
387
+ const declared = Object.keys(spec.returns ?? {});
388
+ const wanted = rest.replace(/^\./, '').split('.')[0];
389
+ if (!wanted)
390
+ return; // `$vars.<name>.output` — the whole object.
391
+ if (declared.length === 0) {
392
+ report('APIWF_READ_WITHOUT_RETURNS', `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of API workflow "${name}"'s `
393
+ + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing `
394
+ + `can tell a real field from a typo. The workflow's output arguments live on the tenant; declare `
395
+ + `the ones you read. \`uip maestro flow registry get uipath.core.api-workflow.${spec.key}\` `
396
+ + `shows what the platform synthesizes for it.`, `returns: { ${wanted}: 'string' }`);
397
+ return;
398
+ }
399
+ if (declared.includes(wanted))
400
+ return;
401
+ const nearest = nearestName(wanted, declared);
402
+ report('APIWF_READ_UNKNOWN_FIELD', `API workflow "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
403
+ + `"${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined `
404
+ + `on every rung and on the platform.`, nearest !== undefined ? `out('${name}', '${nearest}')` : undefined);
405
+ }
406
+ /**
407
+ * A read of an agentic process's output: `out('<step>', '<field>')`.
408
+ *
409
+ * `waitForJob` records the job's `OutputArguments` UNWRAPPED under `output`, so the
410
+ * readable fields are exactly the ones `returns` declares. The three mistakes are
411
+ * api-workflow's, and one of them has a sharper edge here: a process may DECLARE a
412
+ * field and return it EMPTY (`ProcurementProcess` declares `{status: boolean}` and
413
+ * answers `{"status": null}` on every job), so a declared read is checkable but a
414
+ * non-empty VALUE is not something any offline gate can promise.
415
+ */
416
+ export function checkAgenticProcessRead({ node: name, field, rest, reader: step }, spec, report) {
417
+ if (field === undefined)
418
+ return; // `out('call')` — the whole output object.
419
+ if (field === 'error')
420
+ return; // the error envelope every process node declares.
421
+ if (field !== 'output') {
422
+ report('AGENTIC_READ_NOT_OUTPUT', `Agentic process "${name}" publishes the job's output arguments under "output", but `
423
+ + `"$vars.${name}.${field}" (read in "${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', '<field>')`);
424
+ return;
425
+ }
426
+ const declared = Object.keys(spec.returns ?? {});
427
+ const wanted = rest.replace(/^\./, '').split('.')[0];
428
+ if (!wanted)
429
+ return; // `$vars.<name>.output` — the whole object.
430
+ if (declared.length === 0) {
431
+ report('AGENTIC_READ_WITHOUT_RETURNS', `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of agentic process "${name}"'s `
432
+ + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing can `
433
+ + `tell a real field from a typo. The process's output arguments live on the tenant; declare the ones `
434
+ + `you read. \`uip maestro flow registry get uipath.core.agentic-process.${spec.key}\` shows what `
435
+ + `the platform synthesizes for it.`, `returns: { ${wanted}: 'string' }`);
436
+ return;
437
+ }
438
+ if (declared.includes(wanted))
439
+ return;
440
+ const nearest = nearestName(wanted, declared);
441
+ report('AGENTIC_READ_UNKNOWN_FIELD', `Agentic process "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
442
+ + `"${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined on `
443
+ + `every rung and on the platform.`, nearest !== undefined ? `out('${name}', '${nearest}')` : undefined);
444
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The process factories — `rpaWorkflow()`, `apiWorkflow()`, `publishedFunction()`
3
+ * and `agenticProcess()`. Moved from core/actions.ts in Phase 4 of
4
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
5
+ */
6
+ import { type ActionSpec } from '../../../core/actions.js';
7
+ import type { AgenticProcessInputs, ApiWorkflowInputs, PublishedFunctionInputs, RpaWorkflowInputs } from './spec.js';
8
+ /**
9
+ * Invoke a deployed RPA (robotic process automation) workflow — a classic
10
+ * UI-automation process, published to Orchestrator, run by a robot.
11
+ *
12
+ * @remarks
13
+ * Three fields identify the process and they all come from the tenant, which is
14
+ * why they are named exactly as the platform names them:
15
+ *
16
+ * key the published process's release key (a GUID). It becomes part of
17
+ * the node's TYPE — `uipath.core.rpa-workflow.<key>` — so the
18
+ * platform can tell one process's node from another's.
19
+ * name the process's name in Orchestrator.
20
+ * folderPath the Orchestrator folder it lives in.
21
+ *
22
+ * `name` + `folderPath` are what the robot is actually started by (the runtime
23
+ * re-resolves the release from them), and together they are also the process's
24
+ * `resourceKey`: `"<folderPath>.<name>"`. The serializer emits the two of them
25
+ * as the flow's process bindings and points the node at them, because a
26
+ * bindings entry the node does not reference is not a bound process.
27
+ *
28
+ * `inputs` are the PROCESS's own input arguments — whatever it declares, by its
29
+ * own argument names. They are not a fixed schema.
30
+ *
31
+ * The step publishes whatever the process returns, under `output`: read a field
32
+ * with `out('<step>', '<field>')`. Nothing local can check that an argument name
33
+ * or a returned field name matches the deployed process — only the robot knows —
34
+ * so a wrong name shows up as a faulted job on a live run, not at compile time.
35
+ *
36
+ * @example
37
+ * **Start a deployed process and read what it returns**
38
+ * ```ts
39
+ * .step('getTitle', rpaWorkflow({
40
+ * key: '486edc26-0658-4ac1-92c9-1ef953927151',
41
+ * name: 'RPA Workflow',
42
+ * folderPath: 'Shared/uipath-maestro-flow/ProjectEuler RPA',
43
+ * inputs: { problemId: 123 },
44
+ * }))
45
+ * .return({ title: out('getTitle', 'title') })
46
+ * ```
47
+ *
48
+ * @param inputs - The published process to start (`key`, `name`, `folderPath`),
49
+ * its `inputs`, and the output fields you read back via `returns`.
50
+ * @returns An action spec to pass to `.step(name, …)`.
51
+ */
52
+ export declare function rpaWorkflow(inputs: RpaWorkflowInputs): ActionSpec;
53
+ /**
54
+ * Invoke a deployed API workflow — a coded workflow published to Orchestrator,
55
+ * run as a serverless job rather than by a robot.
56
+ *
57
+ * @remarks
58
+ * The same three identifiers as `rpaWorkflow`, for the same reason: `key` is
59
+ * the key the node's TYPE carries (`uipath.core.api-workflow.<key>`), while
60
+ * `name` + `folderPath` are what the runtime resolves the job by — and together
61
+ * they are the workflow's `resourceKey`, `"<folderPath>.<name>"`. The serializer
62
+ * emits those two as the flow's process bindings and points the node at them.
63
+ *
64
+ * The step publishes the job's output arguments under `output`, so
65
+ * `out('<step>', '<field>')` reads one of the fields `returns` declares. Whether
66
+ * an argument name or a returned field actually matches the deployed workflow is
67
+ * something only the tenant knows — a wrong one is a faulted job on a live run,
68
+ * not a compile error.
69
+ *
70
+ * @example
71
+ * **Start an API workflow and read a declared output argument**
72
+ * ```ts
73
+ * .step('getAge', apiWorkflow({
74
+ * key: 'ce857908-ee1d-4392-b552-38bcea0be29c',
75
+ * name: 'NameToAgeFixed',
76
+ * folderPath: 'Shared',
77
+ * inputs: { name: 'tomasz' },
78
+ * returns: { EstimatedAge: 'integer' },
79
+ * }))
80
+ * .return({ age: out('getAge', 'EstimatedAge') })
81
+ * ```
82
+ *
83
+ * @param inputs - The published API workflow to start (`key`, `name`,
84
+ * `folderPath`), its `inputs`, and the fields you read back via `returns`.
85
+ * @returns An action spec to pass to `.step(name, …)`.
86
+ */
87
+ export declare function apiWorkflow(inputs: ApiWorkflowInputs): ActionSpec;
88
+ /**
89
+ * Invoke a published Orchestrator **Function** — a deployed unit of code run as
90
+ * one step (`uipath.core.function.<key>`, dispatched as
91
+ * `Orchestrator.ExecuteFunctionAsync`).
92
+ *
93
+ * ```ts
94
+ * .step('echo', publishedFunction({
95
+ * key: '7059bdb5-fdd7-4e13-9d7b-1748aaeb129d',
96
+ * name: 'acme-echo',
97
+ * folderPath: 'Shared/acme-echo',
98
+ * inputs: { message: 'hello' },
99
+ * returns: { echoed: 'string' },
100
+ * }))
101
+ * ```
102
+ *
103
+ * The node type carries the key, so the function must already exist on the
104
+ * tenant — read `key`, `name` and `folderPath` from the registry rather than
105
+ * constructing them. Like the other published families, the resource identity
106
+ * is CLOSED by a binding pair (`<folderPath>.<name>`), and two steps on the
107
+ * same function share one pair.
108
+ *
109
+ * @param inputs - The function's identity, its arguments, and what it returns.
110
+ * @returns An action spec to pass to `.step(...)`.
111
+ */
112
+ export declare function publishedFunction(inputs: PublishedFunctionInputs): ActionSpec;
113
+ /**
114
+ * Invoke a deployed AGENTIC PROCESS — a Maestro process orchestration published to
115
+ * Orchestrator: a BPMN process that coordinates agents, robots and people, started
116
+ * as a job the same way an RPA or API workflow is.
117
+ *
118
+ * @remarks
119
+ * The same three identifiers as `rpaWorkflow` and `apiWorkflow`, for the same
120
+ * reason: `key` is the key the node's TYPE carries
121
+ * (`uipath.core.agentic-process.<key>`), while `name` + `folderPath` are what
122
+ * the runtime resolves the job by — and together they are the process's
123
+ * `resourceKey`, `"<folderPath>.<name>"`. The serializer emits those two as the
124
+ * flow's process bindings and points the node at them.
125
+ *
126
+ * By default the step WAITS for the process and publishes its output arguments
127
+ * under `output`, so `out('<step>', '<field>')` reads one of the fields
128
+ * `returns` declares.
129
+ *
130
+ * `completion: 'fire-and-forget'` selects the designer's own async switch
131
+ * instead: the node's service type becomes `Orchestrator.Start…ProcessAsync`
132
+ * and it publishes no output but `error`. The type is a discriminated union, so
133
+ * `returns` is impossible in that form and nothing downstream can read one; a
134
+ * local run dispatches without waiting and yields `{ jobKey, started: true }`.
135
+ *
136
+ * `form` picks which published Agentic Process this is — `'bpmn'` (the default,
137
+ * a Maestro process orchestration), `'flow'` (a published Maestro Flow), or
138
+ * `'case'` (a Case Management process). They share one public concept and
139
+ * differ only in wire identity.
140
+ *
141
+ * @example
142
+ * **Start a Maestro process and wait for its result**
143
+ * ```ts
144
+ * .step('runIntake', agenticProcess({
145
+ * key: '4fc450ab-89be-4462-8fc8-21ac4c1d6fb9',
146
+ * name: 'ProcurementProcess',
147
+ * folderPath: 'Shared/uipath-agents/ProcurementProcess',
148
+ * inputs: { productId: 1 },
149
+ * returns: { status: 'boolean' },
150
+ * }))
151
+ * .return({ ok: out('runIntake', 'status') })
152
+ * ```
153
+ *
154
+ * @param inputs - The published Maestro process to start (`key`, `name`,
155
+ * `folderPath`), its `inputs`, and the fields you read back via `returns`.
156
+ * @returns An action spec to pass to `.step(name, …)`.
157
+ */
158
+ export declare function agenticProcess(inputs: AgenticProcessInputs): ActionSpec;