@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/serialize.js CHANGED
@@ -62,8 +62,8 @@ import { outputBackingGlobal, planOutputBacking } from './flow-output-backing.js
62
62
  import { mapJsCode } from './core/js-code-spans.js';
63
63
  import { contributionPort } from './core/definition-handles.js';
64
64
  import { prepareCommand } from './core/cli-spelling.js';
65
- import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
66
65
  import { resourceNodeSortOrder } from './core/process-resource-sort-order.js';
66
+ import { processResourceBindings, processResourceLocation } from './core/process-resource.js';
67
67
  import { types } from './core/expr.js';
68
68
  import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
69
69
  import { ownEntry } from './core/own-entry.js';
@@ -126,491 +126,6 @@ const SUBFLOW_OUTPUT = {
126
126
  source: '=result.response',
127
127
  var: 'output',
128
128
  };
129
- /** The RPA-workflow node type family. The process's release key is the suffix. */
130
- const RPA_WORKFLOW_PREFIX = 'uipath.core.rpa-workflow.';
131
- /**
132
- * Where a process resource lives, as ONE slot every process-resource definition
133
- * shares. In the platform model "in this solution" is orthogonal to the
134
- * resource type: flow-core's single `createModel` (`@uipath/flow-core`
135
- * `manifest/mappers/node-manifest-builder.ts`, what `uip maestro flow registry
136
- * get --local` and Studio Web both run) adds `projectId`, `projectName` and
137
- * `isInSolution: true` to the model of ANY process resource built from a sibling
138
- * project.
139
- *
140
- * The in-solution shape, transcribed from `registry get --local` for a sibling
141
- * Flow (2026-10-08) and from uipath-cli's `buildInSolutionNodeManifests`:
142
- * - `resourceKey` is the bare key (the sibling's `resource.key`), not
143
- * `<folderPath>.<name>`; the folder default is `''`;
144
- * - no `section: 'Published'`;
145
- * - `projectId` on the definition AND on the model, with `projectName` and
146
- * `isInSolution: true` (the marker the canvas reads);
147
- * - description `(In-solution) <name>`, tags `in-solution` / `local`, and the
148
- * `open-workflow` toolbar action.
149
- */
150
- function processResourceLocation(spec) {
151
- if (spec.location === 'in-solution') {
152
- return {
153
- inSolution: true,
154
- folderPath: '',
155
- resourceKey: spec.key,
156
- description: `(In-solution) ${spec.name}`,
157
- tags: ['in-solution', 'local'],
158
- definitionExtras: {
159
- toolbarExtensions: {
160
- design: { actions: [{ id: 'open-workflow', icon: 'external-link', label: 'Open' }] },
161
- },
162
- projectId: spec.projectId,
163
- },
164
- modelExtras: { projectId: spec.projectId, projectName: spec.name, isInSolution: true },
165
- };
166
- }
167
- const folderPath = spec.folderPath ?? '';
168
- return {
169
- inSolution: false,
170
- folderPath,
171
- resourceKey: `${folderPath}.${spec.name}`,
172
- description: `(${folderPath})`,
173
- tags: [],
174
- definitionExtras: {},
175
- modelExtras: {},
176
- };
177
- }
178
- /**
179
- * Build the definition for one deployed RPA process.
180
- *
181
- * This definition CANNOT be bundled, and that is not an oversight: the node type
182
- * carries the process's release key, and the contract (which arguments it takes,
183
- * what it returns) belongs to whatever someone published on a tenant. The
184
- * platform's own answer is per-tenant too — `uip maestro flow registry get
185
- * uipath.core.rpa-workflow.<key>` synthesizes it from the deployed process.
186
- *
187
- * So the static parts below are transcribed from the deployed corpus rather than
188
- * invented: 45 `uipath.core.rpa-workflow.*` instances across 47 `.flow`
189
- * artifacts of `skill-flow-rpa` (2026-05-18 → 07-24), cross-checked against
190
- * `registry get` for the same process. Every constant here is one those agree
191
- * on — `category: 'rpa-workflow'`, `sortOrder: 530`, `supportsErrorHandling`,
192
- * `icon: 'rpa'`, the `bpmn:ServiceTask` / `Orchestrator.StartJob` / `v2` model,
193
- * `section: 'Published'` (42/45), `debug.runtime: 'bpmnEngine'`, and the
194
- * `<bindings.name>` / `<bindings.folderPath>` context placeholders.
195
- * `sortOrder` is not one of those constants any more: flow-core derives it from
196
- * the category, and renumbered after 07-29, so it comes from
197
- * `resourceNodeSortOrder` (#1045).
198
- *
199
- * Two fields are derived from the author's call rather than from the tenant, and
200
- * that limit is real:
201
- *
202
- * - `inputDefinition` declares exactly the arguments the author passes, typed
203
- * from the values passed. That is the PR-10 payload-schema pattern — declare
204
- * what is evidenced — but here the evidence is the authored call, not the
205
- * deployed process, so it cannot catch an argument the process does not have.
206
- * Nothing local can: only the robot knows. A wrong argument name faults the
207
- * job on a live run, which is what the `robot` rung is for.
208
- * - `outputDefinition.output` carries no schema for the same reason. Its source
209
- * expressions follow the tenant-current registry (`=this` / `=Error`). Older
210
- * corpus artifacts mostly used `=result.response` / `=result.Error`, but a
211
- * 2026-08-04 product debug proved that old spelling now completes the RPA
212
- * element while publishing a null step output.
213
- */
214
- function rpaWorkflowDef(spec) {
215
- const loc = processResourceLocation(spec);
216
- const { resourceKey } = loc;
217
- const properties = {};
218
- for (const [k, v] of Object.entries(spec.inputs)) {
219
- properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
220
- }
221
- return {
222
- nodeType: `${RPA_WORKFLOW_PREFIX}${spec.key}`,
223
- version: '1.0.0',
224
- category: 'rpa-workflow',
225
- runtimeConstraints: { exclude: ['api-function'] },
226
- description: loc.description,
227
- tags: loc.tags,
228
- sortOrder: resourceNodeSortOrder('rpa-workflow'),
229
- supportsErrorHandling: true,
230
- display: { label: spec.name, icon: 'rpa' },
231
- handleConfiguration: [
232
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
233
- { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
234
- ],
235
- ...loc.definitionExtras,
236
- model: {
237
- type: 'bpmn:ServiceTask',
238
- serviceType: 'Orchestrator.StartJob',
239
- version: 'v2',
240
- ...(loc.inSolution ? {} : { section: 'Published' }),
241
- bindings: {
242
- resource: 'process',
243
- resourceSubType: 'Process',
244
- resourceKey,
245
- orchestratorType: 'process',
246
- values: [
247
- { name: 'name', propertyAttribute: 'name', default: spec.name },
248
- { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
249
- ],
250
- },
251
- ...loc.modelExtras,
252
- context: [
253
- { name: 'name', type: 'string', value: '<bindings.name>' },
254
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
255
- { name: '_label', type: 'string', value: spec.name },
256
- ],
257
- },
258
- inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
259
- inputDefaults: {},
260
- outputDefinition: {
261
- output: {
262
- type: 'object',
263
- description: 'The return value of the RPA process',
264
- source: '=this',
265
- var: 'output',
266
- // The process's output arguments, as the author declared them. The
267
- // platform's own definition carries this when it can read the deployed
268
- // process (`registry get` returns exactly this shape); offline the author
269
- // is the only source, and without it a downstream read is unresolvable ("reads
270
- // from an undefined object") — rightly, since nothing could tell a real
271
- // field from a typo.
272
- ...(spec.returns && Object.keys(spec.returns).length
273
- ? {
274
- schema: {
275
- type: 'object',
276
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
277
- },
278
- }
279
- : {}),
280
- },
281
- error: {
282
- type: 'object',
283
- description: 'Error information if the RPA process fails',
284
- source: '=Error',
285
- var: 'error',
286
- },
287
- },
288
- debug: { runtime: 'bpmnEngine' },
289
- };
290
- }
291
- /** The API-workflow node type family. The published workflow's key is the suffix. */
292
- const API_WORKFLOW_PREFIX = 'uipath.core.api-workflow.';
293
- /**
294
- * Build the definition for one deployed API workflow.
295
- *
296
- * Like rpa's, this definition CANNOT be bundled: the node type carries the
297
- * workflow's key and the platform synthesizes the contract from whatever someone
298
- * published on a tenant (`uip maestro flow registry get
299
- * uipath.core.api-workflow.<key>` answers per-tenant). Unlike rpa's, the
300
- * platform's copy declares a real OUTPUT SCHEMA as well as an input one — for
301
- * `Shared.NameToAgeFixed` it is `{EstimatedAge: integer}` — which is still not
302
- * knowable offline, so `returns` is what supplies it.
303
- *
304
- * The constants are transcribed from what the deployed corpus and the registry
305
- * AGREE on, not invented: 42 `uipath.core.api-workflow.*` instances across 42
306
- * dated `skill-flow-api-workflow` artifacts (2026-05-18 → 07-29), cross-checked
307
- * against `registry get` for today's workflow. Every one is unanimous —
308
- * `category: 'api-workflow'`, `sortOrder: 510` (now 640, see `resourceNodeSortOrder`, #1045), `supportsErrorHandling`,
309
- * `icon: 'api'`, the `bpmn:ServiceTask` / `Orchestrator.ExecuteApiWorkflowAsync` /
310
- * `v2` model, `section: 'Published'` (37/42; the 5 without are the in-solution
311
- * shape, a bare-GUID resourceKey — rpa's 3/45 split again), `resourceSubType:
312
- * 'Api'`, `orchestratorType: 'api'`, `debug.runtime: 'bpmnEngine'`, and the
313
- * `<bindings.name>` / `<bindings.folderPath>` context placeholders.
314
- *
315
- * `version` is `1.0.0` in 42 of 42 and the registry agrees: one version, no
316
- * migrations, so unlike queue's there is no era to be a version behind on. What
317
- * there IS an era on is the tenant RESOURCE — the corpus splits 31/4 between two
318
- * different workflows with two different output field names (`estimatedAge` vs
319
- * `EstimatedAge`) — which is why the author passes the three identifiers rather than
320
- * anything here defaulting them.
321
- *
322
- * (This comment used to say those 31 "name a workflow that no longer exists". That
323
- * was backwards: re-dated 2026-07-29 with `--all-folders --limit 300`, 36 of the 42
324
- * still resolve, the 31 among them, and the 6 that do not are early shapes with a
325
- * blank or stale folder. The claim rested on the 50-row truncation PR 14 found. See
326
- * `typescript/tests/api-workflow.test.ts`'s header for the recount.)
327
- *
328
- * `outputs.output.source` is `=this`, which is what the definition itself
329
- * declares. The corpus is split (`=result.response` 33/42, `=this` 6/42, and two
330
- * passing runs a day apart disagree), so the platform accepts either; we emit the
331
- * one the platform's own definition writes.
332
- */
333
- function apiWorkflowDef(spec) {
334
- const resourceKey = `${spec.folderPath}.${spec.name}`;
335
- const properties = {};
336
- for (const [k, v] of Object.entries(spec.inputs)) {
337
- properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
338
- }
339
- return {
340
- nodeType: `${API_WORKFLOW_PREFIX}${spec.key}`,
341
- version: '1.0.0',
342
- category: 'api-workflow',
343
- runtimeConstraints: { exclude: ['api-function'] },
344
- description: `(${spec.folderPath})`,
345
- tags: [],
346
- sortOrder: resourceNodeSortOrder('api-workflow'),
347
- supportsErrorHandling: true,
348
- display: { label: spec.name, icon: 'api' },
349
- handleConfiguration: [
350
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
351
- { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
352
- ],
353
- model: {
354
- type: 'bpmn:ServiceTask',
355
- serviceType: 'Orchestrator.ExecuteApiWorkflowAsync',
356
- version: 'v2',
357
- section: 'Published',
358
- bindings: {
359
- resource: 'process',
360
- resourceSubType: 'Api',
361
- resourceKey,
362
- orchestratorType: 'api',
363
- values: [
364
- { name: 'name', propertyAttribute: 'name', default: spec.name },
365
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
366
- ],
367
- },
368
- context: [
369
- { name: 'name', type: 'string', value: '<bindings.name>' },
370
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
371
- { name: '_label', type: 'string', value: spec.name },
372
- ],
373
- },
374
- inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
375
- inputDefaults: {},
376
- outputDefinition: {
377
- output: {
378
- type: 'object',
379
- source: '=this',
380
- var: 'output',
381
- // The workflow's output arguments, as the author declared them — the same
382
- // shape `registry get` returns when it can reach the deployed workflow.
383
- // Without it a downstream read is unresolvable, rightly: nothing offline could
384
- // tell a real field from a typo.
385
- ...(spec.returns && Object.keys(spec.returns).length
386
- ? {
387
- schema: {
388
- type: 'object',
389
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
390
- },
391
- }
392
- : {}),
393
- },
394
- error: {
395
- type: 'object',
396
- description: 'Error information if the node fails',
397
- source: '=Error',
398
- var: 'error',
399
- },
400
- },
401
- debug: { runtime: 'bpmnEngine' },
402
- };
403
- }
404
- /** The agentic-process node type family. The published process's key is the suffix. */
405
- const FUNCTION_PREFIX = 'uipath.core.function.';
406
- /**
407
- * The definition for one published Orchestrator FUNCTION, transcribed from the
408
- * platform's own synthesized manifest (`uip maestro flow registry get
409
- * uipath.core.function.<key>`, read 2026-08-19): the api-workflow shape with
410
- * this family's service type (`Orchestrator.ExecuteFunctionAsync`), binding
411
- * subtype (`Function`), `orchestratorType: 'function'`, and its own
412
- * `listingVersion` — plus the `error` source handle the registry declares
413
- * alongside `output` (gated on `errorHandlingEnabled`, as everywhere else).
414
- */
415
- function publishedFunctionDef(spec) {
416
- const resourceKey = `${spec.folderPath}.${spec.name}`;
417
- const properties = {};
418
- for (const [k, v] of Object.entries(spec.inputs)) {
419
- properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
420
- }
421
- return {
422
- nodeType: `${FUNCTION_PREFIX}${spec.key}`,
423
- version: '1.0.0',
424
- category: 'function',
425
- description: `(${spec.folderPath})`,
426
- tags: [],
427
- sortOrder: resourceNodeSortOrder('function'),
428
- supportsErrorHandling: true,
429
- display: { label: spec.name, icon: 'function' },
430
- handleConfiguration: [
431
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
432
- {
433
- position: 'right',
434
- handles: [
435
- { id: 'output', type: 'source', handleType: 'output' },
436
- {
437
- id: 'error', label: 'Error', type: 'source', handleType: 'output',
438
- visible: '{inputs.errorHandlingEnabled}',
439
- constraints: { maxConnections: 1 },
440
- },
441
- ],
442
- },
443
- ],
444
- model: {
445
- type: 'bpmn:ServiceTask',
446
- serviceType: 'Orchestrator.ExecuteFunctionAsync',
447
- version: 'v2',
448
- section: 'Published',
449
- listingVersion: '1.0.0',
450
- bindings: {
451
- resource: 'process',
452
- resourceSubType: 'Function',
453
- resourceKey,
454
- orchestratorType: 'function',
455
- values: [
456
- { name: 'name', propertyAttribute: 'name', default: spec.name },
457
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
458
- ],
459
- },
460
- context: [
461
- { name: 'name', type: 'string', value: '<bindings.name>' },
462
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
463
- { name: '_label', type: 'string', value: spec.name },
464
- ],
465
- },
466
- inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
467
- inputDefaults: {},
468
- outputDefinition: {
469
- output: {
470
- type: 'object',
471
- source: '=this',
472
- var: 'output',
473
- // The function's output arguments as the author declared them — same
474
- // rule and same reason as the api-workflow family's.
475
- ...(spec.returns && Object.keys(spec.returns).length
476
- ? {
477
- schema: {
478
- type: 'object',
479
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
480
- },
481
- }
482
- : {}),
483
- },
484
- error: {
485
- type: 'object',
486
- description: 'Error information if the node fails',
487
- source: '=Error',
488
- var: 'error',
489
- },
490
- },
491
- debug: { runtime: 'bpmnEngine' },
492
- };
493
- }
494
- /**
495
- * Build the definition for one deployed agentic process (a Maestro process
496
- * orchestration).
497
- *
498
- * Unbundlable for rpa's and api-workflow's reason — the node type carries a
499
- * tenant's process key and the platform synthesizes the contract from whatever
500
- * someone published there. So the constants are transcribed from what the
501
- * platform's own two oracles AGREE on, not invented:
502
- *
503
- * - `uip maestro flow registry get uipath.core.agentic-process.<key>` for ALL
504
- * THREE agentic processes on the tenant (2026-07-29): `category:
505
- * 'agentic-process'`, `sortOrder: 507` (now 610 for bpmn, 600 flow, 620 case:
506
- * `resourceNodeSortOrder`, #1045), `supportsErrorHandling`, `icon:
507
- * 'agentic-process'`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
508
- * `Orchestrator.StartAgenticProcess` / `v2` model, `section: 'Published'`,
509
- * `resourceSubType: 'ProcessOrchestration'`, `orchestratorType:
510
- * 'processorchestration'`, `debug.runtime: 'bpmnEngine'`, the
511
- * `<bindings.name>` / `<bindings.folderPath>` context placeholders, and
512
- * `runtimeConstraints.exclude: ['api-function']`;
513
- * - flow-v1's own generator, which is where those come from:
514
- * `manifest/mappers/node-manifest-builder.ts::createModel` hard-codes
515
- * `bpmn:ServiceTask` for every published-resource node and derives
516
- * `orchestratorType` as `resourceType.toLowerCase()`;
517
- * `RESOURCE_TYPE_METADATA.ProcessOrchestration` gives the service type.
518
- *
519
- * **There is NO deployed corpus for this family** — zero `.flow` artifacts under
520
- * `runs/` and zero tasks under `uipath-skills/tests/tasks/uipath-maestro-flow/`
521
- * carry an agentic-process node (checked 2026-07-29). The evidence is the two
522
- * oracles above plus one live job started from an emitted `.flow`; there is no
523
- * majority-of-42 to lean on, and `shared/sample-flows/agentic-process.flow` is
524
- * NOT that corpus: it is an older, fire-and-forget instance whose model type,
525
- * service type, output sources and `bindings.values` shape all disagree with what
526
- * `registry get` returns today.
527
- *
528
- * `Orchestrator.StartAgenticProcess` is the SYNCHRONOUS spelling — the one that
529
- * waits and publishes the process's output arguments. The platform's
530
- * fire-and-forget switch swaps in `…StartAgenticProcessAsync` and then keeps only
531
- * the `error` output, which is why this surface does not offer it (see
532
- * `agenticProcess`'s doc comment).
533
- */
534
- function agenticProcessDef(spec) {
535
- const form = ownEntry(AGENTIC_PROCESS_FORMS, spec.form ?? 'bpmn') ?? AGENTIC_PROCESS_FORMS.bpmn;
536
- const loc = processResourceLocation(spec);
537
- const { resourceKey } = loc;
538
- const properties = {};
539
- for (const [k, v] of Object.entries(spec.inputs)) {
540
- properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
541
- }
542
- return {
543
- nodeType: `${form.prefix}${spec.key}`,
544
- version: '1.0.0',
545
- category: form.category,
546
- runtimeConstraints: { exclude: ['api-function'] },
547
- description: loc.description,
548
- tags: loc.tags,
549
- sortOrder: resourceNodeSortOrder(form.category),
550
- supportsErrorHandling: true,
551
- display: { label: spec.name, icon: form.icon },
552
- handleConfiguration: [
553
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
554
- { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
555
- ],
556
- ...loc.definitionExtras,
557
- model: {
558
- type: 'bpmn:ServiceTask',
559
- serviceType: form.serviceType,
560
- version: 'v2',
561
- ...(loc.inSolution ? {} : { section: 'Published' }),
562
- bindings: {
563
- resource: 'process',
564
- resourceSubType: form.resourceSubType,
565
- resourceKey,
566
- orchestratorType: form.orchestratorType,
567
- values: [
568
- { name: 'name', propertyAttribute: 'name', default: spec.name },
569
- { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
570
- ],
571
- },
572
- ...loc.modelExtras,
573
- context: [
574
- { name: 'name', type: 'string', value: '<bindings.name>' },
575
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
576
- { name: '_label', type: 'string', value: spec.name },
577
- ],
578
- },
579
- // The fire-and-forget switch, exactly as flow-v1's withFireAndForgetInput
580
- // declares it: a boolean INPUT on the definition (default false). The sync
581
- // serviceType stays on the model — the platform's BPMN serializer swaps in
582
- // the async sibling when the instance opts in.
583
- inputDefinition: { type: 'object', properties: { ...properties, fireAndForget: { type: 'boolean' } } },
584
- inputDefaults: { fireAndForget: false },
585
- outputDefinition: {
586
- output: {
587
- type: 'object',
588
- source: '=this',
589
- var: 'output',
590
- // The process's output arguments, as the author declared them — the same
591
- // shape `registry get` returns when it can reach the deployed process
592
- // (nested under `schema`, which is the spelling gen-tests reads). Without
593
- // it a downstream read is unresolvable, rightly: nothing offline could tell a
594
- // real field from a typo.
595
- ...(spec.returns && Object.keys(spec.returns).length
596
- ? {
597
- schema: {
598
- type: 'object',
599
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
600
- },
601
- }
602
- : {}),
603
- },
604
- error: {
605
- type: 'object',
606
- description: 'Error information if the node fails',
607
- source: '=Error',
608
- var: 'error',
609
- },
610
- },
611
- debug: { runtime: 'bpmnEngine' },
612
- };
613
- }
614
129
  /** The published-agent node type family. The agent's release key is the suffix. */
615
130
  const AGENT_PREFIX = 'uipath.core.agent.';
616
131
  /** The inline (autonomous) agent's node type — fixed, not per-instance. */
@@ -2726,62 +2241,6 @@ function ixpDeploymentBindings(spec, existing) {
2726
2241
  },
2727
2242
  ] };
2728
2243
  }
2729
- /**
2730
- * The two bindings a process reference always carries — `name` + `folderPath`,
2731
- * both keyed to the SAME `resourceKey`, which is how the node (via its
2732
- * definition's `model.bindings.resourceKey`) finds them. 45 of 45 deployed rpa
2733
- * instances and 42 of 42 deployed api-workflow instances have exactly these two,
2734
- * and no more.
2735
- *
2736
- * ONE PAIR PER PROCESS, not per step. Bindings are addressed by
2737
- * `resource + resourceKey + propertyAttribute` — the key a bindings lookup map is
2738
- * built on — so a second pair for the same process is not a second
2739
- * reference: it is a duplicate that SHADOWS the first, both steps resolve to
2740
- * whichever was inserted last, and the loser sits in the file referenced by
2741
- * nothing. This is the shape PR 12 filed against the rpa family (the queue
2742
- * emitter already avoided it) and the ride-along this card owns; the ids now come
2743
- * from the process rather than from a step name, so two steps starting the same
2744
- * process share one pair.
2745
- */
2746
- function processResourceBindings(spec, existing) {
2747
- const resourceKey = spec.resourceKey ?? `${spec.folderPath}.${spec.name}`;
2748
- const found = (attr) => [...existing.values()].find((b) => b.resource === 'process' && b.resourceKey === resourceKey && b.propertyAttribute === attr)?.id;
2749
- const already = { name: found('name'), folderPath: found('folderPath') };
2750
- if (already.name && already.folderPath) {
2751
- return { ids: { name: already.name, folderPath: already.folderPath }, entries: [] };
2752
- }
2753
- // A readable, stable id per process. Two processes sharing a NAME across folders
2754
- // would collide, so the id is suffixed until it is free — the queue emitter's
2755
- // shape, which this is a port of.
2756
- const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || 'Process';
2757
- let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
2758
- for (let n = 2; existing.has(`${base}Name`) || existing.has(`${base}FolderPath`); n++) {
2759
- base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}${n}`;
2760
- }
2761
- const ids = { name: `${base}Name`, folderPath: `${base}FolderPath` };
2762
- return { ids, entries: [
2763
- {
2764
- id: ids.name,
2765
- name: 'name',
2766
- type: 'string',
2767
- resource: 'process',
2768
- resourceKey,
2769
- default: spec.name,
2770
- propertyAttribute: 'name',
2771
- resourceSubType: spec.resourceSubType,
2772
- },
2773
- {
2774
- id: ids.folderPath,
2775
- name: 'folderPath',
2776
- type: 'string',
2777
- resource: 'process',
2778
- resourceKey,
2779
- default: spec.folderPath,
2780
- propertyAttribute: 'folderPath',
2781
- resourceSubType: spec.resourceSubType,
2782
- },
2783
- ] };
2784
- }
2785
2244
  /**
2786
2245
  * Declare a LIST operation's output as the ARRAY it is, when the ledger has
2787
2246
  * MEASURED that it is one ([#203](https://github.com/UiPath/flow-builder-sdk/issues/203)).
@@ -3385,20 +2844,6 @@ export function bundledActionNodeType(spec) {
3385
2844
  export function perCallDefinition(spec) {
3386
2845
  const s = spec?.inputs ?? {};
3387
2846
  switch (spec?.kind) {
3388
- case 'publishedFunction':
3389
- return publishedFunctionDef({ key: s.key, name: s.name, folderPath: s.folderPath, inputs: s.inputs ?? {}, returns: s.returns });
3390
- case 'apiWorkflow':
3391
- return apiWorkflowDef({ key: s.key, name: s.name, folderPath: s.folderPath, inputs: s.inputs ?? {}, returns: s.returns });
3392
- case 'agenticProcess':
3393
- return agenticProcessDef({
3394
- key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
3395
- inputs: s.inputs ?? {}, returns: s.returns, form: s.form,
3396
- });
3397
- case 'rpaWorkflow':
3398
- return rpaWorkflowDef({
3399
- key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
3400
- inputs: s.inputs ?? {}, returns: s.returns,
3401
- });
3402
2847
  case 'agent':
3403
2848
  return agentDef({
3404
2849
  key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
@@ -3778,17 +3223,26 @@ function eventDetail(rc, sub, nodeId, opts, site) {
3778
3223
  * or in a custom node's inputs. Recursing into it would put `{"__lookup": …}` on
3779
3224
  * the wire where the connector expects an id.
3780
3225
  */
3781
- function renderDeep(val, rename = NO_RENAME) {
3226
+ function renderDeep(val, rename = NO_RENAME, rendered) {
3782
3227
  if (isLookupToken(val))
3783
3228
  throw new Error(misplacedLookupMessage(val));
3784
3229
  if (val instanceof Expr)
3785
3230
  return renderValue(val, rename);
3231
+ // A value `ctx.render` already rendered is the file's: a `lit`'s object is
3232
+ // written as is. Walking it again made `lit(new Date(…))` `{}` and rendered
3233
+ // an `Expr` inside a `lit({ … })`.
3234
+ if (val !== null && typeof val === 'object' && rendered?.has(val))
3235
+ return val;
3236
+ // A value that says how JSON writes it (a `Date`, via `toJSON`) is written
3237
+ // that way: walking its own keys made a bare `new Date(…)` `{}`.
3238
+ if (val !== null && typeof val === 'object' && typeof val.toJSON === 'function')
3239
+ return val;
3786
3240
  if (Array.isArray(val))
3787
- return val.map((v) => renderDeep(v, rename));
3241
+ return val.map((v) => renderDeep(v, rename, rendered));
3788
3242
  if (val && typeof val === 'object') {
3789
3243
  const o = {};
3790
3244
  for (const [k, v] of Object.entries(val))
3791
- o[k] = renderDeep(v, rename);
3245
+ o[k] = renderDeep(v, rename, rendered);
3792
3246
  return o;
3793
3247
  }
3794
3248
  return val;
@@ -5017,7 +4471,7 @@ export function serialize(built, opts = {}) {
5017
4471
  const node = makeNode(o.typeName, id, o.stepName, { parentId: o.parentId, def: o.def, requestedVersion: o.requestedVersion });
5018
4472
  if (contribution.model === 'omit')
5019
4473
  delete node.model;
5020
- node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
4474
+ node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename, o.rendered);
5021
4475
  if (contribution.outputs) {
5022
4476
  // Replaced outputs are the instance's own, written verbatim: a human task's
5023
4477
  // come from its form, where a literal `{nodeId}` in an outcome name or a
@@ -5075,13 +4529,26 @@ export function serialize(built, opts = {}) {
5075
4529
  // expression renders, so a handler reading this step resolves correctly.
5076
4530
  if (impl.errorEnvelope)
5077
4531
  scope.rename.errorEnvelopes.set(step.name, impl.errorEnvelope);
4532
+ const rendered = new WeakSet();
5078
4533
  const ctx = {
5079
4534
  step: step.name,
5080
4535
  nodeId: id,
5081
4536
  flowId: built.id,
5082
4537
  version,
5083
4538
  pinnedVersion: step.options?.version,
5084
- render: (value) => renderValue(value, scope.rename),
4539
+ render: (value) => {
4540
+ const out = renderValue(value, scope.rename);
4541
+ // Only a `lit` unwraps to a new value. Its JSON copy is the file's: a
4542
+ // copy, so an object the author also passes outside the `lit` is not
4543
+ // mistaken for this one and still has its expressions rendered.
4544
+ if (out !== value && out !== null && typeof out === 'object') {
4545
+ const copy = JSON.parse(JSON.stringify(out));
4546
+ if (copy !== null && typeof copy === 'object')
4547
+ rendered.add(copy);
4548
+ return copy;
4549
+ }
4550
+ return out;
4551
+ },
5085
4552
  renameRefs: (js) => renameRefs(js, scope.rename),
5086
4553
  stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
5087
4554
  mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
@@ -5126,6 +4593,16 @@ export function serialize(built, opts = {}) {
5126
4593
  else if (impl.definition === 'per-tenant' && resolved.carried === undefined) {
5127
4594
  throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${version} returned no definition.`);
5128
4595
  }
4596
+ // A type that owns a family of names (`typeNamePrefix`) is placed under the
4597
+ // name its definition carries — `uipath.core.function.<key>`, not the prefix.
4598
+ // The bare prefix is allowed: an empty `key` builds it, check refuses that
4599
+ // input (`*_NO_KEY`), and serialize alone emits what it always did.
4600
+ const prefix = entry.type.typeNamePrefix;
4601
+ const nodeType = prefix !== undefined ? def?.nodeType : impl.typeName;
4602
+ if (prefix !== undefined && (typeof nodeType !== 'string' || !nodeType.startsWith(prefix))) {
4603
+ throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} supplied a definition for `
4604
+ + `${JSON.stringify(nodeType)}, which is not a name under "${prefix}".`);
4605
+ }
5129
4606
  if (impl.definition === 'per-tenant') {
5130
4607
  const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
5131
4608
  // The declaration answers for check, which has no definition; here the
@@ -5134,7 +4611,7 @@ export function serialize(built, opts = {}) {
5134
4611
  throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} declares an error port, `
5135
4612
  + 'but the definition it supplied has no error handle and no supportsErrorHandling.');
