@uipath/maestro-builder-sdk 6.42.0 → 6.44.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.
Files changed (61) hide show
  1. package/dist/api-index.md +104 -103
  2. package/dist/api-members.md +319 -317
  3. package/dist/check.d.ts +0 -19
  4. package/dist/check.js +140 -1042
  5. package/dist/compile-cli.js +2 -1
  6. package/dist/core/actions.d.ts +10 -713
  7. package/dist/core/actions.js +0 -193
  8. package/dist/core/agent-sidecar.d.ts +186 -0
  9. package/dist/core/agent-sidecar.js +409 -0
  10. package/dist/core/agent-tool-resources.d.ts +62 -0
  11. package/dist/core/agent-tool-resources.js +81 -0
  12. package/dist/{config.js → core/connector-configuration.js} +2 -2
  13. package/dist/core/form-field-type.d.ts +7 -0
  14. package/dist/core/form-field-type.js +12 -2
  15. package/dist/core/node-id-placeholder.d.ts +8 -0
  16. package/dist/core/node-id-placeholder.js +10 -0
  17. package/dist/core/ts-literal.d.ts +6 -0
  18. package/dist/core/ts-literal.js +15 -0
  19. package/dist/core/typed-raw-node.js +0 -5
  20. package/dist/decompile.js +239 -857
  21. package/dist/flow-expr-check.js +5 -0
  22. package/dist/flow-node-registry.js +2 -1
  23. package/dist/flow-node-type.d.ts +139 -6
  24. package/dist/flow-sdk.d.ts +41 -5
  25. package/dist/flow-sdk.js +15 -1
  26. package/dist/flow-step-variables.d.ts +4 -0
  27. package/dist/flow-step-variables.js +22 -0
  28. package/dist/loop-version.d.ts +23 -6
  29. package/dist/loop-version.js +37 -15
  30. package/dist/nodes/agent/flow/check.d.ts +88 -0
  31. package/dist/nodes/agent/flow/check.js +873 -0
  32. package/dist/nodes/agent/flow/factories.d.ts +134 -0
  33. package/dist/nodes/agent/flow/factories.js +143 -0
  34. package/dist/nodes/agent/flow/family.d.ts +3 -0
  35. package/dist/nodes/agent/flow/family.js +8 -0
  36. package/dist/nodes/agent/flow/index.d.ts +3 -0
  37. package/dist/nodes/agent/flow/index.js +3 -0
  38. package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
  39. package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
  40. package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
  41. package/dist/nodes/agent/flow/inline-agent.js +526 -0
  42. package/dist/nodes/agent/flow/node-type.d.ts +20 -0
  43. package/dist/nodes/agent/flow/node-type.js +422 -0
  44. package/dist/nodes/agent/flow/resources.d.ts +502 -0
  45. package/dist/nodes/agent/flow/resources.js +1409 -0
  46. package/dist/nodes/agent/flow/spec.d.ts +448 -0
  47. package/dist/nodes/agent/flow/spec.js +1 -0
  48. package/dist/nodes/hitl/flow/check.js +15 -0
  49. package/dist/nodes/hitl/flow/node-type.js +18 -3
  50. package/dist/nodes/process/flow/check.d.ts +40 -2
  51. package/dist/nodes/process/flow/check.js +156 -0
  52. package/dist/nodes/process/flow/factories.d.ts +58 -3
  53. package/dist/nodes/process/flow/factories.js +59 -2
  54. package/dist/nodes/process/flow/node-type.d.ts +4 -1
  55. package/dist/nodes/process/flow/node-type.js +198 -12
  56. package/dist/nodes/process/flow/spec.d.ts +112 -5
  57. package/dist/serialize.d.ts +9 -54
  58. package/dist/serialize.js +102 -2777
  59. package/package.json +2 -2
  60. /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
  61. /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
@@ -4,6 +4,19 @@ import { nearestName } from '../../../core/edit-distance.js';
4
4
  import { ownEntry } from '../../../core/own-entry.js';
5
5
  import { renamedFieldMessage } from '../../../core/process-resource.js';
6
6
  export const PROCESS_DIAGNOSTICS = {
7
+ AGENT_EMPTY_INPUT_FLOOR: { severity: 'warning', summary: 'an agent step that binds no inputs' },
8
+ AGENT_EMPTY_RETURNS: { severity: 'warning', summary: 'an agent step with returns: {}' },
9
+ AGENT_KEY_NOT_GUID: { severity: 'error', summary: 'an agent step whose key is not a GUID' },
10
+ AGENT_LOCAL_NO_PROJECT_ID: { severity: 'error', summary: 'an agent step in-solution, with no projectId' },
11
+ AGENT_LOCAL_PROJECT_ID_NOT_GUID: { severity: 'error', summary: 'an agent step in-solution, with a projectId that is not a GUID' },
12
+ AGENT_NAME_IS_PATH: { severity: 'error', summary: 'an agent step whose name looks like a folder path' },
13
+ AGENT_NO_FOLDER: { severity: 'error', summary: 'an agent step with no folderPath' },
14
+ AGENT_NO_KEY: { severity: 'error', summary: 'an agent step with no key' },
15
+ AGENT_NO_NAME: { severity: 'error', summary: 'an agent step with no name' },
16
+ AGENT_READ_NOT_OUTPUT: { severity: 'error', summary: 'an agent step read outside output' },
17
+ AGENT_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'an agent step read of a field returns does not declare' },
18
+ AGENT_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'an agent step field read with no returns' },
19
+ AGENT_READ_WRONG_CASE: { severity: 'error', summary: 'an agent step read of a declared field in the wrong case' },
7
20
  AGENTIC_EMPTY_RETURNS: { severity: 'warning', summary: 'an agentic process step with returns: {}' },
