@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.
package/dist/check.js CHANGED
@@ -11,13 +11,13 @@ import { planContributionBindings } from './core/contribution-bindings.js';
11
11
  import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
12
12
  import { actionVersion, bundledActionNodeType, bundledContainerDefinition, bundledDefinition, perCallDefinition, resolveContributionDefinition } from './serialize.js';
13
13
  import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText, } from './core/form-field-type.js';
14
- import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
15
14
  import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
16
15
  import { checkCookedEscapes } from './flow-expr-check.js';
17
16
  import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
18
17
  import { prepareCommand } from './core/cli-spelling.js';
19
18
  import { commentText } from './core/comment-text.js';
20
19
  import { nearestName } from './core/edit-distance.js';
20
+ import { renamedFieldMessage } from './core/process-resource.js';
21
21
  import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome, outcomeIdOf } from './core/hitl-routing.js';
22
22
  import { hasExits } from './core/step-ports.js';
23
23
  import { stopsAtEnd } from './flow-step-tree.js';
@@ -514,24 +514,16 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
514
514
  });
515
515
  }
516
516
  }
517
- if (s.spec.kind === 'rpaWorkflow')
518
- checkRpaWorkflow(s.name, s.spec.inputs, diags);
519
- if (s.spec.kind === 'apiWorkflow') {
520
- checkApiWorkflow(s.name, s.spec.inputs, diags);
521
- }
522
- if (s.spec.kind === 'publishedFunction') {
523
- checkPublishedFunction(s.name, s.spec.inputs, diags);
524
- }
525
517
  if (s.spec.kind === 'createOutgoingCall' || s.spec.kind === 'endCall' || s.spec.kind === 'voiceAgent') {
526
518
  checkVoice(s.name, s.spec.kind, s.spec.inputs, diags);
527
519
  }
528
520
  if (s.spec.kind === 'conversationalAgent') {
529
521
  checkConversationalAgent(s.name, s.spec.inputs, diags);
530
522
  }
531
- if (s.spec.kind === 'agenticProcess') {
532
- checkAgenticProcess(s.name, s.spec.inputs, diags);
523
+ // The process family checks the step itself; the flow-level fire-and-forget
524
+ // read rule below needs to know which steps dispatch without waiting.
525
+ if (s.spec.kind === 'agenticProcess')
533
526
  agenticProcesses.set(s.name, s.spec.inputs);
534
- }
535
527
  if (s.spec.kind === 'agent') {
536
528
  checkAgent(s.name, s.spec.inputs, diags);
537
529
  }
@@ -2106,161 +2098,6 @@ function codeOnly(src) {
2106
2098
  return out.join('');
2107
2099
  }
2108
2100
  const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2109