5136
4613
  }
5137
- declaredErrorPorts.set(id, { port, typeName: impl.typeName, version });
4614
+ declaredErrorPorts.set(id, { port, typeName: nodeType, version });
5138
4615
  }
5139
4616
  // A forward port is a handle the definition declares, one of the type's
5140
4617
  // author-named exits, or one the product derives (`derivesPort`: a human
@@ -5153,8 +4630,9 @@ export function serialize(built, opts = {}) {
5153
4630
  if (resolved.carried !== undefined)
5154
4631
  bundledNodeDefs.set(id, def ?? resolved.carried);
5155
4632
  return placeStepNode({
5156
- where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: version, def, contribution, bound, tails, scope, parentId,
4633
+ where, stepName: step.name, id, typeName: nodeType, requestedVersion: version, def, contribution, bound, tails, scope, parentId,
5157
4634
  trackOutcomes: true,
4635
+ rendered,
5158
4636
  });
5159
4637
  }
5160
4638
  function emitAction(step, tails, scope, parentId) {
@@ -5892,55 +5370,6 @@ export function serialize(built, opts = {}) {
5892
5370
  addEdge(scope, t, id);
5893
5371
  return [{ node: id, port: successPort(def.nodeType) }];
5894
5372
  }
5895
- if (spec.kind === 'rpaWorkflow' || spec.kind === 'apiWorkflow' || spec.kind === 'agenticProcess'
5896
- || spec.kind === 'publishedFunction') {
5897
- // One emit path for the FOUR process-resource families this SDK authors: they
5898
- // differ in the definition they carry (node type, serviceType, subtype, the
5899
- // key's field name) and in nothing else — the node body, the binding pair and
5900
- // the model-stripping are identical, and the DATA paths that differ are all
5901
- // downstream in the engine.
5902
- const s = spec.inputs;
5903
- const nodeInputs = s.inputs ?? {};
5904
- const fireAndForget = spec.kind === 'agenticProcess' && spec.inputs.completion === 'fire-and-forget';
5905
- // The same definition `check` asks about the error port.
5906
- const def = perCallDefinition(spec);
5907
- // The bindings first: two steps on the same process share ONE pair, and the
5908
- // definition's `model.context` names ids that have to exist.
5909
- // Read off the definition: one kind carries several subtypes (agenticProcess by `form`).
5910
- // The pair's key and folder come off it too: an in-solution step's resourceKey is the
5911
- // bare sibling key and its folder is `''`.
5912
- const subType = def.model.bindings.resourceSubType;
5913
- const pairFolder = def.model.bindings.values.find((v) => v.name === 'folderPath').default;
5914
- const { entries } = processResourceBindings({ name: s.name, folderPath: pairFolder, resourceSubType: subType, resourceKey: def.model.bindings.resourceKey }, resourceBindings);
5915
- for (const b of entries)
5916
- resourceBindings.set(b.id, b);
5917
- const id = stepUid(scope, step.name);
5918
- const node = makeNode(def.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def });
5919
- // The deployed shape keeps the resource model on the DEFINITION only —
5920
- // 42 of 45 rpa instances and 42 of 42 api-workflow instances carry no
5921
- // node-level `model` at all. makeNode copies a definition's model onto the
5922
- // node for the connector families, where the platform does the same; here it
5923
- // would emit a block the platform never writes, so it comes off again.
5924
- // (agentic-process has no deployed corpus to count, so it follows its two
5925
- // siblings — and the model resolves from either place since
5926
- // #94, which is what the step-0 probe walked to a live job.)
5927
- delete node.model;
5928
- node.inputs = renderMap(nodeInputs, scope.rename);
5929
- if (fireAndForget) {
5930
- // The platform's fire-and-forget switch, persisted the way the canvas
5931
- // persists it: the design-time flag on the instance. The dispatch
5932
- // publishes NO normal output — only dispatch failures route — so the
5933
- // node keeps only its `error` output and the flag never becomes a
5934
- // process input argument (renderMap above saw only the author's inputs).
5935
- node.inputs.fireAndForget = true;
5936
- if (node.outputs?.output)
5937
- delete node.outputs.output;
5938
- }
5939
- scope.nodes.push(node);
5940
- for (const t of tails)
5941
- addEdge(scope, t, id);
5942
- return [{ node: id, port: successPort(def.nodeType) }];
5943
- }
5944
5373
  if (spec.kind === 'conversationalAgent') {
5945
5374
  // The text CONVERSATIONAL agent: the inline agent's cluster shape (a
5946
5375
  // minted `source` plus an `agent.json` sidecar) with the family's own