8
21
  AGENTIC_KEY_NOT_KEY: { severity: 'error', summary: 'an agentic process step whose key is not a GUID' },
9
22
  AGENTIC_LOCAL_FORM_UNVERIFIED: { severity: 'error', summary: 'an agentic process step in-solution, with a form other than flow' },
@@ -442,3 +455,146 @@ export function checkAgenticProcessRead({ node: name, field, rest, reader: step
442
455
  + `"${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined on `
443
456
  + `every rung and on the platform.`, nearest !== undefined ? `out('${name}', '${nearest}')` : undefined);
444
457
  }
458
+ /**
459
+ * Check an `agent(...)` call — a published or in-solution agent (coded or
460
+ * low-code).
461
+ *
462
+ * WHAT THESE RULES CAN AND CANNOT SEE. Whether the agent exists, which kind it is,
463
+ * and what it returns all live on the tenant. So these cover what is decidable from
464
+ * the call — and two of them are the exact classes v1's own baseline on this family
465
+ * fails on, which is why they are errors rather than warnings:
466
+ *
467
+ * - **A reference to nothing.** 5 of 16 `skill-flow-coded-agent` runs in the
468
+ * 06-29 → 07-20 window emitted an agent node whose `resourceKey` was a LOCAL
469
+ * project id and whose `folderPath` was `""`, and faulted at job start with
470
+ * `[170007] … HTTP 404 "The job's associated process could not be found"`. A
471
+ * blank `name` or `folderPath` is that flow, caught before a tenant round trip.
472
+ * - **The empty-input floor.** `inputs` is required by the type, so `{}` is the only
473
+ * way to reach it, and it is worth saying what the platform then does — see
474
+ * `AGENT_EMPTY_INPUT_FLOOR`.
475
+ *
476
+ * What no rule here can see: whether the agent's ANSWER is right. That is CP-3's
477
+ * ruling — a green live rung means a model responded with the declared shape — and
478
+ * the gate that does cover a flow FAKING an answer is an offline `replays:` seed
479
+ * asserting the exact unfixtured value, not a check.
480
+ */
481
+ export function checkAgent(inputs, ctx) {
482
+ const step = ctx.step;
483
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
484
+ const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
485
+ const inSolution = inputs?.location === 'in-solution';
486
+ if (blank(inputs?.key) && !blank(inputs?.agent)) {
487
+ at('AGENT_NO_KEY', renamedFieldMessage('Agent', step, 'agent'), `key: ${JSON.stringify(inputs.agent)}`);
488
+ }
489
+ else if (blank(inputs?.key)) {
490
+ at('AGENT_NO_KEY', `Agent step "${step}" has no \`key\`. It is a GUID and becomes part of the node's `
491
+ + `type — without it there is no agent to start. For a published agent, find the Orchestrator `
492
+ + `release with \`uip or processes list --all-folders --limit 200\` (take the row's \`Key\`) or `
493
+ + `\`uip maestro flow registry search uipath.core.agent\`. For an in-solution agent, read `
494
+ + `\`resource.key\` from \`resources/solution_folder/process/agent/<project>.json\`.`, `key: '6a47dc5b-5fd4-461f-b97d-6c1817abcaaa'`);
495
+ }
496
+ else if (!GUID_RE.test(String(inputs.key).trim())) {
497
+ at('AGENT_KEY_NOT_GUID', `Agent step "${step}"'s \`key\` is ${JSON.stringify(inputs.key)}, which is not a key. \`key\` is `
498
+ + `the GUID the tenant gives the published agent (it becomes the node type's suffix); the agent's NAME `
499
+ + `goes in \`name\` and its folder in \`folderPath\`. Passing a ProcessKey like `
500
+ + `"CountLetters.CodedAgent.agent.CountLetters" here is the common form of this — that string is the `
501
+ + `row's \`ProcessKey\`, not its \`Key\`.`, `key: '<guid>', name: ${JSON.stringify(inputs.key)}`);
502
+ }
503
+ if (blank(inputs?.name)) {
504
+ at('AGENT_NO_NAME', `Agent step "${step}" has no \`name\`. The runtime resolves the agent's release by name + folder, not `
505
+ + `by the key in the node type, so without it the job cannot start — deployed, that is `
506
+ + `\`[170007] … 404 The job's associated process could not be found\`.`, `name: 'CountLetters LowCode Agent'`);
507
+ }
508
+ if (!inSolution && blank(inputs?.folderPath)) {
509
+ at('AGENT_NO_FOLDER', `Agent step "${step}" has no \`folderPath\`. The runtime resolves the agent by listing ONE Orchestrator `
510
+ + `folder, so a blank folder resolves nothing — and an empty \`folderPath\` is the exact shape of v1's `
511
+ + `commonest live failure on this node type (5 of 16 runs, 06-29 → 07-20: `
512
+ + `\`[170007] … 404 The job's associated process could not be found\`). It is also what disambiguates `
513
+ + `two same-named agents: this tenant has \`CountLetters\` in both \`Shared\` and `
514
+ + `\`Shared/uipath-maestro-flow/CountLetters CodedAgent\`.`, `folderPath: 'Shared/uipath-maestro-flow/CountLetters LowCode'`);
515
+ }
516
+ if (inSolution && blank(inputs?.projectId)) {
517
+ at('AGENT_LOCAL_NO_PROJECT_ID', `In-solution agent step "${step}" has no \`projectId\`. Read \`resource.projectKey\` from `
518
+ + `\`resources/solution_folder/process/agent/<project>.json\` after registering the sibling `
519
+ + `with \`uip solution projects add\`.`, `projectId: '<resource.projectKey>'`);
520
+ }
521
+ else if (inSolution && !GUID_RE.test(String(inputs.projectId).trim())) {
522
+ at('AGENT_LOCAL_PROJECT_ID_NOT_GUID', `In-solution agent step "${step}" has projectId ${JSON.stringify(inputs.projectId)}, which is not `
523
+ + `the registered sibling project's GUID. Read \`resource.projectKey\` from the generated resource file.`, `projectId: '<resource.projectKey>'`);
524
+ }
525
+ const name = typeof inputs?.name === 'string' ? inputs.name.trim() : '';
526
+ if (name.includes('/')) {
527
+ at('AGENT_NAME_IS_PATH', `Agent step "${step}"'s \`name\` is ${JSON.stringify(name)}, which looks like a folder path. \`name\` is `
528
+ + `the agent's own name in Orchestrator; the path it lives in goes in \`folderPath\`. Together they form `
529
+ + `the resourceKey "<folderPath>.<name>", so swapping them points the job at nothing.`);
530
+ }
531
+ // The platform's SECOND place for rewriting an agent node's inputs behind the
532
+ // author's back (the first is the reserved-key class — see AGENTIC_RESERVED_INPUT).
533
+ // A warning rather than an error: an argument-less agent is legal, and the floor
534
+ // makes it work. What is not acceptable is that it happen silently.
535
+ if (inputs?.inputs && Object.keys(inputs.inputs).length === 0) {
536
+ at('AGENT_EMPTY_INPUT_FLOOR', `Agent step "${step}" binds no inputs. The platform does not send an empty body for an agent the way `
537
+ + `it does for the other job families — flow-v1's \`applyEmptyInputFloor\` seeds `
538
+ + `\`JobArguments: { input: "" }\` (or \`{}\` / \`[]\`, typed off the agent's declared \`input\` field), `
539
+ + `because the Python agent runner validates the body against a schema with a REQUIRED \`input\` field `
540
+ + `and rejects a bare \`{}\` with "Field required: input" — the pod then exits 0x33. So the deployed `
541
+ + `node carries an argument named \`input\` that you did not write, and which is not in the agent's own `
542
+ + `schema unless it happens to declare one. Locally the envelope passes straight through, so no rung `
543
+ + `sees the difference. Bind the agent's own arguments if it takes any.`);
544
+ }
545
+ if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
546
+ at('AGENT_EMPTY_RETURNS', `Agent step "${step}" declares \`returns: {}\` — an empty contract, which emits no output schema at `
547
+ + `all and so reads exactly like omitting it. If the agent returns nothing, drop the key; if it `
548
+ + `returns something, name the fields (\`uip maestro flow registry get uipath.core.agent.`
549
+ + `${inputs.key}\` shows them under outputDefinition.output.schema.properties).`);
550
+ }
551
+ }
552
+ /**
553
+ * A read of a published agent's output: `out('<step>', '<field>')`.
554
+ *
555
+ * The three mistakes are api-workflow's and agentic-process's, plus a fourth this
556
+ * family owns: a read whose CASE differs from the declared field. That is worth its
557
+ * own message because for a while it was a live-only defect rather than a mistake —
558
+ * a poller that read the job through `uip agent run status` got a PascalCased
559
+ * envelope, so `output.Count` was right live and wrong offline while
560
+ * `output.count` was the reverse (flow-builder-sdk #122). Now the agent's own field
561
+ * names arrive verbatim and the wrong case is simply undefined; naming it as a CASE
562
+ * problem rather than an unknown field is the difference between a two-second fix and
563
+ * a re-read of the registry.
564
+ */
565
+ export function checkAgentRead({ node: name, field, rest, reader: step }, spec, report) {
566
+ if (field === undefined)
567
+ return; // `out('call')` — the whole output object.
568
+ if (field === 'error')
569
+ return; // the error envelope every agent node declares.
570
+ if (field !== 'output') {
571
+ report('AGENT_READ_NOT_OUTPUT', `Agent "${name}" publishes the job's output arguments under "output", but `
572
+ + `"$vars.${name}.${field}" (read in "${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', '<field>')`);
573
+ return;
574
+ }
575
+ const declared = Object.keys(spec.returns ?? {});
576
+ const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
577
+ if (!wanted)
578
+ return; // `$vars.<name>.output` — the whole object.
579
+ if (declared.length === 0) {
580
+ report('AGENT_READ_WITHOUT_RETURNS', `"$vars.${name}.output.${wanted}" (read in "${step}") reads a field of agent "${name}"'s result, but `
581
+ + `the step declares no \`returns\` — so the node carries no output schema and nothing can tell a real `
582
+ + `field from a typo. The agent's output fields live on the tenant; declare the ones you read. `
583
+ + `\`uip maestro flow registry get uipath.core.agent.${spec.key}\` shows them under `
584
+ + `outputDefinition.output.schema.properties.`, `returns: { ${wanted}: 'string' }`);
585
+ return;
586
+ }
587
+ if (declared.includes(wanted))
588
+ return;
589
+ const caseMatch = declared.find((d) => d.toLowerCase() === wanted.toLowerCase());
590
+ if (caseMatch) {
591
+ report('AGENT_READ_WRONG_CASE', `Agent "${name}" declares "${caseMatch}", not "${wanted}" (read as "$vars.${name}.output.${wanted}" `
592
+ + `in "${step}") — the same field, the wrong case. An agent answers with the field names its own `
593
+ + `definition declares, and a read is case-sensitive on every rung, so this is undefined everywhere.`, `out('${name}', '${caseMatch}')`);
594
+ return;
595
+ }
596
+ const nearest = nearestName(wanted, declared);
597
+ report('AGENT_READ_UNKNOWN_FIELD', `Agent "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in "${step}"). `
598
+ + `Its \`returns\` names: ${declared.join(', ')}. A read outside that set is undefined on every rung and `
599
+ + `on the platform.`, nearest ? `out('${name}', '${nearest}')` : undefined);
600
+ }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * The process factories — `rpaWorkflow()`, `apiWorkflow()`, `publishedFunction()`
3
- * and `agenticProcess()`. Moved from core/actions.ts in Phase 4 of
2
+ * The process factories — `rpaWorkflow()`, `apiWorkflow()`, `publishedFunction()`,
3
+ * `agenticProcess()` and `agent()`. Moved from core/actions.ts in Phase 4 of
4
4
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
5
5
  */
