@uipath/maestro-builder-sdk 6.16.14 → 6.17.1

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 (74) hide show
  1. package/README.md +2 -2
  2. package/dist/api-index.md +115 -114
  3. package/dist/api-members.md +295 -291
  4. package/dist/bindings-v2.d.ts +39 -1
  5. package/dist/bindings-v2.js +289 -9
  6. package/dist/bpmn/compile-cli.js +11 -2
  7. package/dist/bpmn/decompile.js +2 -1
  8. package/dist/bpmn/registry-fallback.js +9 -7
  9. package/dist/case/compile-cli.js +16 -6
  10. package/dist/case/decompile-cli.js +2 -1
  11. package/dist/case/decompile.js +3 -2
  12. package/dist/case/merge-cli.js +3 -2
  13. package/dist/check-cli.js +2 -30
  14. package/dist/check.js +278 -55
  15. package/dist/cli-library.d.ts +18 -4
  16. package/dist/cli-library.js +24 -6
  17. package/dist/cli-result.d.ts +9 -0
  18. package/dist/cli-result.js +12 -0
  19. package/dist/compile-cli.js +82 -86
  20. package/dist/core/actions.d.ts +71 -17
  21. package/dist/core/agentic-process-forms.d.ts +51 -0
  22. package/dist/core/agentic-process-forms.js +49 -0
  23. package/dist/core/binding-messages.d.ts +5 -0
  24. package/dist/core/cli-library.d.ts +16 -7
  25. package/dist/core/cli-library.js +53 -19
  26. package/dist/core/comment-text.d.ts +29 -0
  27. package/dist/core/comment-text.js +40 -0
  28. package/dist/core/connector-checks.d.ts +18 -2
  29. package/dist/core/connector-checks.js +15 -5
  30. package/dist/core/contribution-bindings.d.ts +73 -0
  31. package/dist/core/contribution-bindings.js +105 -0
  32. package/dist/core/error-port.d.ts +25 -0
  33. package/dist/core/error-port.js +36 -0
  34. package/dist/core/expr-check.js +7 -2
  35. package/dist/core/expr.d.ts +11 -5
  36. package/dist/core/expr.js +9 -3
  37. package/dist/core/json-file.d.ts +1 -0
  38. package/dist/core/json-file.js +12 -0
  39. package/dist/core/library.d.ts +19 -0
  40. package/dist/core/library.js +22 -6
  41. package/dist/core/script-return.js +4 -1
  42. package/dist/core/typed-raw-node.js +1 -0
  43. package/dist/core/typescript-module.d.ts +2 -0
  44. package/dist/core/typescript-module.js +18 -0
  45. package/dist/core-definitions.json +210 -0
  46. package/dist/decompile-cli.js +2 -1
  47. package/dist/decompile.js +64 -24
  48. package/dist/emit.js +4 -3
  49. package/dist/flow-sdk.d.ts +18 -12
  50. package/dist/flow-sdk.js +19 -8
  51. package/dist/flow-step-tree.d.ts +2 -3
  52. package/dist/flow-step-tree.js +2 -3
  53. package/dist/load.js +1 -1
  54. package/dist/merge-cli.js +3 -2
  55. package/dist/merge.d.ts +2 -1
  56. package/dist/merge.js +0 -0
  57. package/dist/path-within.d.ts +33 -0
  58. package/dist/path-within.js +41 -0
  59. package/dist/registry/archive/extract.js +2 -5
  60. package/dist/registry/curated-overlay.js +3 -2
  61. package/dist/registry/generators/descriptors.js +20 -18
  62. package/dist/registry/generators/triggers.js +4 -4
  63. package/dist/registry/prepare/messages.js +2 -1
  64. package/dist/registry/prepare/source-labels.js +60 -1
  65. package/dist/registry/prepare-cli.js +14 -4
  66. package/dist/registry/pull-cli.js +3 -2
  67. package/dist/registry/uip/client.d.ts +28 -3
  68. package/dist/registry/uip/client.js +95 -34
  69. package/dist/registry/uip/host.d.ts +41 -0
  70. package/dist/registry/uip/host.js +312 -0
  71. package/dist/serialize.d.ts +7 -0
  72. package/dist/serialize.js +414 -277
  73. package/dist/workdir.js +1 -1
  74. package/package.json +3 -3