- /**
2110
- * The four published-resource factories once each named the release key
2111
- * differently — `rpaWorkflow({process})`, `apiWorkflow({workflow})`,
2112
- * `agenticProcess({process})`, `agent({agent})` — for one identical thing: the
2113
- * GUID in a process's `Key` field. They are all `key` now.
2114
- *
2115
- * Flows authored against the old names would otherwise fail as "has no `key`",
2116
- * which points at the wrong problem. When the retired field is the one carrying a
2117
- * value, say so and name the replacement.
2118
- */
2119
- function renamedFieldMessage(label, step, oldField) {
2120
- return (`${label} step "${step}" uses \`${oldField}\`, which was renamed to \`key\`. All four ` +
2121
- `published-resource factories now take \`key\` — the GUID in the process's \`Key\` field ` +
2122
- `(\`uip or processes list\`) — so they read the same way. Rename \`${oldField}:\` to \`key:\`.`);
2123
- }
2124
- /**
2125
- * Check an `rpaWorkflow(...)` call.
2126
- *
2127
- * WHAT THESE RULES CAN AND CANNOT SEE. Everything about a deployed process that
2128
- * matters — which arguments it takes, what it returns, whether it exists —
2129
- * lives on the tenant. So these rules cover exactly the mistakes that are
2130
- * decidable from the call itself, and the doc says plainly that the rest shows
2131
- * up as a faulted job on a live run. What is decidable:
2132
- *
2133
- * - a missing/blank `key`, `name` or `folderPath`. Any of the three missing
2134
- * is fatal in a way no later rung reports usefully: without `key` the node
2135
- * has no type suffix, and without the other two nothing can resolve the node's
2136
- * `resourceBindings.name` — which reads as an engine error rather than an
2137
- * authoring one.
2138
- * - a `key` that is not a GUID. The commonest form of this is passing the
2139
- * process's NAME where its release key belongs, which produces a node type
2140
- * the platform cannot resolve to anything.
2141
- * - a `name` that looks like a path (contains "/"). The two are swappable, and
2142
- * the resulting `resourceKey` is wrong in a way that only surfaces as a 400
2143
- * from Orchestrator ("Folder does not exist"). That is v1's own 2026-06-26
2144
- * failure mode on this task. A single-segment `folderPath` is NOT decidable:
2145
- * root folders and personal workspaces are one segment, so it is not flagged.
2146
- */
2147
- function checkRpaWorkflow(step, inputs, diags) {
2148
- const at = (code, message, suggestion) => {
2149
- diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
2150
- };
2151
- const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
2152
- if (blank(inputs?.key) && !blank(inputs?.process)) {
2153
- at('RPA_NO_KEY', renamedFieldMessage('RPA', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
2154
- }
2155
- else if (blank(inputs?.key)) {
2156
- at('RPA_NO_KEY', `RPA step "${step}" has no \`key\`. That is the published process's release key (a GUID), and `
2157
- + `it becomes part of the node's type — without it there is no process to start. Find it with `
2158
- + `\`uip or processes list --all-folders --process-type Process\` (the row's \`Key\`).`, `key: '486edc26-0658-4ac1-92c9-1ef953927151'`);
2159
- }
2160
- else if (!GUID_RE.test(String(inputs.key).trim())) {
2161
- at('RPA_KEY_NOT_KEY', `RPA step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a release key. `
2162
- + `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
2163
- + `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
2164
- }
2165
- if (blank(inputs?.name)) {
2166
- at('RPA_NO_NAME', `RPA step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by the key in `
2167
- + `the node type, so without it the process cannot be resolved and the run fails before dispatch.`, `name: 'RPA Workflow'`);
2168
- }
2169
- const inSolution = inputs?.location === 'in-solution';
2170
- if (!inSolution && blank(inputs?.folderPath)) {
2171
- at('RPA_NO_FOLDER', `RPA step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so the `
2172
- + `Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-maestro-flow/ProjectEuler RPA'`);
2173
- }
2174
- if (inSolution && blank(inputs?.projectId)) {
2175
- at('RPA_LOCAL_NO_PROJECT_ID', `In-solution RPA step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
2176
- + `\`resources/solution_folder/process/process/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
2177
- }
2178
- else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
2179
- at('RPA_LOCAL_PROJECT_ID_NOT_GUID', `In-solution RPA step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
2180
- + `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
2181
- }
2182
- if (inSolution && !blank(inputs?.folderPath)) {
2183
- at('RPA_LOCAL_HAS_FOLDER', `In-solution RPA step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
2184
- + `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
2185
- + `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
2186
- + `call the published copy.`);
2187
- }
2188
- // The two are swappable by shape, and swapping them is invisible until
2189
- // Orchestrator answers "Folder does not exist or the user does not have access".
2190
- const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
2191
- if (name.includes('/')) {
2192
- at('RPA_NAME_IS_PATH', `RPA step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
2193
- + `the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. Together they `
2194
- + `form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
2195
- }
2196
- }
2197
- /**
2198
- * Check an `apiWorkflow(...)` call.
2199
- *
2200
- * Same three-identifier shape as `rpaWorkflow`, so the same three mistakes are
2201
- * decidable from the call itself (a blank field, a `key` that is not a key, and
2202
- * the swappable `name`/`folderPath` pair). Two things differ, and both come from
2203
- * the corpus rather than from the surface:
2204
- *
2205
- * - the node type's GUID is the workflow's PROCESS key — the `Key` field of
2206
- * `uip or processes list`, whose `ProcessKey` for an API workflow reads
2207
- * `<name>.api.<name>` — so the command to name in the diagnostic is that one;
2208
- * - `returns` matters MORE here than for rpa, because this task's whole answer is
2209
- * a field of the job's output. A read with no `returns` has no schema to check (and
2210
- * a read of an undeclared field is `undefined` everywhere), so both are said at
2211
- * rung 1 instead — see `checkApiWorkflowRead`.
2212
- *
2213
- * What no rule here can see stays what it is: whether the workflow exists, whether
2214
- * it takes these argument names, whether it returns these fields. Only the tenant
2215
- * knows, and a wrong one is a faulted job on a live run — which is what the `job`
2216
- * rung is for.
2217
- */
2218
- function checkApiWorkflow(step, inputs, diags) {
2219
- const at = (code, message, suggestion) => {
2220
- diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
2221
- };
2222
- const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
2223
- if (blank(inputs?.key) && !blank(inputs?.workflow)) {
2224
- at('APIWF_NO_KEY', renamedFieldMessage('API workflow', step, 'workflow'), `key: ${JSON.stringify(inputs.workflow)}`);
2225
- }
2226
- else if (blank(inputs?.key)) {
2227
- at('APIWF_NO_KEY', `API workflow step "${step}" has no \`key\`. That is the published workflow's key (a GUID), and `
2228
- + `it becomes part of the node's type — without it there is no workflow to invoke. Find it with `
2229
- + `\`uip or processes list --all-folders\` (an API workflow's ProcessKey reads "<name>.api.<name>").`, `key: 'ce857908-ee1d-4392-b552-38bcea0be29c'`);
2230
- }
2231
- else if (!GUID_RE.test(String(inputs.key).trim())) {
2232
- at('APIWF_KEY_NOT_KEY', `API workflow step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
2233
- + `\`key\` is the GUID the tenant gives the published workflow (it becomes the node type's suffix); `
2234
- + `the workflow's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
2235
- }
2236
- if (blank(inputs?.name)) {
2237
- at('APIWF_NO_NAME', `API workflow step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by the `
2238
- + `key in the node type, so without it the workflow cannot be resolved and the run fails before dispatch.`, `name: 'NameToAgeFixed'`);
2239
- }
2240
- if (blank(inputs?.folderPath)) {
2241
- at('APIWF_NO_FOLDER', `API workflow step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so the `
2242
- + `Orchestrator folder holding the workflow is required.`, `folderPath: 'Shared'`);
2243
- }
2244
- const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
2245
- if (name.includes('/')) {
2246
- at('APIWF_NAME_IS_PATH', `API workflow step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
2247
- + `\`name\` is the workflow's own name in Orchestrator; the path it lives in goes in \`folderPath\`. `
2248
- + `Together they form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
2249
- }
2250
- // An empty `returns: {}` is a statement that the workflow returns nothing, which
2251
- // is legal — but it is almost always a half-finished edit, and the difference
2252
- // between it and a missing `returns` is invisible in the emitted file.
2253
- if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
2254
- diags.push({
2255
- level: 'warning',
2256
- code: 'APIWF_EMPTY_RETURNS',
2257
- message: `API workflow step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
2258
- + `schema at all and so reads exactly like omitting it. If the workflow returns nothing, drop the `
2259
- + `key; if it returns something, name the fields.`,
2260
- step,
2261
- });
2262
- }
2263
- }
2264
2101
  /**
2265
2102
  * A read of an API workflow's output: `out('<step>', '<field>')`.
2266
2103
  *
@@ -2401,88 +2238,6 @@ function checkVoice(step, kind, inputs, diags) {
2401
2238
  }
2402
2239
  }
2403
2240
  }
2404
- /**
2405
- * A published FUNCTION step (Phase 5B).
2406
- *
2407
- * The same four identity mistakes as its published siblings, in this family's
2408
- * own vocabulary — and one of its own: a function is usually deployed into a
2409
- * folder of its own name, so `folderPath: 'Shared'` when the tenant says
2410
- * `Shared/<name>` points the binding's resourceKey at nothing.
2411
- */
2412
- function checkPublishedFunction(step, inputs, diags) {
2413
- const at = (code, message, suggestion) => {
2414
- diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
2415
- };
2416
- const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
2417
- if (blank(inputs?.key)) {
2418
- at('FUNCTION_NO_KEY', `Function step "${step}" has no \`key\`. That is the published function's key (a GUID), and it becomes `
2419
- + `part of the node's type — without it there is no function to invoke. Find it with `
2420
- + `\`uip maestro flow registry search "uipath.core.function"\`.`, `key: '7059bdb5-fdd7-4e13-9d7b-1748aaeb129d'`);
2421
- }
2422
- else if (!GUID_RE.test(String(inputs.key).trim())) {
2423
- at('FUNCTION_KEY_NOT_KEY', `Function step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. \`key\` is the `
2424
- + `GUID the tenant gives the published function (it becomes the node type's suffix); the function's NAME `
2425
- + `goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
2426
- }
2427
- if (blank(inputs?.name)) {
2428
- at('FUNCTION_NO_NAME', `Function step "${step}" has no \`name\`. The runtime resolves the function by name + folder, not by the `
2429
- + `key in the node type, so without it the dispatch fails before it starts.`, `name: 'acme-echo'`);
2430
- }
2431
- if (blank(inputs?.folderPath)) {
2432
- at('FUNCTION_NO_FOLDER', `Function step "${step}" has no \`folderPath\`. The runtime resolves the function by name + folder — and a `
2433
- + `function is often deployed into a folder of its OWN name, so read the folder from the tenant rather `
2434
- + `than assuming 'Shared'.`, `folderPath: 'Shared/acme-echo'`);
2435
- }
2436
- const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
2437
- if (name.includes('/')) {
2438
- at('FUNCTION_NAME_IS_PATH', `Function step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. \`name\` is `
2439
- + `the function's own name; the path it lives in goes in \`folderPath\`. Together they form the `
2440
- + `resourceKey "<folderPath>.<name>", so swapping them points the dispatch at nothing.`);
2441
- }
2442
- if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
2443
- diags.push({
2444
- level: 'warning', code: 'FUNCTION_EMPTY_RETURNS', step,
2445
- message: `Function step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
2446
- + `schema and so reads exactly like omitting it. If the function returns nothing, drop the key.`,
2447
- });
2448
- }
2449
- }
2450
- /** A read of a published function's result — the api-workflow rules, this family's codes. */
2451
- function checkPublishedFunctionRead(name, field, rest, spec, step, diags) {
2452
- if (field !== undefined && field !== 'output') {
2453
- diags.push({
2454
- level: 'error', code: 'FUNCTION_READ_NOT_OUTPUT', step,
2455
- message: `Function "${name}" publishes its result under "output", but "$vars.${name}.${field}" `
2456
- + `(read in "${step}") asks for "${field}", which is undefined at run time.`,
2457
- suggestion: `out('${name}', '<field>')`,
2458
- });
2459
- return;
2460
- }
2461
- const declared = Object.keys(spec.returns ?? {});
2462
- const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
2463
- if (!wanted)
2464
- return; // `$vars.<name>.output` — the whole object.
2465
- if (declared.length === 0) {
2466
- diags.push({
2467
- level: 'error', code: 'FUNCTION_READ_WITHOUT_RETURNS', step,
2468
- message: `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of function "${name}"'s `
2469
- + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing can `
2470
- + `tell a real field from a typo. The function's output arguments live on the tenant; declare the ones `
2471
- + `you read. \`uip maestro flow registry get uipath.core.function.${spec.key}\` shows what the platform `
2472
- + `synthesizes for it.`,
2473
- suggestion: `returns: { ${wanted}: 'string' }`,
2474
- });
2475
- return;
2476
- }
2477
- if (declared.includes(wanted))
2478
- return;
2479
- diags.push({
2480
- level: 'error', code: 'FUNCTION_READ_UNKNOWN_FIELD', step,
2481
- message: `"$vars.${name}.output.${wanted}" (read in "${step}") is not one of function "${name}"'s declared `
2482
- + `returns (${declared.map((d) => `"${d}"`).join(', ')}), so it resolves to undefined.`,
2483
- suggestion: `returns: { ${wanted}: 'string' }`,
2484
- });
2485
- }
2486
2241
  /**
2487
2242
  * The read rule the process-shaped families share: the result is published
2488
2243
  * under `output`, keyed by a known field list — the author's `returns`, or a
@@ -2525,16 +2280,6 @@ function checkDeclaredOutputRead(o) {
2525
2280
  ...(nearest !== undefined ? { suggestion: `out('${name}', '${nearest}')` } : {}),
2526
2281
  });
2527
2282
  }
2528
- function checkRpaWorkflowRead(name, field, rest, spec, step, diags) {
2529
- const declared = Object.keys(spec?.returns ?? {});
2530
- checkDeclaredOutputRead({
2531
- prefix: 'RPA', what: 'RPA process', name, field, rest, step, diags,
2532
- declared: declared.length ? declared : undefined,
2533
- withoutReturns: `The process's output arguments live on the tenant; declare the ones you read. `
2534
- + `\`uip maestro flow registry get uipath.core.rpa-workflow.${spec?.key}\` shows what the platform `
2535
- + `synthesizes for it.`,
2536
- });
2537
- }
2538
2283
  /**
2539
2284
  * THE GATE (flow-builder-sdk#850). Each `returns`-bearing kind maps to the rule
2540
2285
  * that checks a read of its output, or to a stated reason it has none.
@@ -2549,10 +2294,10 @@ function checkRpaWorkflowRead(name, field, rest, spec, step, diags) {
2549
2294
  */
2550
2295
  const OUTPUT_READ_RULES = {
2551
2296
  http: { exempt: 'Registered: the http node types read their own output (nodes/http/flow, a `reader` contract).' },
2552
- rpaWorkflow: checkRpaWorkflowRead,
2553
- apiWorkflow: checkApiWorkflowRead,
2554
- publishedFunction: checkPublishedFunctionRead,
2555
- agenticProcess: checkAgenticProcessRead,
2297
+ rpaWorkflow: { exempt: 'Registered: its node type states the fields a read may name (nodes/process/flow/, a `fields` output contract).' },
2298
+ apiWorkflow: { exempt: 'Registered: its node type reads its own output (nodes/process/flow/, a `reader` contract).' },
2299
+ publishedFunction: { exempt: 'Registered: its node type reads its own output (nodes/process/flow/, a `reader` contract).' },
2300
+ agenticProcess: { exempt: 'Registered: its node type reads its own output (nodes/process/flow/, a `reader` contract).' },
2556
2301
  agent: checkAgentRead,
2557
2302
  inlineAgent: checkInlineAgentRead,
2558
2303
  queueItem: { exempt: 'Registered: its node type states the fields a read may name (nodes/queue/flow/, a `fields` output contract).' },
@@ -2562,230 +2307,6 @@ const OUTPUT_READ_RULES = {
2562
2307
  + 'its own false-positive sweep — flow-builder-sdk#855.',
2563
2308
  },
2564
2309
  };
2565
- function checkApiWorkflowRead(name, field, rest, spec, step, diags) {
2566
- if (field === undefined)
2567
- return; // `out('call')` — the whole output object.
2568
- if (field === 'error')
2569
- return; // the error envelope every process node declares.
2570
- if (field !== 'output') {
2571
- diags.push({
2572
- level: 'error',
2573
- code: 'APIWF_READ_NOT_OUTPUT',
2574
- message: `API workflow "${name}" publishes the job's output arguments under "output", but `
2575
- + `"$vars.${name}.${field}" (read in "${step}") asks for "${field}", which is undefined at run time.`,
2576
- step,
2577
- suggestion: `out('${name}', '<field>')`,
2578
- });
2579
- return;
2580
- }
2581
- const declared = Object.keys(spec.returns ?? {});
2582
- const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
2583
- if (!wanted)
2584
- return; // `$vars.<name>.output` — the whole object.
2585
- if (declared.length === 0) {
2586
- diags.push({
2587
- level: 'error',
2588
- code: 'APIWF_READ_WITHOUT_RETURNS',
2589
- message: `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of API workflow "${name}"'s `
2590
- + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing `
2591
- + `can tell a real field from a typo. The workflow's output arguments live on the tenant; declare `
2592
- + `the ones you read. \`uip maestro flow registry get uipath.core.api-workflow.${spec.key}\` `
2593
- + `shows what the platform synthesizes for it.`,
2594
- step,
2595
- suggestion: `returns: { ${wanted}: 'string' }`,
2596
- });
2597
- return;
2598
- }
2599
- if (declared.includes(wanted))
2600
- return;
2601
- const nearest = nearestName(wanted, declared);
2602
- diags.push({
2603
- level: 'error',
2604
- code: 'APIWF_READ_UNKNOWN_FIELD',
2605
- message: `API workflow "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
2606
- + `"${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined `
2607
- + `on every rung and on the platform.`,
2608
- step,
2609
- ...(nearest !== undefined ? { suggestion: `out('${name}', '${nearest}')` } : {}),
2610
- });
2611
- }
2612
- /**
2613
- * Check an `agenticProcess(...)` call.
2614
- *
2615
- * The same three-identifier shape as `rpaWorkflow`/`apiWorkflow`, so the same
2616
- * mistakes are decidable from the call itself. Two things are this family's own:
2617
- *
2618
- * - the node type's GUID is the process's own key — the `Key` field of
2619
- * `uip or processes list`, whose `ProcessKey` for an agentic process reads
2620
- * `<name>.agentic.<name>` — so that is the command the diagnostic names. And it
2621
- * names the pagination, because `--all-folders` returns 50 rows with
2622
- * `Pagination.HasMore: true` and no warning: the tenant this was written against
2623
- * has 98 processes, and the agentic ones are not all in the first page.
2624
- * - `fireAndForget` is a RESERVED input. It is the platform's design-time switch
2625
- * (flow-v1's `FIRE_AND_FORGET_INPUT`), and its serializer destructures it OUT of
2626
- * the node's inputs before emitting, so a process argument that happens to be
2627
- * called `fireAndForget` never reaches the process — silently, with a green run
2628
- * locally, because a runner that passes the whole envelope through sees nothing
2629
- * wrong.
2630
- *
2631
- * What no rule here can see stays what it is: whether the process exists, whether
2632
- * it takes these argument names, whether it returns these fields. Only the tenant
2633
- * knows, and a wrong one is a faulted job on a live run — which is what the `job`
2634
- * rung is for.
2635
- */
2636
- function checkAgenticProcess(step, inputs, diags) {
2637
- const at = (code, message, suggestion) => {
2638
- diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
2639
- };
2640
- const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
2641
- if (blank(inputs?.key) && !blank(inputs?.process)) {
2642
- at('AGENTIC_NO_KEY', renamedFieldMessage('Agentic process', step, 'process'), `key: ${JSON.stringify(inputs.process)}`);
2643
- }
2644
- else if (blank(inputs?.key)) {
2645
- at('AGENTIC_NO_KEY', `Agentic process step "${step}" has no \`key\`. That is the published process's key (a GUID), `
2646
- + `and it becomes part of the node's type — without it there is no process to start. Find it with `
2647
- + `\`uip or processes list --all-folders --limit 100\` (an agentic process's ProcessKey reads `
2648
- + `"<name>.agentic.<name>"; the listing paginates at 50, so pass a limit or scope it to a folder).`, `key: '4fc450ab-89be-4462-8fc8-21ac4c1d6fb9'`);
2649
- }
2650
- else if (!GUID_RE.test(String(inputs.key).trim())) {
2651
- at('AGENTIC_KEY_NOT_KEY', `Agentic process step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. `
2652
- + `\`key\` is the GUID the tenant gives the published process (it becomes the node type's suffix); `
2653
- + `the process's NAME goes in \`name\` and its folder in \`folderPath\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
2654
- }
2655
- if (blank(inputs?.name)) {
2656
- at('AGENTIC_NO_NAME', `Agentic process step "${step}" has no \`name\`. The runtime starts the job by name + folder, not by `
2657
- + `the key in the node type, so without it the process cannot be resolved and the run fails before `
2658
- + `dispatch.`, `name: 'ProcurementProcess'`);
2659
- }
2660
- const inSolution = inputs?.location === 'in-solution';
2661
- if (!inSolution && blank(inputs?.folderPath)) {
2662
- // `registry list --local` shows an in-solution Flow sibling with folderPath: "".
2663
- // An explicit `form` other than 'flow' keeps the published hint: switching it would change the node type.
2664
- const meantInSolution = inputs?.form === 'flow'
2665
- || (typeof inputs?.folderPath === 'string' && inputs?.form === undefined);
2666
- if (meantInSolution) {
2667
- const formPart = inputs?.form === 'flow' ? '' : `, form: 'flow'`;
2668
- at('AGENTIC_NO_FOLDER', `Agentic process step "${step}" has an empty \`folderPath\`. If the process is an unpublished sibling `
2669
- + `Flow in this solution, call it in-solution: set \`location: 'in-solution'\`${formPart} and \`projectId\` `
2670
- + `(the sibling's \`resource.projectKey\` in \`resources/solution_folder/process/flow/<project>.json\`), and `
2671
- + `drop \`folderPath\`. If it is published, set \`folderPath\` to the Orchestrator folder that holds it — `
2672
- + `the runtime starts the job by name + folder.`, `location: 'in-solution'${formPart}, projectId: '<resource.projectKey>'`);
2673
- }
2674
- else {
2675
- at('AGENTIC_NO_FOLDER', `Agentic process step "${step}" has no \`folderPath\`. The runtime starts the job by name + folder, so `
2676
- + `the Orchestrator folder holding the process is required.`, `folderPath: 'Shared/uipath-agents/ProcurementProcess'`);
2677
- }
2678
- }
2679
- const form = inputs?.form ?? 'bpmn';
2680
- if (inSolution && form !== 'flow') {
2681
- at('AGENTIC_LOCAL_FORM_UNVERIFIED', `In-solution agentic process step "${step}" has \`form: ${JSON.stringify(form)}\`. Only \`form: 'flow'\` `
2682
- + `has been run against an in-solution sibling; the ${JSON.stringify(form)} shape is unproven. Call the `
2683
- + `published process instead (drop \`location\` and \`projectId\`, set \`folderPath\`), or use `
2684
- + `\`form: 'flow'\` if the sibling is a Flow.`, `location: 'published', folderPath: 'Shared/<folder>'`);
2685
- }
2686
- if (inSolution && blank(inputs?.projectId)) {
2687
- const folder = (ownEntry(AGENTIC_PROCESS_FORMS, inputs?.form ?? 'bpmn') ?? AGENTIC_PROCESS_FORMS.bpmn).solutionFolder;
2688
- at('AGENTIC_LOCAL_NO_PROJECT_ID', `In-solution agentic process step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
2689
- + `\`resources/solution_folder/process/${folder}/<project>.json\`.`, `projectId: '<resource.projectKey>'`);
2690
- }
2691
- else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
2692
- at('AGENTIC_LOCAL_PROJECT_ID_NOT_GUID', `In-solution agentic process step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
2693
- + `the sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
2694
- }
2695
- if (inSolution && !blank(inputs?.folderPath)) {
2696
- at('AGENTIC_LOCAL_HAS_FOLDER', `In-solution agentic process step "${step}" also sets \`folderPath\` ${JSON.stringify(inputs.folderPath)}. An `
2697
- + `in-solution sibling has no folder until the solution is deployed — the platform's own manifest `
2698
- + `writes \`""\` — so the value would be dropped. Remove it, or drop \`location: 'in-solution'\` to `
2699
- + `call the published copy.`);
2700
- }
2701
- const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
2702
- if (name.includes('/')) {
2703
- at('AGENTIC_NAME_IS_PATH', `Agentic process step "${step}"'s \`name\` is ${JSON.stringify(inputs.name)}, which is a folder path. `
2704
- + `\`name\` is the process's own name in Orchestrator; the path it lives in goes in \`folderPath\`. `
2705
- + `Together they form the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
2706
- }
2707
- // The platform's own design-time input, which its serializer strips before the
2708
- // node is emitted — so a process argument by this name is dropped on the way to
2709
- // the tenant and nothing local notices.
2710
- if (inputs?.inputs && Object.prototype.hasOwnProperty.call(inputs.inputs, 'fireAndForget')) {
2711
- at('AGENTIC_RESERVED_INPUT', `Agentic process step "${step}" passes an input called \`fireAndForget\`. That name is RESERVED: it is `
2712
- + `the platform's design-time switch for starting the process without waiting, and its serializer `
2713
- + `removes the key from the node's inputs before emitting — so a process argument of that name never `
2714
- + `arrives. Locally it would appear to work, because this engine passes the whole envelope through. `
2715
- + `Rename the argument, and note that this surface cannot author fire-and-forget at all (the engine `
2716
- + `always waits for the job).`);
2717
- }
2718
- // An empty `returns: {}` is a statement that the process returns nothing, which is
2719
- // legal — but it is almost always a half-finished edit, and the difference between
2720
- // it and a missing `returns` is invisible in the emitted file.
2721
- if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
2722
- diags.push({
2723
- level: 'warning',
2724
- code: 'AGENTIC_EMPTY_RETURNS',
2725
- message: `Agentic process step "${step}" declares \`returns: {}\` — an empty contract, which emits no output `
2726
- + `schema at all and so reads exactly like omitting it. If the process returns nothing, drop the key; `
2727
- + `if it returns something, name the fields.`,
2728
- step,
2729
- });
2730
- }
2731
- }
2732
- /**
2733
- * A read of an agentic process's output: `out('<step>', '<field>')`.
2734
- *
2735
- * `waitForJob` records the job's `OutputArguments` UNWRAPPED under `output`, so the
2736
- * readable fields are exactly the ones `returns` declares. The three mistakes are
2737
- * api-workflow's, and one of them has a sharper edge here: a process may DECLARE a
2738
- * field and return it EMPTY (`ProcurementProcess` declares `{status: boolean}` and
2739
- * answers `{"status": null}` on every job), so a declared read is checkable but a
2740
- * non-empty VALUE is not something any offline gate can promise.
2741
- */
2742
- function checkAgenticProcessRead(name, field, rest, spec, step, diags) {
2743
- if (field === undefined)
2744
- return; // `out('call')` — the whole output object.
2745
- if (field === 'error')
2746
- return; // the error envelope every process node declares.
2747
- if (field !== 'output') {
2748
- diags.push({
2749
- level: 'error',
2750
- code: 'AGENTIC_READ_NOT_OUTPUT',
2751
- message: `Agentic process "${name}" publishes the job's output arguments under "output", but `
2752
- + `"$vars.${name}.${field}" (read in "${step}") asks for "${field}", which is undefined at run time.`,
2753
- step,
2754
- suggestion: `out('${name}', '<field>')`,
2755
- });
2756
- return;
2757
- }
2758
- const declared = Object.keys(spec.returns ?? {});
2759
- const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
2760
- if (!wanted)
2761
- return; // `$vars.<name>.output` — the whole object.
2762
- if (declared.length === 0) {
2763
- diags.push({
2764
- level: 'error',
2765
- code: 'AGENTIC_READ_WITHOUT_RETURNS',
2766
- message: `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of agentic process "${name}"'s `
2767
- + `result, but the step declares no \`returns\` — so the node carries no output schema and nothing can `
2768
- + `tell a real field from a typo. The process's output arguments live on the tenant; declare the ones `
2769
- + `you read. \`uip maestro flow registry get uipath.core.agentic-process.${spec.key}\` shows what `
2770
- + `the platform synthesizes for it.`,
2771
- step,
2772
- suggestion: `returns: { ${wanted}: 'string' }`,
2773
- });
2774
- return;
2775
- }
2776
- if (declared.includes(wanted))
2777
- return;
2778
- const nearest = nearestName(wanted, declared);
2779
- diags.push({
2780
- level: 'error',
2781
- code: 'AGENTIC_READ_UNKNOWN_FIELD',
2782
- message: `Agentic process "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
2783
- + `"${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined on `
2784
- + `every rung and on the platform.`,
2785
- step,
2786
- ...(nearest ? { suggestion: `out('${name}', '${nearest}')` } : {}),
2787
- });
2788
- }
2789
2310
  /**
2790
2311
  * Check an `agent(...)` call — a published or in-solution agent (coded or
2791
2312
  * low-code).