6
6
  import { type ActionSpec } from '../../../core/actions.js';
7
- import type { AgenticProcessInputs, ApiWorkflowInputs, PublishedFunctionInputs, RpaWorkflowInputs } from './spec.js';
7
+ import type { AgentInputs, AgenticProcessInputs, ApiWorkflowInputs, PublishedFunctionInputs, RpaWorkflowInputs } from './spec.js';
8
8
  /**
9
9
  * Invoke a deployed RPA (robotic process automation) workflow — a classic
10
10
  * UI-automation process, published to Orchestrator, run by a robot.
@@ -156,3 +156,58 @@ export declare function publishedFunction(inputs: PublishedFunctionInputs): Acti
156
156
  * @returns An action spec to pass to `.step(name, …)`.
157
157
  */
158
158
  export declare function agenticProcess(inputs: AgenticProcessInputs): ActionSpec;
159
+ /**
160
+ * Invoke an AGENT resource — either one published to Orchestrator or a sibling
161
+ * agent project registered in the same solution. Both start through
162
+ * `Orchestrator.StartAgentJob`.
163
+ *
164
+ * @remarks
165
+ * **Coded and low-code agents are the same node.** This one factory authors both.
166
+ * `registry get` on a coded agent and on a low-code one returns the same node type,
167
+ * the same service type, the same `sortOrder` and the same bindings shape; the
168
+ * only difference is the icon, which is what `flavour` sets. Which kind a resource
169
+ * is a property of what somebody published, not of the flow — so if a task asks for
170
+ * "a coded agent", what it is asking you to get right is the RESOURCE you point at.
171
+ *
172
+ * **What this factory cannot do, said plainly:** it does not create an agent. A
173
+ * published resource must already be deployed. An in-solution resource must first
174
+ * be scaffolded and registered with `uip solution projects add`; this factory then
175
+ * authors the local reference from the registered resource key and project id.
176
+ *
177
+ * The three identifiers are separate for `rpaWorkflow`'s reason: `agent` is the key
178
+ * the node's TYPE carries (`uipath.core.agent.<agent>`), while `name` +
179
+ * `folderPath` are what the runtime resolves the job by and together are the
180
+ * `resourceKey`, `"<folderPath>.<name>"`. `folderPath` earns its keep twice here —
181
+ * see {@link AgentInputs.folderPath} for the two-agents-called-CountLetters case.
182
+ *
183
+ * The step WAITS for the job and publishes its output arguments under `output`, so
184
+ * `out('<step>', '<field>')` reads a field `returns` declares — using the names the
185
+ * agent's own definition declares, case included.
186
+ *
187
+ * ── the live bar, stated ─────────────────────────────────────────────────────────
188
+ * A green live rung here means **a model responded with the declared output shape**,
189
+ * not that the answer was right. A real Orchestrator job runs, the flow's input
190
+ * reaches it, and its answer reaches the flow's output — all four independently
191
+ * checkable (`ladder.sh job`). Whether the number it returned is the correct number
192
+ * is the model's business, and no rung asserts it. What CAN be asserted is that the
193
+ * flow did not fake it: the offline seeds assert the exact UNFIXTURED value, which a
194
+ * hardcoded answer cannot produce.
195
+ *
196
+ * @example
197
+ * **Start a published agent and read one declared output**
198
+ * ```ts
199
+ * .step('countLetters', agent({
200
+ * key: '6a47dc5b-5fd4-461f-b97d-6c1817abcaaa',
201
+ * name: 'CountLetters LowCode Agent',
202
+ * folderPath: 'Shared/uipath-maestro-flow/CountLetters LowCode',
203
+ * inputs: { inputString: input('word') },
204
+ * returns: { count: 'integer', inputString: 'string' },
205
+ * }))
206
+ * .return({ rs: out('countLetters', 'count') })
207
+ * ```
208
+ *
209
+ * @param inputs - The agent to start (`name`, `folderPath`, and `key` or
210
+ * `projectId`), its `inputs`, and the fields you read back via `returns`.
211
+ * @returns An action spec to pass to `.step(name, …)`.
212
+ */
213
+ export declare function agent(inputs: AgentInputs): ActionSpec;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The process factories — `rpaWorkflow()`, `apiWorkflow()`, `publishedFunction()`
3
- * and `agenticProcess()`. Moved from core/actions.ts in Phase 4 of
2
+ * The process factories — `rpaWorkflow()`, `apiWorkflow()`, `publishedFunction()`,
3
+ * `agenticProcess()` and `agent()`. Moved from core/actions.ts in Phase 4 of
4
4
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
5
5
  */