package/dist/serialize.js CHANGED
@@ -14,6 +14,8 @@ import { Expr, toExpr, SCHEDULE_PRESETS, DELAY_PRESETS, parseIxpProjectType, fla
14
14
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
15
15
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
16
16
  import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
17
+ import { planContributionBindings } from './core/contribution-bindings.js';
18
+ import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
17
19
  import { isAnswerField } from './core/hitl-answers.js';
18
20
  import { connectionConnectors } from './bindings-v2.js';
19
21
  import { BindingRefusal, GUID_RE, bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, missingBindingLabels, undeclaredBinding } from './core/binding-messages.js';
@@ -49,6 +51,8 @@ import { resolveSchemaVersion } from './schema-version.js';
49
51
  import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-variables.js';
50
52
  import { prepareCommand } from './core/cli-spelling.js';
51
53
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
54
+ import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
55
+ import { types } from './core/expr.js';
52
56
  const HERE = dirname(fileURLToPath(import.meta.url));
53
57
  // core-definitions.json is an object keyed by "<nodeType>@<version>".
54
58
  const DEFS = JSON.parse(readFileSync(join(HERE, 'core-definitions.json'), 'utf8'));
@@ -143,6 +147,53 @@ const SUBFLOW_OUTPUT = {
143
147
  };
144
148
  /** The RPA-workflow node type family. The process's release key is the suffix. */
145
149
  const RPA_WORKFLOW_PREFIX = 'uipath.core.rpa-workflow.';
150
+ /**
151
+ * Where a process resource lives, as ONE slot every process-resource definition
152
+ * shares. In the platform model "in this solution" is orthogonal to the
153
+ * resource type: flow-core's single `createModel` (`@uipath/flow-core`
154
+ * `manifest/mappers/node-manifest-builder.ts`, what `uip maestro flow registry
155
+ * get --local` and Studio Web both run) adds `projectId`, `projectName` and
156
+ * `isInSolution: true` to the model of ANY process resource built from a sibling
157
+ * project.
158
+ *
159
+ * The in-solution shape, transcribed from `registry get --local` for a sibling
160
+ * Flow (2026-10-08) and from uipath-cli's `buildInSolutionNodeManifests`:
161
+ * - `resourceKey` is the bare key (the sibling's `resource.key`), not
162
+ * `<folderPath>.<name>`; the folder default is `''`;
163
+ * - no `section: 'Published'`;
164
+ * - `projectId` on the definition AND on the model, with `projectName` and
165
+ * `isInSolution: true` (the marker the canvas reads);
166
+ * - description `(In-solution) <name>`, tags `in-solution` / `local`, and the
167
+ * `open-workflow` toolbar action.
168
+ */
169
+ function processResourceLocation(spec) {
170
+ if (spec.location === 'in-solution') {
171
+ return {
172
+ inSolution: true,
173
+ folderPath: '',
174
+ resourceKey: spec.key,
175
+ description: `(In-solution) ${spec.name}`,
176
+ tags: ['in-solution', 'local'],
177
+ definitionExtras: {
178
+ toolbarExtensions: {
179
+ design: { actions: [{ id: 'open-workflow', icon: 'external-link', label: 'Open' }] },
180
+ },
181
+ projectId: spec.projectId,
182
+ },
183
+ modelExtras: { projectId: spec.projectId, projectName: spec.name, isInSolution: true },
184
+ };
185
+ }
186
+ const folderPath = spec.folderPath ?? '';
187
+ return {
188
+ inSolution: false,
189
+ folderPath,
190
+ resourceKey: `${folderPath}.${spec.name}`,
191
+ description: `(${folderPath})`,
192
+ tags: [],
193
+ definitionExtras: {},
194
+ modelExtras: {},
195
+ };
196
+ }
146
197
  /**
147
198
  * Build the definition for one deployed RPA process.
148
199
  *
@@ -177,7 +228,8 @@ const RPA_WORKFLOW_PREFIX = 'uipath.core.rpa-workflow.';
177
228
  * element while publishing a null step output.
178
229
  */
179
230
  function rpaWorkflowDef(spec) {
180
- const resourceKey = `${spec.folderPath}.${spec.name}`;
231
+ const loc = processResourceLocation(spec);
232
+ const { resourceKey } = loc;
181
233
  const properties = {};
182
234
  for (const [k, v] of Object.entries(spec.inputs)) {
183
235
  properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
@@ -187,8 +239,8 @@ function rpaWorkflowDef(spec) {
187
239
  version: '1.0.0',
188
240
  category: 'rpa-workflow',
189
241
  runtimeConstraints: { exclude: ['api-function'] },
190
- description: `(${spec.folderPath})`,
191
- tags: [],
242
+ description: loc.description,
243
+ tags: loc.tags,
192
244
  sortOrder: 530,
193
245
  supportsErrorHandling: true,
194
246
  display: { label: spec.name, icon: 'rpa' },
@@ -196,11 +248,12 @@ function rpaWorkflowDef(spec) {
196
248
  { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
197
249
  { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
198
250
  ],
251
+ ...loc.definitionExtras,
199
252
  model: {
200
253
  type: 'bpmn:ServiceTask',
201
254
  serviceType: 'Orchestrator.StartJob',
202
255
  version: 'v2',
203
- section: 'Published',
256
+ ...(loc.inSolution ? {} : { section: 'Published' }),
204
257
  bindings: {
205
258
  resource: 'process',
206
259
  resourceSubType: 'Process',
@@ -208,9 +261,10 @@ function rpaWorkflowDef(spec) {
208
261
  orchestratorType: 'process',
209
262
  values: [
210
263
  { name: 'name', propertyAttribute: 'name', default: spec.name },
211
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
264
+ { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
212
265
  ],
213
266
  },
267
+ ...loc.modelExtras,
214
268
  context: [
215
269
  { name: 'name', type: 'string', value: '<bindings.name>' },
216
270
  { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
@@ -453,41 +507,6 @@ function publishedFunctionDef(spec) {
453
507
  debug: { runtime: 'bpmnEngine' },
454
508
  };
455
509
  }
456
- const AGENTIC_PROCESS_PREFIX = 'uipath.core.agentic-process.';
457
- /**
458
- * The three published forms under the Agentic Process umbrella (design §2.1).
459
- * One public factory, three wire identities — transcribed from flow-v1's
460
- * `RESOURCE_TYPE_METADATA` (ProcessOrchestration / Flow / CaseManagement):
461
- * node-type segment, category, icon, resource subtype, `orchestratorType`
462
- * (`resourceType.toLowerCase()`, per `createModel`), and the sync/async
463
- * service-type pair. All three declare the fire-and-forget switch.
464
- */
465
- const AGENTIC_PROCESS_FORMS = {
466
- bpmn: {
467
- prefix: AGENTIC_PROCESS_PREFIX,
468
- category: 'agentic-process',
469
- icon: 'agentic-process',
470
- resourceSubType: 'ProcessOrchestration',
471
- orchestratorType: 'processorchestration',
472
- serviceType: 'Orchestrator.StartAgenticProcess',
473
- },
474
- flow: {
475
- prefix: 'uipath.core.flow.',
476
- category: 'flow',
477
- icon: 'flow-project',
478
- resourceSubType: 'Flow',
479
- orchestratorType: 'flow',
480
- serviceType: 'Orchestrator.StartFlowProcess',
481
- },
482
- case: {
483
- prefix: 'uipath.core.case-management.',
484
- category: 'case-management',
485
- icon: 'case-management',
486
- resourceSubType: 'CaseManagement',
487
- orchestratorType: 'casemanagement',
488
- serviceType: 'Orchestrator.StartCaseMgmtProcess',
489
- },
490
- };
491
510
  /**
492
511
  * Build the definition for one deployed agentic process (a Maestro process
493
512
  * orchestration).
@@ -529,7 +548,8 @@ const AGENTIC_PROCESS_FORMS = {
529
548
  */
530
549
  function agenticProcessDef(spec) {
531
550
  const form = AGENTIC_PROCESS_FORMS[spec.form ?? 'bpmn'];
532
- const resourceKey = `${spec.folderPath}.${spec.name}`;
551
+ const loc = processResourceLocation(spec);
552
+ const { resourceKey } = loc;
533
553
  const properties = {};
534
554
  for (const [k, v] of Object.entries(spec.inputs)) {
535
555
  properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
@@ -539,8 +559,8 @@ function agenticProcessDef(spec) {
539
559
  version: '1.0.0',
540
560
  category: form.category,
541
561
  runtimeConstraints: { exclude: ['api-function'] },
542
- description: `(${spec.folderPath})`,
543
- tags: [],
562
+ description: loc.description,
563
+ tags: loc.tags,
544
564
  sortOrder: 507,
545
565
  supportsErrorHandling: true,
546
566
  display: { label: spec.name, icon: form.icon },
@@ -548,11 +568,12 @@ function agenticProcessDef(spec) {
548
568
  { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
549
569
  { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
550
570
  ],
571
+ ...loc.definitionExtras,
551
572
  model: {
552
573
  type: 'bpmn:ServiceTask',
553
574
  serviceType: form.serviceType,
554
575
  version: 'v2',
555
- section: 'Published',
576
+ ...(loc.inSolution ? {} : { section: 'Published' }),
556
577
  bindings: {
557
578
  resource: 'process',
558
579
  resourceSubType: form.resourceSubType,
@@ -560,9 +581,10 @@ function agenticProcessDef(spec) {
560
581
  orchestratorType: form.orchestratorType,
561
582
  values: [
562
583
  { name: 'name', propertyAttribute: 'name', default: spec.name },
563
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
584
+ { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
564
585
  ],
565
586
  },
587
+ ...loc.modelExtras,
566
588
  context: [
567
589
  { name: 'name', type: 'string', value: '<bindings.name>' },
568
590
  { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
@@ -741,28 +763,6 @@ function agentDef(spec) {
741
763
  debug: { runtime: 'bpmnEngine' },
742
764
  };
743
765
  }
744
- /**
745
- * Build the definition for the INLINE autonomous agent.
746
- *
747
- * This is the one node type in the agent family that is the SAME on every tenant —
748
- * it carries its own configuration instead of referencing a resource — so
749
- * `uip maestro flow registry get uipath.agent.autonomous` is a direct oracle rather
750
- * than a per-instance answer, and the def can be built from constants without a
751
- * corpus vote. Transcribed 2026-07-29: `version: '1.2'`, `sortOrder: 5`,
752
- * `supportsErrorHandling: true`, `model.source: true`,
753
- * `Orchestrator.StartInlineAgentJob`, the three required inputs, the artifact
754
- * handles (escalation / context / tool) and `success` + `error` on the right with
755
- * `isDefaultForType` on `success`.
756
- *
757
- * The deployed corpus (29 `uipath.agent.autonomous` nodes, 05-18 → 07-29) agrees on
758
- * everything a `.flow` carries and settles the one thing the def leaves open:
759
- * `outputs.output.source` is `=result.response` in 27 of 27 that declare outputs —
760
- * unanimous here, unlike the published family, because the platform's
761
- * append-`=this` fallback only fires for a node that declares no `output` at all.
762
- * `typeVersion` in that corpus is 1.1 ×10, 1.2 ×15, 1.3 ×4; we emit the registry's
763
- * 1.2. (`shared/sample-flows/inline-agent.flow` says 1.0, which appears in **none**
764
- * of the 29 — the same bundled-sample era gap as the published family's.)
765
- */
766
766
  /**
767
767
  * The definition for ONE Context Grounding index resource, transcribed from the
768
768
  * platform's own registry entry (`uip maestro flow registry get
@@ -984,6 +984,45 @@ const CONNECTOR_TOOL_RESOURCE_TYPE = 'integration';
984
984
  * on its own it says nothing about who owns a file: `properties.toolType` does.
985
985
  */
986
986
  const INTERNAL_TOOL_RESOURCE_TYPE = 'internal';
987
+ /**
988
+ * Where an agent schema property points to declare "this is a FILE":
989
+ * `{ $ref: JOB_ATTACHMENT_REF }`, with {@link jobAttachmentDefinition} under the
990
+ * same schema's `definitions['job-attachment']`.
991
+ */
992
+ const JOB_ATTACHMENT_REF = '#/definitions/job-attachment';
993
+ /**
994
+ * The canonical `job-attachment` definition an agent schema's file field
995
+ * `$ref`s into — the uipath-agents skill's `agent-definition.md` § File
996
+ * Attachments ("Schema is fixed — copy verbatim"), and the same block the
997
+ * platform's own analyze-attachments tool contract carries.
998
+ *
999
+ * It tells the agent runtime that an input is an Orchestrator attachment, so a
1000
+ * file tool (Analyze Files) can resolve the attachment's `ID`.
1001
+ *
1002
+ * A function, not a shared object: each schema gets its own copy, so an edit
1003
+ * to one emitted file cannot reach another.
1004
+ *
1005
+ * (The case serializer keeps its own copy in `case/entry-points.ts`: that one
1006
+ * mirrors the platform's `entry-points.json` packager byte for byte, and its
1007
+ * `MimeType` description differs.)
1008
+ */
1009
+ function jobAttachmentDefinition() {
1010
+ return {
1011
+ type: 'object',
1012
+ properties: {
1013
+ ID: { type: 'string', description: 'Orchestrator attachment key' },
1014
+ FullName: { type: 'string', description: 'File name' },
1015
+ MimeType: { type: 'string', description: 'MIME type, e.g. "application/pdf", "image/png"' },
1016
+ Metadata: {
1017
+ type: 'object',
1018
+ description: 'Dictionary<string, string> of metadata',
1019
+ additionalProperties: { type: 'string' },
1020
+ },
1021
+ },
1022
+ required: ['ID'],
1023
+ 'x-uipath-resource-kind': 'JobAttachment',
1024
+ };
1025
+ }
987
1026
  /** The `properties.toolType` of the built-in HTTP-request tool's file (#935). */
988
1027
  const HTTP_REQUEST_TOOL_TYPE = 'http-request';
989
1028
  /**
@@ -1139,29 +1178,13 @@ function builtinToolResourceJson(tool, label, inputs, spec) {
1139
1178
  properties: {
1140
1179
  attachments: {
1141
1180
  type: 'array',
1142
- items: { $ref: '#/definitions/job-attachment' },
1181
+ items: { $ref: JOB_ATTACHMENT_REF },
1143
1182
  description: String(inputs.attachmentsDescription ?? ''),
1144
1183
  },
1145
1184
  analysisTask: { type: 'string', description: String(inputs.analysisTaskDescription ?? '') },
1146
1185
  },
1147
1186
  required: ['attachments', 'analysisTask'],
1148
- definitions: {
1149
- 'job-attachment': {
1150
- type: 'object',
1151
- properties: {
1152
- ID: { type: 'string', description: 'Orchestrator attachment key' },
1153
- FullName: { type: 'string', description: 'File name' },
1154
- MimeType: { type: 'string', description: 'MIME type, e.g. "application/pdf", "image/png"' },
1155
- Metadata: {
1156
- type: 'object',
1157
- description: 'Dictionary<string, string> of metadata',
1158
- additionalProperties: { type: 'string' },
1159
- },
1160
- },
1161
- required: ['ID'],
1162
- 'x-uipath-resource-kind': 'JobAttachment',
1163
- },
1164
- },
1187
+ definitions: { 'job-attachment': jobAttachmentDefinition() },
1165
1188
  },
1166
1189
  outputSchema: {
1167
1190
  type: 'object',
@@ -1324,6 +1347,24 @@ const QUICK_FORM_FIELD_SCHEMA = {
1324
1347
  text: { type: 'string' }, number: { type: 'number' }, boolean: { type: 'boolean' },
1325
1348
  'date-time': { type: 'string', format: 'date-time' },
1326
1349
  };
1350
+ /**
1351
+ * The folder an app escalation names: the author's `folderPath`, or
1352
+ * `solution_folder` when they gave none — an app that ships in this solution.
1353
+ *
1354
+ * `solution_folder` is the platform's own spelling for that case: it is the
1355
+ * `Folder` that `uip solution resources list` reports for a solution-internal
1356
+ * app, and what the agents skill tells authors to copy into
1357
+ * `channels[].properties.folderName`. The SDK used to write `""`, which no
1358
+ * product writer produces (flow-builder-sdk#967). Measured, the two are the
1359
+ * same everywhere it counts: the agent tool's binding generator drops both
1360
+ * from the App binding (`resolveFolderPathBindingValue`), `uip agent refresh`
1361
+ * plus `uip solution resources refresh` derive byte-identical files from
1362
+ * either, and a live debug resolves the app in the debug folder for both. So
1363
+ * this changes the vocabulary, not the behaviour.
1364
+ */
1365
+ function escalationAppFolder(app) {
1366
+ return app.folderPath ? app.folderPath : 'solution_folder';
1367
+ }
1327
1368
  /**
1328
1369
  * The `resources/<id>/resource.json` an inline agent's ESCALATION needs — the
1329
1370
  * escalation half of what `toolResourceJson` does for a tool, and inert without
@@ -1400,7 +1441,7 @@ function escalationResourceJson(esc, spec) {
1400
1441
  // refresh` imports; `folderName` is the app's Orchestrator folder.
1401
1442
  resourceKey: app.key,
1402
1443
  appName: app.name,
1403
- folderName: app.folderPath ?? '',
1444
+ folderName: escalationAppFolder(app),
1404
1445
  // The app's deploy version (its action schema's `version`). The SDK does
1405
1446
  // not read the action schema, so this is the platform's own default — the
1406
1447
  // storage migration that introduced the field writes `1` when absent.
@@ -2183,134 +2224,88 @@ function memoryResourceDef(spec) {
2183
2224
  },
2184
2225
  };
2185
2226
  }
2186
- function inlineAgentDef(spec) {
2187
- return {
2188
- nodeType: INLINE_AGENT_TYPE,
2189
- version: '1.2',
2190
- category: 'agent',
2191
- description: 'AI agent that completes tasks autonomously',
2192
- tags: ['agentic', 'ai', 'autonomous', 'agent'],
2193
- sortOrder: 5,
2194
- supportsErrorHandling: true,
2195
- display: { label: 'Autonomous Agent', icon: 'autonomous-agent', shape: 'rectangle' },
2196
- handleConfiguration: [
2197
- {
2198
- position: 'top',
2199
- handles: [{
2200
- id: 'escalation', type: 'source', handleType: 'artifact', label: 'Escalations', showButton: true,
2201
- constraints: {
2202
- // BOTH spellings, as the platform's own def declares them (`registry
2203
- // get uipath.agent.autonomous`, 2026-07-31): the bare type is the
2204
- // pre-split era's escalation node, the wildcard is today's suffixed
2205
- // variants (`.coded-action-app` / `.quick-form`). Emitting only the
2206
- // bare one — as this did until card G16 — declared a handle that
2207
- // rejected every node the current registry can actually serve.
2208
- allowedTargets: [
2209
- { nodeType: 'uipath.agent.resource.escalation' },
2210
- { nodeType: 'uipath.agent.resource.escalation.*' },
2211
- ],
2212
- validationMessage: 'Only Agent Escalation nodes can connect here',
2213
- },
2214
- }],
2215
- },
2216
- {
2217
- position: 'bottom',
2218
- handles: [
2219
- {
2220
- id: 'context', type: 'source', handleType: 'artifact', label: 'Context', showButton: true,
2221
- constraints: {
2222
- allowedTargets: [{ nodeType: 'uipath.agent.resource.context.*' }],
2223
- validationMessage: 'Only Context resource nodes can connect here',
2224
- },
2225
- },
2226
- {
2227
- id: 'tool', type: 'source', handleType: 'artifact', label: 'Tools', showButton: true,
2228
- constraints: {
2229
- allowedTargets: [{ nodeType: 'uipath.agent.resource.tool.*' }],
2230
- validationMessage: 'Only Tool resource nodes can connect here',
2231
- },
2232
- },
2233
- ],
2234
- },
2235
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
2236
- {
2237
- position: 'right',
2238
- handles: [
2239
- { id: 'success', type: 'source', handleType: 'output', isDefaultForType: true },
2240
- {
2241
- id: 'error', type: 'source', handleType: 'output', label: 'Error',
2242
- visible: '{inputs.errorHandlingEnabled}',
2243
- constraints: { maxConnections: 1 },
2244
- },
2245
- ],
2246
- },
2247
- ],
2248
- inputDefinition: {
2249
- type: 'object',
2250
- properties: {
2251
- systemPrompt: { type: 'string', minLength: 1, errorMessage: { minLength: 'System prompt is required' } },
2252
- userPrompt: { type: 'string', minLength: 1, errorMessage: { minLength: 'User prompt is required' } },
2253
- model: { type: 'string', minLength: 1, errorMessage: { minLength: 'Model is required' } },
2254
- temperature: { type: 'number', minimum: 0, maximum: 1 },
2255
- maxTokenPerResponse: { type: 'number', minimum: 0, maximum: 16384 },
2256
- modelMaxTokens: { type: 'number', minimum: 0 },
2257
- maxIterations: { type: 'number', minimum: 1, maximum: 100 },
2258
- guardrails: { type: 'array' },
2259
- agentInputVariables: { type: 'array' },
2260
- agentOutputVariables: { type: 'array' },
2261
- },
2262
- required: ['systemPrompt', 'userPrompt', 'model'],
2263
- },
2264
- inputDefaults: {},
2265
- outputDefinition: {
2266
- output: {
2267
- type: 'object',
2268
- description: 'Agent response',
2269
- // `=result.response`, not `=this` — 27 of 27 deployed instances, and the
2270
- // engine's own inline dispatch returns `{ response: { …declared keys } }`.
2271
- source: '=result.response',
2272
- var: 'output',
2273
- // NO per-instance schema here, and that is the whole point of this comment:
2274
- // `definitions[]` is keyed by node TYPE, and every inline agent in a flow
2275
- // shares this ONE entry. A flow with two agents (an analyst and a writer,
2276
- // say) therefore cannot have both contracts here — whichever was emitted
2277
- // last would win, and every read against the other resolved to the wrong
2278
- // shape. Measured on GA row 7's port, whose four `.return()` mappings off
2279
- // the ANALYST drew 25 undeclared-field errors quoting the WRITER's fields
2280
- // (`Available at "$vars.disputeAnalystAgent.output": "subject", "body"`) on
2281
- // a flow that is correct.
2282
- //
2283
- // The instance is where an authored contract belongs, and the SDK already
2284
- // writes it there (`node.outputs.output.schema`, below). A reader that
2285
- // refines per node then picks it up — applying an instance schema when the
2286
- // definition's is GENERIC and never overriding a definition that declares
2287
- // a shape, which is right in general and was
2288
- // exactly what this def defeated.
2289
- //
2290
- // Deployed truth agrees: v1's own two-agent BillingDisputeResolution
2291
- // artifact (1.0, 2026-07-15) carries `properties: { content: {…} }` on the
2292
- // shared definition and `schema: null` on both agent nodes.
2293
- },
2294
- error: {
2295
- type: 'object',
2296
- description: 'Error information if the node fails',
2297
- source: '=Error',
2298
- var: 'error',
2299
- },
2300
- },
2301
- model: {
2302
- source: true,
2303
- type: 'bpmn:ServiceTask',
2304
- serviceType: 'Orchestrator.StartInlineAgentJob',
2305
- version: 'v2',
2306
- context: [
2307
- { name: '_label', type: 'string', value: '' },
2308
- { name: 'name', type: 'string', value: '' },
2309
- { name: 'entryPoint', type: 'string', value: '' },
2310
- ],
2311
- },
2312
- debug: { runtime: 'bpmnEngine' },
2313
- };
2227
+ /** Numeric dotted compare: '1.10' > '1.9'. */
2228
+ function compareVersions(a, b) {
2229
+ const pa = a.split('.').map(Number);
2230
+ const pb = b.split('.').map(Number);
2231
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
2232
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
2233
+ if (d !== 0)
2234
+ return d;
2235
+ }
2236
+ return 0;
2237
+ }
2238
+ /**
2239
+ * The inline agent's bundled versions, oldest first, and the newest of them.
2240
+ * The newest bundled pair is the default, so there is no version literal in
2241
+ * this file: when the registry serves a newer version, `check:definitions`
2242
+ * reports the missing pair, `sync:definitions` adds it, and the default follows.
2243
+ */
2244
+ const INLINE_AGENT_VERSIONS = Object.keys(DEFS)
2245
+ .filter((k) => k.startsWith(`${INLINE_AGENT_TYPE}@`))
2246
+ .map((k) => k.slice(INLINE_AGENT_TYPE.length + 1))
2247
+ .sort(compareVersions);
2248
+ const INLINE_AGENT_DEFAULT_VERSION = INLINE_AGENT_VERSIONS[INLINE_AGENT_VERSIONS.length - 1];
2249
+ /** True when a definition declares the agent's `memory` artifact handle. */
2250
+ function declaresMemoryHandle(def) {
2251
+ return (def?.handleConfiguration ?? []).some((g) => (g?.handles ?? []).some((h) => h?.id === 'memory'));
2252
+ }
2253
+ /**
2254
+ * The definition for the INLINE autonomous agent: the bundled pair for
2255
+ * `version` (the newest by default), plus an explicit overlay of the details
2256
+ * the registry payload does not carry.
2257
+ *
2258
+ * This is the one node type in the agent family that is the SAME on every
2259
+ * tenant — it carries its own configuration instead of referencing a resource —
2260
+ * so `uip maestro flow registry get uipath.agent.autonomous` is a direct oracle
2261
+ * and the bundle entry is the registry's definition as last synced
2262
+ * (`sync:definitions` writes it through unchanged; `check:definitions --deep`
2263
+ * reports content that has moved since). Versions and content both come from
2264
+ * there.
2265
+ *
2266
+ * The overlay, and nothing else:
2267
+ *
2268
+ * * `outputDefinition.output.source` is `=result.response`. The registry's
2269
+ * definition declares no `source`; the deployed corpus (29 `uipath.agent.autonomous` nodes, 05-18 → 07-29) has
2270
+ * `=result.response` in 27 of 27 that declare outputs, and the engine's own
2271
+ * inline dispatch returns `{ response: { …declared keys } }`. Without it the
2272
+ * node's `output` is the whole result envelope rather than the declared keys.
2273
+ * It is applied at every version; the registry payload has no `source`.
2274
+ * * The `memory` handle is kept only when the flow attaches memory. It is
2275
+ * LOCAL content (#431) on the bundled 1.4, absent from the registry's 1.4
2276
+ * (`check.ts` AGENT_MEMORY_NOT_AUTHORABLE), so an agent without memory gets
2277
+ * the registry's handle set.
2278
+ *
2279
+ * NO per-instance schema goes on the definition: `definitions[]` is keyed by
2280
+ * node TYPE, and every inline agent in a flow shares this one entry. A flow
2281
+ * with two agents (an analyst and a writer, say) therefore cannot have both
2282
+ * contracts here — whichever was emitted last would win, and every read against
2283
+ * the other resolved to the wrong shape (GA row 7's port: 25 undeclared-field
2284
+ * errors on a correct flow). The authored contract goes on the NODE
2285
+ * (`node.outputs.output.schema`, in the `inlineAgent` arm), where a reader that
2286
+ * refines per node picks it up.
2287
+ *
2288
+ * The bundled 1.2 is the pre-#956 default, kept so a flow this SDK compiled
2289
+ * before — and a decompile of one, which pins `{ version: '1.2' }` — still
2290
+ * compiles. The corpus also carries 1.1 and 1.3; `shared/sample-flows/
2291
+ * inline-agent.flow` says 1.0, which appears in none of the 29.
2292
+ */
2293
+ function inlineAgentDef(version, withMemory) {
2294
+ const bundled = DEFS[`${INLINE_AGENT_TYPE}@${version}`];
2295
+ if (!bundled)
2296
+ return undefined;
2297
+ const def = clone(bundled);
2298
+ const output = def.outputDefinition?.output;
2299
+ if (output)
2300
+ output.source = '=result.response';
2301
+ if (!withMemory) {
2302
+ for (const group of def.handleConfiguration ?? []) {
2303
+ if (Array.isArray(group?.handles))
2304
+ group.handles = group.handles.filter((h) => h?.id !== 'memory');
2305
+ }
2306
+ def.handleConfiguration = (def.handleConfiguration ?? []).filter((g) => g?.handles?.length);
2307
+ }
2308
+ return def;
2314
2309
  }
2315
2310
  /**
2316
2311
  * Classify a RENDERED input value (the output of {@link renderValue}).
@@ -3934,7 +3929,7 @@ function describeDeclared(bindings) {
3934
3929
  * exactly (design §2.6/§4.2): the named version resolves or compilation fails,
3935
3930
  * and a contributor that ignored the author's requested version is rejected.
3936
3931
  */
3937
- function resolveContributionDefinition(ref, requestedVersion, where) {
3932
+ export function resolveContributionDefinition(ref, requestedVersion, where) {
3938
3933
  if (!ref || typeof ref.nodeType !== 'string' || !ref.nodeType || typeof ref.version !== 'string' || !ref.version) {
3939
3934
  throw new Error(`${where}: contribute() returned a DefinitionReference without an exact nodeType + version.`);
3940
3935
  }
@@ -4381,9 +4376,9 @@ function newScope(outputs, vars = []) {
4381
4376
  * note at `uipath.ts:118-123`, MST-9544).
4382
4377
  */
4383
4378
  /**
4384
- * Wire every parked `.rejoin('<step>')` edge, now that the whole scope exists.
4379
+ * Wire every parked `.stepToRef('<step>')` edge, now that the whole scope exists.
4385
4380
  *
4386
- * An unresolved target should be impossible — `check`'s REJOIN_UNKNOWN_TARGET
4381
+ * An unresolved target should be impossible — `check`'s REF_UNKNOWN_TARGET
4387
4382
  * runs first and names the steps that do exist — but a serializer that silently
4388
4383
  * dropped the edge would emit a flow whose handler goes nowhere, which is the
4389
4384
  * exact class of failure `.onError` used to have (an error path that vanishes in
@@ -4419,10 +4414,16 @@ function typeAgentInputSchemas(scope, globals) {
4419
4414
  if (g.direction === 'in' && g.triggerNodeId)
4420
4415
  lookup.set(`${g.triggerNodeId}.output.${g.id}`, { type: g.type, schema: g.schema });
4421
4416
  }
4422
- for (const { properties, refs } of scope.agentSchemas) {
4417
+ for (const { schema, refs } of scope.agentSchemas) {
4423
4418
  for (const r of refs) {
4424
- properties[r.flatName] = jsonSchemaForRef(r.binding, lookup);
4419
+ schema.properties[r.flatName] = jsonSchemaForRef(r.binding, lookup);
4425
4420
  }
4421
+ // A file property is a `$ref`, and a `$ref` resolves against THIS schema's
4422
+ // `definitions` — so the block is added exactly when a file property is
4423
+ // present (a schema with no file keeps the shape it always had).
4424
+ const hasFile = Object.values(schema.properties).some((p) => p?.$ref === JOB_ATTACHMENT_REF);
4425
+ if (hasFile)
4426
+ schema.definitions = { ...schema.definitions, 'job-attachment': jobAttachmentDefinition() };
4426
4427
  }
4427
4428
  }
4428
4429
  /** One property's schema — `{}` when the type cannot be established. See {@link typeAgentInputSchemas}. */
@@ -4512,17 +4513,58 @@ function walkSchemaType(schema, segments) {
4512
4513
  }
4513
4514
  return typeof current?.type === 'string' ? current.type : undefined;
4514
4515
  }
4515
- /** A resolved type → its JSON-Schema property, with v1's two normalisations. */
4516
+ /**
4517
+ * A resolved type → its JSON-Schema property.
4518
+ *
4519
+ * Every type a flow can DECLARE (`types.*`) has its own case, and the switch is
4520
+ * exhaustive over {@link TypeDesc}: adding a type to `types` without deciding
4521
+ * what an agent sees for it is a compile error here, not a silent `{}`.
4522
+ *
4523
+ * `file` → `{ $ref: '#/definitions/job-attachment' }`; the caller adds the
4524
+ * definition (see `typeAgentInputSchemas`).
4525
+ *
4526
+ * Typeless `{}` is the answer for `any` and for "unknown" (`undefined`,
4527
+ * or a type string from a node definition that is not in the SDK's
4528
+ * vocabulary): that is v1's own rule for a type it cannot establish
4529
+ * (`buildAgentInputTypeResolver` → `'any'` → a property with no `type`), and it
4530
+ * keeps the runtime's pydantic model from refusing a value the SDK only
4531
+ * guessed at.
4532
+ */
4516
4533
  function jsonSchemaForType(type) {
4517
- // `any`, `undefined` and the file/octet aliases all mean "do not constrain".
4518
- // (v1 maps `octet-stream`/`file` to `object`; the SDK declares nothing rather
4519
- // than guess, because a file input reaches an agent through a `$ref` the
4520
- // sidecar does not carry.)
4521
- if (!type || type === 'any' || type === 'file' || type === 'octet-stream')
4534
+ if (type === 'octet-stream')
4535
+ return { $ref: JOB_ATTACHMENT_REF }; // v1's alias for file
4536
+ if (type === undefined || !isTypeDesc(type))
4522
4537
  return {};
4523
- if (type === 'array')
4524
- return { type: 'array', items: {} };
4525
- return { type };
4538
+ switch (type) {
4539
+ case 'file': return { $ref: JOB_ATTACHMENT_REF };
4540
+ case 'any': return {};
4541
+ // A bare `type: 'array'` makes the runtime's `jsonschema_to_pydantic`
4542
+ // reject a valid array (v1's note at `uipath.ts:118-123`, MST-9544).
4543
+ case 'array': return { type: 'array', items: {} };
4544
+ case 'string':
4545
+ case 'number':
4546
+ case 'integer':
4547
+ case 'boolean':
4548
+ case 'object':
4549
+ return { type };
4550
+ // The case vocabulary's widths and date kinds, as JSON Schema spells them
4551
+ // (the same table `case/entry-points.ts` projects with).
4552
+ case 'float': return { type: 'number', format: 'float' };
4553
+ case 'double': return { type: 'number', format: 'double' };
4554
+ case 'date': return { type: 'string', format: 'date' };
4555
+ case 'datetime': return { type: 'string', format: 'date-time' };
4556
+ // A case `jsonSchema` variable's shape lives in its `body`, which this
4557
+ // lookup does not carry — so its type is not established here.
4558
+ case 'jsonSchema': return {};
4559
+ default: {
4560
+ const unmapped = type;
4561
+ throw new Error(`jsonSchemaForType: no agent schema mapping for type '${String(unmapped)}'`);
4562
+ }
4563
+ }
4564
+ }
4565
+ const TYPE_DESCS = new Set(Object.values(types));
4566
+ function isTypeDesc(type) {
4567
+ return TYPE_DESCS.has(type);
4526
4568
  }
4527
4569
  /**
4528
4570
  * An End node's `outputs` map: every variable this scope exposes as a flow
@@ -4595,8 +4637,8 @@ function renameRefs(text, rename) {
4595
4637
  /** Every step name a flow declares in its own scope (not its children's). */
4596
4638
  function stepNamesOf(steps, out = []) {
4597
4639
  for (const s of steps) {
4598
- // `return` and `rejoin` carry a synthetic `name` and create no node named
4599
- // after it (an End id is claimed as `end`; a rejoin is one edge).
4640
+ // `return` carries a synthetic `name` but creates no node named after it (an
4641
+ // End id is claimed as `end`); `stepToRef` has no `name` at all (it is one edge).
4600
4642
  if ('name' in s && s.kind !== 'return')
4601
4643
  out.push(s.name);
4602
4644
  if (s.kind === 'branch') {
@@ -4638,6 +4680,73 @@ export function serialize(built, opts = {}) {
4638
4680
  * A bindings entry the node does not reference is not a bound resource.
4639
4681
  */
4640
4682
  const resourceBindings = new Map();
4683
+ /**
4684
+ * The resolved definition of every contribution-lowered node, by node id, so an
4685
+ * `error` edge from one can be checked against its definition (#943). Typed
4686
+ * families are not recorded: their definitions are the SDK's own.
4687
+ */
4688
+ const contributionDefs = new Map();
4689
+ /** Refuse an `error` edge from a contribution-lowered node whose definition has no error port. */
4690
+ function refuseErrorEdgeWithoutPort(nodeId, stepName) {
4691
+ const def = contributionDefs.get(nodeId);
4692
+ if (def !== undefined && !definitionHasErrorPort(def))
4693
+ throw new Error(errorPortRefusal(stepName, def));
4694
+ }
4695
+ /**
4696
+ * Hoist a contribution's own bindings, then close every `<bindings.NAME>` its
4697
+ * definition references and it did not supply, from the definition's own
4698
+ * `model.bindings` (flow-builder-sdk#942). Shared by the three contribution
4699
+ * sites (step, trigger, entry point), so a `rawNode` and a hand-written
4700
+ * subclass get the same entries a typed emitter would. `check` runs the same
4701
+ * plan dry (`checkContribution` in `check.ts`), so it reports what this throws.
4702
+ */
4703
+ function hoistContributionBindings(def, supplied, where) {
4704
+ for (const b of supplied ?? [])
4705
+ resourceBindings.set(b.id, b);
4706
+ const plan = planContributionBindings(def, supplied);
4707
+ if (plan.problems.length) {
4708
+ throw new BindingRefusal('CONTRIBUTION_BINDING_UNRESOLVED', `${where}: ${plan.problems.join(' ')}`);
4709
+ }
4710
+ if (plan.entries.length === 0)
4711
+ return;
4712
+ // A process reference is the name + folderPath pair every typed process step
4713
+ // emits: go through the same builder, so a raw node and a typed step on ONE
4714
+ // process share one pair instead of shadowing each other.
4715
+ const byName = new Map(plan.entries.map((e) => [e.name, e]));
4716
+ const pairName = byName.get('name');
4717
+ const pairFolder = byName.get('folderPath');
4718
+ if (plan.resource === 'process' && plan.entries.length === 2 && pairName?.propertyAttribute === 'name'
4719
+ && pairFolder?.propertyAttribute === 'folderPath' && pairName.default !== undefined
4720
+ && pairFolder.default !== undefined) {
4721
+ const { entries } = processResourceBindings({
4722
+ name: pairName.default,
4723
+ folderPath: pairFolder.default,
4724
+ resourceSubType: pairName.resourceSubType,
4725
+ resourceKey: plan.resourceKey,
4726
+ }, resourceBindings);
4727
+ for (const b of entries) {
4728
+ // processResourceBindings types the subtype to the families it knows; keep
4729
+ // the definition's own spelling (or none) verbatim.
4730
+ if (pairName.resourceSubType === undefined)
4731
+ delete b.resourceSubType;
4732
+ resourceBindings.set(b.id, b);
4733
+ }
4734
+ return;
4735
+ }
4736
+ for (const e of plan.entries) {
4737
+ const same = [...resourceBindings.values()].some((b) => b.resource === e.resource
4738
+ && b.resourceKey === e.resourceKey && b.name === e.name && b.propertyAttribute === e.propertyAttribute);
4739
+ if (same)
4740
+ continue;
4741
+ const slug = e.resourceKey.replace(/[^A-Za-z0-9]/g, '') || 'Resource';
4742
+ const cap = (t) => t.charAt(0).toUpperCase() + t.slice(1);
4743
+ const stem = `b${cap(slug)}${cap(e.name.replace(/[^A-Za-z0-9]/g, ''))}`;
4744
+ let id = stem;
4745
+ for (let n = 2; resourceBindings.has(id); n++)
4746
+ id = `${stem}${n}`;
4747
+ resourceBindings.set(id, { id, ...e });
4748
+ }
4749
+ }
4641
4750
  /**
4642
4751
  * Extra files the flow needs beside the `.flow` — see `SerializeOpts.sidecars`.
4643
4752
  * Written into the caller's array when one was passed, so a caller that wants them
@@ -4689,7 +4798,7 @@ export function serialize(built, opts = {}) {
4689
4798
  /**
4690
4799
  * `uid` for a node that IS an author's step, recording which id that step got.
4691
4800
  *
4692
- * `.rejoin('<step>')` names a step, and the id it was emitted under is not
4801
+ * `.stepToRef('<step>')` names a step, and the id it was emitted under is not
4693
4802
  * derivable from the name: `uid` sanitizes, de-duplicates against every id in
4694
4803
  * the file, and hands a subflow COPY a prefixed one from the rename map. So
4695
4804
  * the mapping is recorded where it is decided rather than reconstructed later
@@ -4733,13 +4842,15 @@ export function serialize(built, opts = {}) {
4733
4842
  * (design §2.6): resolution is exact — `<type>@<version>` from the bundle, or
4734
4843
  * the version of the definition already in hand — and a version the SDK
4735
4844
  * cannot resolve exactly fails compilation rather than floating to a default.
4845
+ * An undefined `label` takes the definition's `display.label`, which is what
4846
+ * the canvas shows on a freshly dropped node.
4736
4847
  */
4737
4848
  function makeNode(type, id, label, o = {}) {
4738
4849
  let def = o.def ?? defByType.get(type);
4739
4850
  if (o.requestedVersion !== undefined && def?.version !== o.requestedVersion) {
4740
4851
  const exact = o.def ? undefined : DEFS[`${type}@${o.requestedVersion}`];
4741
4852
  if (!exact) {
4742
- throw new Error(`"${label}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
4853
+ throw new Error(`"${label ?? id}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
4743
4854
  + `${type}@${def?.version ?? '<none>'}. Versions are exact — omit { version } to use `
4744
4855
  + `the pinned default, or request a version this SDK ships.`);
4745
4856
  }
@@ -4752,7 +4863,7 @@ export function serialize(built, opts = {}) {
4752
4863
  type,
4753
4864
  typeVersion: def?.version ?? '1.0.0',
4754
4865
  ui: { position: { x: 256 + col * 288, y: 144 + (o.parentId ? 160 : 0) }, size: { width: 96, height: 96 }, collapsed: false },
4755
- display: { label, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
4866
+ display: { label: label ?? def?.display?.label ?? id, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
4756
4867
  };
4757
4868
  col++;
4758
4869
  // A Data Fabric node gets no node-level `model`, whichever path emits it
@@ -4876,7 +4987,7 @@ export function serialize(built, opts = {}) {
4876
4987
  if (customTrigger) {
4877
4988
  const contribution = customTrigger.contribute(built.triggerOptions?.version !== undefined ? { requestedVersion: built.triggerOptions.version } : {});
4878
4989
  const def = resolveContributionDefinition(contribution.definition, built.triggerOptions?.version, 'trigger');
4879
- const node = makeNode(def.nodeType, triggerId, def.display?.label ?? 'Start', { def, requestedVersion: built.triggerOptions?.version });
4990
+ const node = makeNode(def.nodeType, triggerId, built.triggerOptions?.label ?? def.display?.label ?? 'Start', { def, requestedVersion: built.triggerOptions?.version });
4880
4991
  const inputs = renderDeep(contribution.inputs ?? {}, NO_RENAME);
4881
4992
  if (def.model?.entryPointId === true && inputs.entryPointId === undefined) {
4882
4993
  inputs.entryPointId = stableId(`${built.id}:start:entryPoint`);
@@ -4884,25 +4995,26 @@ export function serialize(built, opts = {}) {
4884
4995
  inputs.isDefaultEntryPoint = true;
4885
4996
  }
4886
4997
  node.inputs = inputs;
4887
- for (const b of contribution.bindings ?? [])
4888
- resourceBindings.set(b.id, b);
4998
+ hoistContributionBindings(def, contribution.bindings, 'trigger');
4889
4999
  root.nodes.push(node);
4890
5000
  }
5001
+ // The author's `.trigger(spec, { label })`; makeNode falls back to the definition's label.
5002
+ const rootLabel = built.triggerOptions?.label;
4891
5003
  const trigger = customTrigger
4892
5004
  ? root.nodes[root.nodes.length - 1]
4893
5005
  : eventTriggerRc
4894
- ? makeNode(eventTriggerRc.nodeType, triggerId, built.trigger?.kind === 'event' ? 'Event trigger' : 'Start', { def: eventTriggerRc.v1def, requestedVersion: built.triggerOptions?.version })
5006
+ ? makeNode(eventTriggerRc.nodeType, triggerId, rootLabel, { def: eventTriggerRc.v1def, requestedVersion: built.triggerOptions?.version })
4895
5007
  : built.trigger?.kind === 'scheduled'
4896
- ? makeNode(NODE_TYPE.scheduledTrigger, triggerId, 'Scheduled trigger', {
5008
+ ? makeNode(NODE_TYPE.scheduledTrigger, triggerId, rootLabel, {
4897
5009
  requestedVersion: scheduledVersion(built.trigger.inputs, built.triggerOptions?.version),
4898
5010
  })
4899
5011
  : built.trigger?.kind === 'form'
4900
- ? makeNode(NODE_TYPE.formTrigger, triggerId, 'Form trigger', { requestedVersion: built.triggerOptions?.version })
5012
+ ? makeNode(NODE_TYPE.formTrigger, triggerId, rootLabel, { requestedVersion: built.triggerOptions?.version })
4901
5013
  : built.trigger?.kind === 'conversation'
4902
- ? makeNode(NODE_TYPE.conversationTrigger, triggerId, 'Conversation trigger', { requestedVersion: built.triggerOptions?.version })
5014
+ ? makeNode(NODE_TYPE.conversationTrigger, triggerId, rootLabel, { requestedVersion: built.triggerOptions?.version })
4903
5015
  : built.trigger?.kind === 'voice'
4904
- ? makeNode(NODE_TYPE.voiceTrigger, triggerId, 'Incoming call trigger', { requestedVersion: built.triggerOptions?.version })
4905
- : makeNode(NODE_TYPE.trigger, triggerId, 'Manual trigger', { requestedVersion: built.triggerOptions?.version });
5016
+ ? makeNode(NODE_TYPE.voiceTrigger, triggerId, rootLabel, { requestedVersion: built.triggerOptions?.version })
5017
+ : makeNode(NODE_TYPE.trigger, triggerId, rootLabel, { requestedVersion: built.triggerOptions?.version });
4906
5018
  if (!customTrigger) {
4907
5019
  trigger.inputs = eventTriggerRc && built.trigger?.kind === 'event'
4908
5020
  ? {
@@ -5020,8 +5132,8 @@ export function serialize(built, opts = {}) {
5020
5132
  if (contribution.outputs) {
5021
5133
  node.outputs = { ...(node.outputs ?? {}), ...subNodeId(clone(contribution.outputs), id) };
5022
5134
  }
5023
- for (const b of contribution.bindings ?? [])
5024
- resourceBindings.set(b.id, b);
5135
+ hoistContributionBindings(def, contribution.bindings, where);
5136
+ contributionDefs.set(id, def);
5025
5137
  scope.nodes.push(node);
5026
5138
  for (const t of tails)
5027
5139
  addEdge(scope, t, id);
@@ -5179,16 +5291,30 @@ export function serialize(built, opts = {}) {
5179
5291
  // required: it is what the platform scaffolds, so a flow that reads
5180
5292
  // `content` is a real (if toy) flow. `check` says so.
5181
5293
  : { content: 'string' };
5182
- // Rich options select a newer definition: `mode` needs the registry's
5183
- // CURRENT 1.3 (which adds the field), `memory` needs 1.4 (which adds the
5184
- // handle — derived from 1.3 by the workbench v1.4 migration; the tenant
5185
- // registry still serves 1.3). Without either, the 1.2 literal is emitted
5186
- // byte-identically.
5187
- const def = s.memory !== undefined
5188
- ? clone(DEFS[`${INLINE_AGENT_TYPE}@1.4`])
5189
- : s.mode !== undefined
5190
- ? clone(DEFS[`${INLINE_AGENT_TYPE}@1.3`])
5191
- : inlineAgentDef({ returns });
5294
+ // ONE default version for every call shape — the newest bundled pair (see
5295
+ // `inlineAgentDef`). `mode` and `memory` used to pick the version too (1.3
5296
+ // and 1.4), so a plain agent got a superseded 1.2 and each option moved
5297
+ // the node to another version as a side effect (#956). Both now live in
5298
+ // the sidecar and on edges; neither selects a definition. An explicit
5299
+ // `{ version }` (what decompile writes back) resolves exactly.
5300
+ const version = step.options?.version ?? INLINE_AGENT_DEFAULT_VERSION;
5301
+ // `definitions[]` holds one entry per type@version, so a flow where any
5302
+ // agent attaches memory keeps the handle for all of them.
5303
+ const withMemory = s.memory !== undefined
5304
+ || declaresMemoryHandle(emittedDefs.get(`${INLINE_AGENT_TYPE}@${version}`));
5305
+ const def = inlineAgentDef(version, withMemory);
5306
+ if (!def) {
5307
+ throw new Error(`"${step.name}" requests ${INLINE_AGENT_TYPE}@${version}, but this SDK resolves only `
5308
+ + `${INLINE_AGENT_VERSIONS.map((v) => `${INLINE_AGENT_TYPE}@${v}`).join(', ')}. Versions are exact — `
5309
+ + `omit { version } to use the default (${INLINE_AGENT_DEFAULT_VERSION}), or request a version this SDK ships.`);
5310
+ }
5311
+ if (s.memory !== undefined && !declaresMemoryHandle(def)) {
5312
+ throw new Error(`"${step.name}" attaches \`memory\`, but ${INLINE_AGENT_TYPE}@${version} declares no \`memory\` handle. `
5313
+ + (version === INLINE_AGENT_DEFAULT_VERSION
5314
+ ? `Pin a version that has it with inlineAgent({ ..., version: '<x.y>' }) `
5315
+ + `(bundled with memory: ${INLINE_AGENT_VERSIONS.filter((v) => declaresMemoryHandle(DEFS[`${INLINE_AGENT_TYPE}@${v}`])).join(', ') || 'none'}), or drop \`memory\`.`
5316
+ : `Drop { version: '${version}' } to use ${INLINE_AGENT_DEFAULT_VERSION}, or drop \`memory\`.`));
5317
+ }
5192
5318
  const id = stepUid(scope, step.name);
5193
5319
  // `source` is the agent's directory name. Derived from the flow id + step
5194
5320
  // name rather than random so a recompile is byte-identical — a v4-shaped uuid
@@ -5277,7 +5403,7 @@ export function serialize(built, opts = {}) {
5277
5403
  // The schema's property TYPES need this scope's finished variables, which
5278
5404
  // do not exist yet — park them (the `properties` object is filled in
5279
5405
  // place when the scope closes). See `typeAgentInputSchemas`.
5280
- scope.agentSchemas.push({ properties: agentJson.inputSchema.properties, refs: plan.refs });
5406
+ scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
5281
5407
  // GROUNDING — one resource node per context index, joined to the agent's own
5282
5408
  // `context` handle. An ARTIFACT, not a step: it takes no incoming control
5283
5409
  // flow, produces no output, and never reaches the End node. The edge runs OUT
@@ -5626,7 +5752,7 @@ export function serialize(built, opts = {}) {
5626
5752
  name: app.name,
5627
5753
  appName: app.name,
5628
5754
  displayName: app.name,
5629
- folderPath: app.folderPath ?? '',
5755
+ folderPath: escalationAppFolder(app),
5630
5756
  inputSchema: fieldSchema(app.inputs),
5631
5757
  outputSchema: fieldSchema(app.outputs),
5632
5758
  },
@@ -5693,21 +5819,28 @@ export function serialize(built, opts = {}) {
5693
5819
  // downstream in the engine.
5694
5820
  const s = spec.inputs;
5695
5821
  const nodeInputs = s.inputs ?? {};
5696
- const subType = spec.kind === 'apiWorkflow' ? 'Api'
5697
- : spec.kind === 'agenticProcess' ? 'ProcessOrchestration'
5698
- : spec.kind === 'publishedFunction' ? 'Function'
5699
- : 'Process';
5700
5822
  const fireAndForget = spec.kind === 'agenticProcess' && spec.inputs.completion === 'fire-and-forget';
5701
5823
  const def = spec.kind === 'publishedFunction'
5702
- ? publishedFunctionDef({ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, inputs: nodeInputs, returns: spec.inputs.returns })
5824
+ ? publishedFunctionDef({ key: spec.inputs.key, name: s.name, folderPath: spec.inputs.folderPath, inputs: nodeInputs, returns: spec.inputs.returns })
5703
5825
  : spec.kind === 'apiWorkflow'
5704
- ? apiWorkflowDef({ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, inputs: nodeInputs, returns: spec.inputs.returns })
5826
+ ? apiWorkflowDef({ key: spec.inputs.key, name: s.name, folderPath: spec.inputs.folderPath, inputs: nodeInputs, returns: spec.inputs.returns })
5705
5827
  : spec.kind === 'agenticProcess'
5706
- ? agenticProcessDef({ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, inputs: nodeInputs, returns: spec.inputs.returns, form: spec.inputs.form })
5707
- : rpaWorkflowDef({ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, inputs: nodeInputs, returns: spec.inputs.returns });
5828
+ ? agenticProcessDef({
5829
+ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, location: spec.inputs.location,
5830
+ projectId: spec.inputs.projectId, inputs: nodeInputs, returns: spec.inputs.returns, form: spec.inputs.form,
5831
+ })
5832
+ : rpaWorkflowDef({
5833
+ key: spec.inputs.key, name: s.name, folderPath: s.folderPath, location: spec.inputs.location,
5834
+ projectId: spec.inputs.projectId, inputs: nodeInputs, returns: spec.inputs.returns,
5835
+ });
5708
5836
  // The bindings first: two steps on the same process share ONE pair, and the
5709
5837
  // definition's `model.context` names ids that have to exist.
5710
- const { entries } = processResourceBindings({ name: s.name, folderPath: s.folderPath, resourceSubType: subType }, resourceBindings);
5838
+ // Read off the definition: one kind carries several subtypes (agenticProcess by `form`).
5839
+ // The pair's key and folder come off it too: an in-solution step's resourceKey is the
5840
+ // bare sibling key and its folder is `''`.
5841
+ const subType = def.model.bindings.resourceSubType;
5842
+ const pairFolder = def.model.bindings.values.find((v) => v.name === 'folderPath').default;
5843
+ const { entries } = processResourceBindings({ name: s.name, folderPath: pairFolder, resourceSubType: subType, resourceKey: def.model.bindings.resourceKey }, resourceBindings);
5711
5844
  for (const b of entries)
5712
5845
  resourceBindings.set(b.id, b);
5713
5846
  const id = stepUid(scope, step.name);
@@ -5917,7 +6050,7 @@ export function serialize(built, opts = {}) {
5917
6050
  });
5918
6051
  if (plan.refs.length > 0) {
5919
6052
  const agentJson = sidecars[sidecars.length - 1].json;
5920
- scope.agentSchemas.push({ properties: agentJson.inputSchema.properties, refs: plan.refs });
6053
+ scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
5921
6054
  }
5922
6055
  for (const t of tails)
5923
6056
  addEdge(scope, t, id);
@@ -6338,7 +6471,7 @@ export function serialize(built, opts = {}) {
6338
6471
  * The handler is its OWN path, and it still is: its leftover tails get their
6339
6472
  * own End rather than rejoining the chain, because `.onError` must not
6340
6473
  * SILENTLY change what runs after the step. What changed is that an author can
6341
- * now ASK — `h.rejoin('<step>')` emits an edge into a step the success path
6474
+ * now ASK — `h.stepToRef('<step>')` emits an edge into a step the success path
6342
6475
  * also reaches, and returns no tail, so nothing is capped. Never inferred:
6343
6476
  * a handler that just runs out of steps ends exactly as it always did.
6344
6477
  *
@@ -6403,8 +6536,10 @@ export function serialize(built, opts = {}) {
6403
6536
  throw new Error(`.stepToList('${step.port}', …) after step "${source}": emitted node "${nodeId}" not found in scope`);
6404
6537
  // Only the error port carries this: without it the platform routes but Studio
6405
6538
  // Web HIDES the handle, so no human can see the handler.
6406
- if (step.port === 'error')
6539
+ if (step.port === 'error') {
6540
+ refuseErrorEdgeWithoutPort(nodeId, source);
6407
6541
  node.inputs = { ...(node.inputs ?? {}), errorHandlingEnabled: true };
6542
+ }
6408
6543
  const handlerTails = emit(handler, [{ node: nodeId, port: step.port }], scope, parentId, breakTarget);
6409
6544
  if (handlerTails.length) {
6410
6545
  const endId = uid(scope, `${source}${step.port === 'error' ? 'Error' : step.port.charAt(0).toUpperCase() + step.port.slice(1)}End`);
@@ -6805,6 +6940,7 @@ export function serialize(built, opts = {}) {
6805
6940
  throw new Error(`.stepToRef('${step.port}', '${step.target}') has no step to leave from.`);
6806
6941
  refuseRewire('.stepToRef', step.port, lastAction ?? '?', tails);
6807
6942
  if (step.port === 'error') {
6943
+ refuseErrorEdgeWithoutPort(from.node, lastAction ?? from.node);
6808
6944
  const node = scope.nodes.find((n) => n.id === from.node);
6809
6945
  if (node)
6810
6946
  node.inputs = { ...(node.inputs ?? {}), errorHandlingEnabled: true };
@@ -6885,19 +7021,20 @@ export function serialize(built, opts = {}) {
6885
7021
  takenIds.add(ep.id);
6886
7022
  const spec = ep.trigger;
6887
7023
  const requestedVersion = ep.options?.version;
7024
+ // The author's label; makeNode falls back to the definition's label.
7025
+ const epLabel = ep.options?.label;
6888
7026
  const customEp = spec instanceof FlowTrigger && !('kind' in spec) ? spec : undefined;
6889
7027
  const epSpec = spec;
6890
7028
  let node;
6891
7029
  if (customEp) {
6892
7030
  const contribution = customEp.contribute(requestedVersion !== undefined ? { requestedVersion } : {});
6893
7031
  const def = resolveContributionDefinition(contribution.definition, requestedVersion, `entryPoint "${ep.id}"`);
6894
- node = makeNode(def.nodeType, ep.id, def.display?.label ?? ep.id, { def, requestedVersion });
7032
+ node = makeNode(def.nodeType, ep.id, ep.options?.label ?? def.display?.label ?? ep.id, { def, requestedVersion });
6895
7033
  node.inputs = {
6896
7034
  entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`),
6897
7035
  ...renderDeep(contribution.inputs ?? {}, NO_RENAME),
6898
7036
  };
6899
- for (const b of contribution.bindings ?? [])
6900
- resourceBindings.set(b.id, b);
7037
+ hoistContributionBindings(def, contribution.bindings, `entryPoint "${ep.id}"`);
6901
7038
  }
6902
7039
  else if (epSpec.kind === 'event') {
6903
7040
  if (!opts.library)
@@ -6905,7 +7042,7 @@ export function serialize(built, opts = {}) {
6905
7042
  const sub = epSpec.subscription;
6906
7043
  const rc = opts.library.resolveEvent('trigger', sub.connector, sub.event, sub.version, sub.object);
6907
7044
  refuseUnverifiedByoa(opts, 'trigger', sub);
6908
- node = makeNode(rc.nodeType, ep.id, 'Event trigger', { def: rc.v1def, requestedVersion });
7045
+ node = makeNode(rc.nodeType, ep.id, epLabel, { def: rc.v1def, requestedVersion });
6909
7046
  node.inputs = {
6910
7047
  entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`),
6911
7048
  detail: eventDetail(rc, sub, ep.id, opts, `onEvent() entry point "${ep.id}"`),
@@ -6920,7 +7057,7 @@ export function serialize(built, opts = {}) {
6920
7057
  resourceBindings.set(b.id, b);
6921
7058
  }
6922
7059
  else if (epSpec.kind === 'scheduled') {
6923
- node = makeNode(NODE_TYPE.scheduledTrigger, ep.id, 'Scheduled trigger', {
7060
+ node = makeNode(NODE_TYPE.scheduledTrigger, ep.id, epLabel, {
6924
7061
  requestedVersion: scheduledVersion(epSpec.inputs, requestedVersion),
6925
7062
  });
6926
7063
  node.inputs = {
@@ -6929,22 +7066,22 @@ export function serialize(built, opts = {}) {
6929
7066
  };
6930
7067
  }
6931
7068
  else if (epSpec.kind === 'form') {
6932
- node = makeNode(NODE_TYPE.formTrigger, ep.id, 'Form trigger', { requestedVersion });
7069
+ node = makeNode(NODE_TYPE.formTrigger, ep.id, epLabel, { requestedVersion });
6933
7070
  node.inputs = {
6934
7071
  entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`),
6935
7072
  schema: { fields: formTriggerFields(ep.inputs), outcomes: [] },
6936
7073
  };
6937
7074
  }
6938
7075
  else if (epSpec.kind === 'conversation') {
6939
- node = makeNode(NODE_TYPE.conversationTrigger, ep.id, 'Conversation trigger', { requestedVersion });
7076
+ node = makeNode(NODE_TYPE.conversationTrigger, ep.id, epLabel, { requestedVersion });
6940
7077
  node.inputs = { entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`) };
6941
7078
  }
6942
7079
  else if (epSpec.kind === 'voice') {
6943
- node = makeNode(NODE_TYPE.voiceTrigger, ep.id, 'Incoming call trigger', { requestedVersion });
7080
+ node = makeNode(NODE_TYPE.voiceTrigger, ep.id, epLabel, { requestedVersion });
6944
7081
  node.inputs = { entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`) };
6945
7082
  }
6946
7083
  else if (epSpec.kind === 'manual') {
6947
- node = makeNode(NODE_TYPE.trigger, ep.id, 'Manual trigger', { requestedVersion });
7084
+ node = makeNode(NODE_TYPE.trigger, ep.id, epLabel, { requestedVersion });
6948
7085
  node.inputs = { entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`) };
6949
7086
  }
6950
7087
  else {