6
6
  import { builtinActionSpec } from '../../../core/actions.js';
@@ -171,3 +171,60 @@ export function agenticProcess(inputs) {
171
171
  }
172
172
  return builtinActionSpec({ kind: 'agenticProcess', inputs });
173
173
  }
174
+ /**
175
+ * Invoke an AGENT resource — either one published to Orchestrator or a sibling
176
+ * agent project registered in the same solution. Both start through
177
+ * `Orchestrator.StartAgentJob`.
178
+ *
179
+ * @remarks
180
+ * **Coded and low-code agents are the same node.** This one factory authors both.
181
+ * `registry get` on a coded agent and on a low-code one returns the same node type,
182
+ * the same service type, the same `sortOrder` and the same bindings shape; the
183
+ * only difference is the icon, which is what `flavour` sets. Which kind a resource
184
+ * is a property of what somebody published, not of the flow — so if a task asks for
185
+ * "a coded agent", what it is asking you to get right is the RESOURCE you point at.
186
+ *
187
+ * **What this factory cannot do, said plainly:** it does not create an agent. A
188
+ * published resource must already be deployed. An in-solution resource must first
189
+ * be scaffolded and registered with `uip solution projects add`; this factory then
190
+ * authors the local reference from the registered resource key and project id.
191
+ *
192
+ * The three identifiers are separate for `rpaWorkflow`'s reason: `agent` is the key
193
+ * the node's TYPE carries (`uipath.core.agent.<agent>`), while `name` +
194
+ * `folderPath` are what the runtime resolves the job by and together are the
195
+ * `resourceKey`, `"<folderPath>.<name>"`. `folderPath` earns its keep twice here —
196
+ * see {@link AgentInputs.folderPath} for the two-agents-called-CountLetters case.
197
+ *
198
+ * The step WAITS for the job and publishes its output arguments under `output`, so
199
+ * `out('<step>', '<field>')` reads a field `returns` declares — using the names the
200
+ * agent's own definition declares, case included.
201
+ *
202
+ * ── the live bar, stated ─────────────────────────────────────────────────────────
203
+ * A green live rung here means **a model responded with the declared output shape**,
204
+ * not that the answer was right. A real Orchestrator job runs, the flow's input
205
+ * reaches it, and its answer reaches the flow's output — all four independently
206
+ * checkable (`ladder.sh job`). Whether the number it returned is the correct number
207
+ * is the model's business, and no rung asserts it. What CAN be asserted is that the
208
+ * flow did not fake it: the offline seeds assert the exact UNFIXTURED value, which a
209
+ * hardcoded answer cannot produce.
210
+ *
211
+ * @example
212
+ * **Start a published agent and read one declared output**
213
+ * ```ts
214
+ * .step('countLetters', agent({
215
+ * key: '6a47dc5b-5fd4-461f-b97d-6c1817abcaaa',
216
+ * name: 'CountLetters LowCode Agent',
217
+ * folderPath: 'Shared/uipath-maestro-flow/CountLetters LowCode',
218
+ * inputs: { inputString: input('word') },
219
+ * returns: { count: 'integer', inputString: 'string' },
220
+ * }))
221
+ * .return({ rs: out('countLetters', 'count') })
222
+ * ```
223
+ *
224
+ * @param inputs - The agent to start (`name`, `folderPath`, and `key` or
225
+ * `projectId`), its `inputs`, and the fields you read back via `returns`.
226
+ * @returns An action spec to pass to `.step(name, …)`.
227
+ */
228
+ export function agent(inputs) {
229
+ return builtinActionSpec({ kind: 'agent', inputs });
230
+ }
@@ -1,6 +1,6 @@
1
1
  import { type ProcessResourceDef } from '../../../core/process-resource.js';
2
2
  import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
3
- import type { AgenticProcessInputs, ApiWorkflowInputs, PublishedFunctionInputs, RpaWorkflowInputs } from './spec.js';
3
+ import type { AgentInputs, AgenticProcessInputs, ApiWorkflowInputs, PublishedFunctionInputs, RpaWorkflowInputs } from './spec.js';
4
4
  type ProcessSpec = {
5
5
  kind: 'rpaWorkflow';
6
6
  inputs: RpaWorkflowInputs;
@@ -13,6 +13,9 @@ type ProcessSpec = {
13
13
  } | {
14
14
  kind: 'agenticProcess';
15
15
  inputs: AgenticProcessInputs;
16
+ } | {
17
+ kind: 'agent';
18
+ inputs: AgentInputs;
16
19
  };
17
20
  /**
18
21
  * The definition a process step's node is emitted against — what serialize
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * The process family's node types — one per published-resource kind this SDK
3
- * starts as a job: an RPA process, an API workflow, a published function, and an
4
- * agentic process in each of its three forms. Each type's name carries the
5
- * resource's key (`uipath.core.function.<key>`), so the type owns every name under
6
- * its prefix (`typeNamePrefix`) and supplies the definition it built, transcribed
7
- * from the platform's own synthesized one (per-tenant). Moved from serialize.ts /
8
- * check.ts / decompile.ts in Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md;
9
- * serialized output, printed source, codes and messages unchanged.
3
+ * starts as a job: an RPA process, an API workflow, a published function, an
4
+ * agentic process in each of its three forms, and a published agent. Each type's
5
+ * name carries the resource's key (`uipath.core.function.<key>`), so the type owns
6
+ * every name under its prefix (`typeNamePrefix`) and supplies the definition it
7
+ * built, transcribed from the platform's own synthesized one (per-tenant).
8
+ * Moved from serialize.ts / check.ts / decompile.ts in Phase 4 of
9
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; codes and messages unchanged, and
10
+ * printed source and serialized output unchanged except as the design doc's 4j
11
+ * and 4l entries list.
10
12
  */
11
13
  import { AGENTIC_PROCESS_FORMS } from '../../../core/agentic-process-forms.js';
12
14
  import { definitionHasErrorPort } from '../../../core/error-port.js';
@@ -14,7 +16,7 @@ import { Expr } from '../../../core/expr.js';
14
16
  import { ownEntry } from '../../../core/own-entry.js';
15
17
  import { resourceNodeSortOrder } from '../../../core/process-resource-sort-order.js';
16
18
  import { processResourceBindings, processResourceLocation } from '../../../core/process-resource.js';
17
- import { checkAgenticProcess, checkAgenticProcessRead, checkApiWorkflow, checkApiWorkflowRead, checkPublishedFunction, checkPublishedFunctionRead, checkRpaWorkflow, rpaWorkflowRead, } from './check.js';
19
+ import { checkAgent, checkAgentRead, checkAgenticProcess, checkAgenticProcessRead, checkApiWorkflow, checkApiWorkflowRead, checkPublishedFunction, checkPublishedFunctionRead, checkRpaWorkflow, rpaWorkflowRead, } from './check.js';
18
20
  /** The RPA-workflow node type family. The process's release key is the suffix. */
19
21
  const RPA_WORKFLOW_PREFIX = 'uipath.core.rpa-workflow.';
20
22
  /**
@@ -453,6 +455,141 @@ function agenticProcessDef(spec) {
453
455
  debug: { runtime: 'bpmnEngine' },
454
456
  };
455
457
  }
458
+ /** The published-agent node type family. The agent's release key is the suffix. */
459
+ const AGENT_PREFIX = 'uipath.core.agent.';
460
+ /**
461
+ * Build the definition for one published agent — coded or low-code, which are the
462
+ * SAME node.
463
+ *
464
+ * Unbundlable for rpa's, api-workflow's and agentic-process's reason: the node type
465
+ * carries a tenant's release key and the platform synthesizes the contract from
466
+ * whatever somebody published there. The constants below are transcribed from what
467
+ * the platform's oracles AGREE on, and this family is the first in the wave with
468
+ * BOTH a rich registry answer and a large deployed corpus, so the agreement is
469
+ * checkable rather than asserted:
470
+ *
471
+ * - `uip maestro flow registry get uipath.core.agent.<key>` for ALL NINE agents on
472
+ * the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505` (510 since flow-core's
473
+ * renumber: the 54 runs from 09-01 below carry 510; `resourceNodeSortOrder`, #1045),
474
+ * `supportsErrorHandling: true`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
475
+ * `Orchestrator.StartAgentJob` / `v2` model, `section: 'Published'`,
476
+ * `resourceSubType: 'Agent'`, `orchestratorType: 'agent'`,
477
+ * `debug.runtime: 'bpmnEngine'`, `runtimeConstraints.exclude: ['api-function']`,
478
+ * and the `<bindings.name>` / `<bindings.folderPath>` context placeholders;
479
+ * - **61 deployed, debug-green agent instances** across 61 dated
480
+ * `skill-flow-{coded,lowcode}-agent` artifacts (05-18 → 07-29): `sortOrder: 505`
481
+ * in 61/61, the `<folderPath>.<name>` resourceKey in 58/61 (the 3 exceptions are
482
+ * the in-solution shape — a bare key and an EMPTY folderPath — which 404'd at
483
+ * job start in those runs; that is the shape of `location: 'in-solution'`,
484
+ * built by `processResourceLocation`, and it starts the job when the sibling is
485
+ * in the solution: 54 v1 `skill-flow-coded-agent` runs, 09-01 → 10-08, carry
486
+ * `isInSolution: true`, 45 score 1.0 and none of the other 9 fails on a
487
+ * job-start 404, #1012), and the descriptor
488
+ * ARRAY `bindings.values` in 59/61 (2 early runs used the object form);
489
+ * - flow-v1's own generator, which is where those come from:
490
+ * `node-manifest-builder.ts::createModel` hard-codes `bpmn:ServiceTask` for every
491
+ * published-resource node and derives `orchestratorType` as
492
+ * `resourceType.toLowerCase()`.
493
+ *
494
+ * `shared/sample-flows/agent.flow` is explicitly NOT an oracle here: dated
495
+ * 2026-05-12, it disagrees with both of the above in five places at once
496
+ * (`sortOrder: 530`, a bare-GUID resourceKey, an object-shaped `bindings.values`,
497
+ * and `=result.response` / `=result.Error` output sources). PR 11's rule — date a
498
+ * bundled sample before trusting the layer that copied it — and this is its second
499
+ * instance on that file.
500
+ *
501
+ * `outputs.output.source` is `=this`, which is what the definition declares. The
502
+ * corpus is split (`=result.response` 45, `=this` 14, both deploying green), and the
503
+ * split has a mechanical explanation: flow-v1's `applyOrchestratorActivityOutputs`
504
+ * APPENDS `{name:'output', source:'=this'}` when a node declares no `output`, so
505
+ * `=this` is what the platform writes for itself. We emit that one.
506
+ *
507
+ * The ONLY field that differs between a coded and a low-code agent is
508
+ * `display.icon` — which is why one factory serves both.
509
+ */
510
+ function agentDef(spec) {
511
+ // One location slot with the other process resources: the in-solution
512
+ // model carries `projectId`, `projectName` AND `isInSolution: true`, as
513
+ // flow-core's `createModel` writes for every sibling-project resource.
514
+ const loc = processResourceLocation(spec);
515
+ const { inSolution, resourceKey } = loc;
516
+ const properties = {};
517
+ for (const [k, v] of Object.entries(spec.inputs)) {
518
+ properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
519
+ }
520
+ return {
521
+ nodeType: `${AGENT_PREFIX}${spec.key}`,
522
+ version: '1.0.0',
523
+ category: 'agent',
524
+ runtimeConstraints: { exclude: ['api-function'] },
525
+ description: loc.description,
526
+ tags: loc.tags,
527
+ sortOrder: resourceNodeSortOrder('agent'),
528
+ supportsErrorHandling: true,
529
+ display: {
530
+ label: spec.name,
531
+ icon: spec.flavour === 'coded' ? 'coded-agent' : 'autonomous-agent',
532
+ },
533
+ handleConfiguration: [
534
+ { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
535
+ { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
536
+ ],
537
+ ...loc.definitionExtras,
538
+ model: {
539
+ type: 'bpmn:ServiceTask',
540
+ serviceType: 'Orchestrator.StartAgentJob',
541
+ version: 'v2',
542
+ ...(inSolution ? {} : { section: 'Published' }),
543
+ bindings: {
544
+ resource: 'process',
545
+ resourceSubType: 'Agent',
546
+ resourceKey,
547
+ orchestratorType: 'agent',
548
+ values: [
549
+ { name: 'name', propertyAttribute: 'name', default: spec.name },
550
+ { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
551
+ ],
552
+ },
553
+ ...loc.modelExtras,
554
+ context: [
555
+ { name: 'name', type: 'string', value: '<bindings.name>' },
556
+ { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
557
+ { name: '_label', type: 'string', value: spec.name },
558
+ ],
559
+ },
560
+ inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
561
+ inputDefaults: inSolution
562
+ ? Object.fromEntries(Object.keys(properties).map((name) => [name, '']))
563
+ : {},
564
+ outputDefinition: {
565
+ output: {
566
+ type: 'object',
567
+ source: '=this',
568
+ var: 'output',
569
+ // The agent's output fields as the author declared them — the same shape
570
+ // `registry get` returns when it can reach the deployed agent (nested under
571
+ // `schema`, which is the spelling gen-tests reads). Without it a downstream
572
+ // read is unresolvable, rightly: nothing offline could tell a real field from a
573
+ // typo, and this is also what fixtures the node offline.
574
+ ...(spec.returns && Object.keys(spec.returns).length
575
+ ? {
576
+ schema: {
577
+ type: 'object',
578
+ properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
579
+ },
580
+ }
581
+ : {}),
582
+ },
583
+ error: {
584
+ type: 'object',
585
+ description: 'Error information if the node fails',
586
+ source: '=Error',
587
+ var: 'error',
588
+ },
589
+ },
590
+ debug: { runtime: 'bpmnEngine' },
591
+ };
592
+ }
456
593
  /**
457
594
  * The definition a process step's node is emitted against — what serialize
458
595
  * supplies and what `errorPort` asks, so the two cannot disagree (#1040).
@@ -474,10 +611,15 @@ export function processDefinition(spec) {
474
611
  key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
475
612
  inputs: s.inputs ?? {}, returns: s.returns,
476
613
  });
614
+ case 'agent':
615
+ return agentDef({
616
+ key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
617
+ inputs: s.inputs ?? {}, returns: s.returns, flavour: s.flavour,
618
+ });
477
619
  }
478
620
  }
479
621
  /**
480
- * One emit path for the four process-resource kinds: they differ in the
622
+ * One emit path for the five process-resource kinds: they differ in the
481
623
  * definition they carry (node type, serviceType, subtype, the key's field name)
482
624
  * and in nothing else — the node body, the binding pair and the model-stripping
483
625
  * are identical, and the DATA paths that differ are all downstream in the engine.
@@ -518,6 +660,27 @@ function serializeProcess(spec, ctx) {
518
660
  ...(outputs !== undefined ? { outputs, replaceOutputs: true } : {}),
519
661
  };
520
662
  }
663
+ /**
664
+ * Read back the expression template older SDK builds emitted on an in-solution
665
+ * `agent()` input (#1013): `{ type: 'literal', expression: '{{ <js> }}' }`.
666
+ * serialize now emits the platform's `{ type: 'jsExpression', expression }`
667
+ * instead, but `.flow` files written between 2026-08-10 and the fix still carry
668
+ * the template, and the runtime evaluates it (the agent receives the value, not
669
+ * the braces). So on an in-solution agent node a literal that is exactly one
670
+ * `{{ … }}` is that expression, and it decompiles to the reference it was
671
+ * written from rather than to a string. Anything else is returned unchanged.
672
+ */
673
+ function legacyAgentTemplateInput(v) {
674
+ if (!v || typeof v !== 'object' || Array.isArray(v))
675
+ return v;
676
+ const o = v;
677
+ if (o.type !== 'literal' || typeof o.expression !== 'string')
678
+ return v;
679
+ const m = /^\{\{\s*([\s\S]+?)\s*\}\}$/.exec(o.expression);
680
+ if (!m || m[1].includes('}}') || m[1].includes('{{'))
681
+ return v;
682
+ return { ...o, type: 'jsExpression', expression: m[1] };
683
+ }
521
684
  /** A value as the author wrote it: an expression back to an Expr, a literal string as itself, any other JSON as is. */
522
685
  function lifted(raw, ctx) {
523
686
  const text = Array.isArray(raw) ? undefined : ctx.sourceText(raw);
@@ -554,7 +717,27 @@ function liftProcess(node, ctx, prefix, kind, form) {
554
717
  // author's map, so it comes back off here.
555
718
  const fireAndForget = ctx.plainValue(inputs.fireAndForget) === true;
556
719
  delete inputs.fireAndForget;
720
+ if (kind === 'agent' && (def?.model?.isInSolution === true || def?.model?.projectId !== undefined)) {
721
+ for (const [k, v] of Object.entries(inputs))
722
+ inputs[k] = legacyAgentTemplateInput(v);
723
+ }
557
724
  const schema = def?.outputDefinition?.output?.schema?.properties;
725
+ const returns = schema && Object.keys(schema).length > 0
726
+ ? Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')]))
727
+ : undefined;
728
+ // The designer writes the process's output schema on the definition whether
729
+ // or not the step waits for it. `agenticProcess()` refuses `returns` beside
730
+ // `completion: 'fire-and-forget'` (a dispatch publishes no output, so nothing
731
+ // could read one), so the schema is not carried into source — said, not
732
+ // dropped silently (#1312). Merge keeps the original's definition row, so a
733
+ // no-edit round trip loses nothing in the file.
734
+ if (fireAndForget && returns !== undefined) {
735
+ const id = String(node.id ?? '');
736
+ ctx.lossy(`process step ${JSON.stringify(id)} is fire-and-forget, so the ${Object.keys(returns).length} output `
737
+ + `field(s) its definition declares (${Object.keys(returns).join(', ')}) are not written as \`returns\`: `
738
+ + `a fire-and-forget dispatch publishes no output, and agenticProcess() refuses the pair. Compile writes the `
739
+ + `definition without that schema. To read the process result, set completion: 'wait' and declare \`returns\`.`);
740
+ }
558
741
  const spec = {
559
742
  key,
560
743
  ...(name !== undefined ? { name } : {}),
@@ -562,8 +745,7 @@ function liftProcess(node, ctx, prefix, kind, form) {
562
745
  ...(!inSolution && folderPath !== undefined ? { folderPath } : {}),
563
746
  ...(inSolution && projectId !== undefined ? { projectId } : {}),
564
747
  ...(Object.keys(inputs).length ? { inputs: Object.fromEntries(Object.entries(inputs).map(([k, v]) => [k, lifted(v, ctx)])) } : {}),
565
- ...(schema && Object.keys(schema).length > 0
566
- ? { returns: Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')])) } : {}),
748
+ ...(returns !== undefined && !fireAndForget ? { returns } : {}),
567
749
  ...(form !== undefined ? { form } : {}),
568
750
  ...(fireAndForget ? { completion: 'fire-and-forget' } : {}),
569
751
  };
@@ -605,6 +787,8 @@ function processOutput(spec) {
605
787
  return { kind: 'reader', read: (read, report) => checkPublishedFunctionRead(read, spec.inputs, report) };
606
788
  case 'agenticProcess':
607
789
  return { kind: 'reader', read: (read, report) => checkAgenticProcessRead(read, spec.inputs, report) };
790
+ case 'agent':
791
+ return { kind: 'reader', read: (read, report) => checkAgentRead(read, spec.inputs, report) };
608
792
  }
609
793
  }
610
794
  function processCheck(spec, ctx) {
@@ -613,13 +797,14 @@ function processCheck(spec, ctx) {
613
797
  case 'apiWorkflow': return checkApiWorkflow(spec.inputs, ctx);
614
798
  case 'publishedFunction': return checkPublishedFunction(spec.inputs, ctx);
615
799
  case 'agenticProcess': return checkAgenticProcess(spec.inputs, ctx);
800
+ case 'agent': return checkAgent(spec.inputs, ctx);
616
801
  }
617
802
  }
618
803
  function processType(kind, prefix, factory, form) {
619
804
  const impl = {
620
805
  kind, typeName: `${prefix}*`, version: '1.0.0', factory, definition: 'per-tenant',
621
806
  // The definition this SDK synthesizes is the one serialize emits, and every
622
- // one of the four sets supportsErrorHandling; asking it keeps the two in step (#1040).
807
+ // one of the five sets supportsErrorHandling; asking it keeps the two in step (#1040).
623
808
  errorPort: (spec) => definitionHasErrorPort(processDefinition(spec)),
624
809
  serialize: (spec, ctx) => serializeProcess(spec, ctx),
625
810
  deserialize: (node, ctx) => liftProcess(node, ctx, prefix, kind, form),
@@ -649,4 +834,5 @@ export const processNodeTypes = [
649
834
  processType('agenticProcess', AGENTIC_PROCESS_FORMS.bpmn.prefix, 'agenticProcess'),
650
835
  processType('agenticProcess', AGENTIC_PROCESS_FORMS.flow.prefix, 'agenticProcess', 'flow'),
651
836
  processType('agenticProcess', AGENTIC_PROCESS_FORMS.case.prefix, 'agenticProcess', 'case'),
837
+ processType('agent', AGENT_PREFIX, 'agent'),
652
838
  ];