@uipath/maestro-builder-sdk 6.42.0 → 6.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/api-index.md +99 -99
  2. package/dist/api-members.md +307 -307
  3. package/dist/check.d.ts +0 -19
  4. package/dist/check.js +40 -1036
  5. package/dist/compile-cli.js +2 -1
  6. package/dist/core/actions.d.ts +1 -713
  7. package/dist/core/actions.js +0 -193
  8. package/dist/core/agent-sidecar.d.ts +186 -0
  9. package/dist/core/agent-sidecar.js +409 -0
  10. package/dist/core/agent-tool-resources.d.ts +62 -0
  11. package/dist/core/agent-tool-resources.js +81 -0
  12. package/dist/{config.js → core/connector-configuration.js} +2 -2
  13. package/dist/core/node-id-placeholder.d.ts +8 -0
  14. package/dist/core/node-id-placeholder.js +10 -0
  15. package/dist/core/ts-literal.d.ts +6 -0
  16. package/dist/core/ts-literal.js +15 -0
  17. package/dist/core/typed-raw-node.js +0 -5
  18. package/dist/decompile.js +34 -822
  19. package/dist/flow-node-registry.js +2 -1
  20. package/dist/flow-node-type.d.ts +130 -6
  21. package/dist/flow-sdk.d.ts +1 -0
  22. package/dist/flow-sdk.js +1 -0
  23. package/dist/nodes/agent/flow/check.d.ts +88 -0
  24. package/dist/nodes/agent/flow/check.js +873 -0
  25. package/dist/nodes/agent/flow/factories.d.ts +134 -0
  26. package/dist/nodes/agent/flow/factories.js +143 -0
  27. package/dist/nodes/agent/flow/family.d.ts +3 -0
  28. package/dist/nodes/agent/flow/family.js +8 -0
  29. package/dist/nodes/agent/flow/index.d.ts +3 -0
  30. package/dist/nodes/agent/flow/index.js +3 -0
  31. package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
  32. package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
  33. package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
  34. package/dist/nodes/agent/flow/inline-agent.js +526 -0
  35. package/dist/nodes/agent/flow/node-type.d.ts +20 -0
  36. package/dist/nodes/agent/flow/node-type.js +422 -0
  37. package/dist/nodes/agent/flow/resources.d.ts +502 -0
  38. package/dist/nodes/agent/flow/resources.js +1409 -0
  39. package/dist/nodes/agent/flow/spec.d.ts +448 -0
  40. package/dist/nodes/agent/flow/spec.js +1 -0
  41. package/dist/nodes/process/flow/check.d.ts +40 -2
  42. package/dist/nodes/process/flow/check.js +156 -0
  43. package/dist/nodes/process/flow/factories.d.ts +58 -3
  44. package/dist/nodes/process/flow/factories.js +59 -2
  45. package/dist/nodes/process/flow/node-type.d.ts +4 -1
  46. package/dist/nodes/process/flow/node-type.js +181 -10
  47. package/dist/nodes/process/flow/spec.d.ts +112 -5
  48. package/dist/serialize.d.ts +9 -54
  49. package/dist/serialize.js +47 -2763
  50. package/package.json +2 -2
  51. /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
  52. /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
package/dist/serialize.js CHANGED
@@ -6,12 +6,11 @@
6
6
  * resolved from the connector library (entry + v1def sidecar), matching what
7
7
  * the platform emits, so the result passes `uip maestro flow validate`.
8
8
  */
9
- import ixpDefinition from './ixp-definition.json' with { type: 'json' };
9
+ import ixpDefinition from './core/ixp-definition.json' with { type: 'json' };
10
10
  import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
11
11
  import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
12
- import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
12
+ import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameSegmentsOf } from './flow-sdk.js';
13
13
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
14
- import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexType } from './core/context-index.js';
15
14
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
16
15
  import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
17
16
  import { planContributionBindings } from './core/contribution-bindings.js';
@@ -20,25 +19,23 @@ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
20
19
  import { stepExits } from './flow-step-exits.js';
21
20
  import { resolveVersionCarried } from './core/nodes/versioned.js';
22
21
  import { indexCarriedDefinitions } from './core/carried-definitions.js';
23
- import { formFieldJsonSchema, formFieldSettingsWire, wireFormFieldType } from './core/form-field-type.js';
24
22
  import { ConnectorResolveError } from './core/connector-resolve-error.js';
25
23
  import { synthesizedDefinitionRefusal } from './core/synthesized-definition.js';
26
24
  import { connectionConnectors } from './bindings-v2.js';
27
25
  import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
28
26
  import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
29
27
  import { genericEventObjectRefusal } from './core/event-checks.js';
30
- import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
28
+ import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitViolation } from './core/hitl-routing.js';
31
29
  import { STICKY_NOTE_TYPE, STICKY_NOTE_TYPE_VERSION } from './core/definitionless-node-types.js';
32
30
  import { placeholderPosition } from './node-placement.js';
33
31
  import { hasExits } from './core/step-ports.js';
34
32
  import { stopsAtEnd } from './flow-step-tree.js';
35
33
  import { assertNodeEnvelope } from './core/node-envelope.js';
36
- import { buildConfiguration } from './config.js';
37
- import { fnv1aX4Uuid, schemaId, stableId } from './core/stable-id.js';
38
- import { derivedContextSource, derivedEscalationSource, derivedToolSource, } from './core/agent-resource-identity.js';
34
+ import { buildConfiguration } from './core/connector-configuration.js';
35
+ import { fnv1aX4Uuid, stableId } from './core/stable-id.js';
39
36
  import { readEventFilter, eventFilterProblem, eventFilterJmes, eventFilterTreeLeaf, jmesString, jmesStringProblem } from './core/event-filters.js';
40
37
  import { FLOW_EXPR_DIALECT } from './core/connector-checks.js';
41
- import { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors, multipartBodyFieldName, transportHttpMethod } from './core/connector-inputs.js';
38
+ import { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors, transportHttpMethod } from './core/connector-inputs.js';
42
39
  /**
43
40
  * What `compile` would refuse these connector inputs with, or `undefined` when it
44
41
  * would accept them — Flow's binding of the shared validator.
@@ -62,14 +59,14 @@ import { outputBackingGlobal, planOutputBacking } from './flow-output-backing.js
62
59
  import { mapJsCode } from './core/js-code-spans.js';
63
60
  import { contributionPort } from './core/definition-handles.js';
64
61
  import { prepareCommand } from './core/cli-spelling.js';
65
- import { resourceNodeSortOrder } from './core/process-resource-sort-order.js';
66
- import { processResourceBindings, processResourceLocation } from './core/process-resource.js';
62
+ import { processResourceBindings } from './core/process-resource.js';
67
63
  import { types } from './core/expr.js';
68
64
  import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
69
- import { ownEntry } from './core/own-entry.js';
70
65
  import { endContentKey } from './flow-end-content.js';
71
66
  import { terminateStepClashMessage, terminateTwiceMessage } from './flow-terminate-names.js';
72
67
  import { jsonClone } from './core/json-clone.js';
68
+ import { subNodeId } from './core/node-id-placeholder.js';
69
+ import { JOB_ATTACHMENT_REF, jobAttachmentDefinition } from './core/agent-sidecar.js';
73
70
  // core-definitions.json is an object keyed by "<nodeType>@<version>".
74
71
  const DEFS = BUNDLED_DEFINITIONS;
75
72
  // FIRST occurrence wins: with multiple bundled versions of one family (HITL
@@ -86,10 +83,6 @@ const NODE_TYPE = {
86
83
  formTrigger: 'core.trigger.form',
87
84
  conversationTrigger: 'core.trigger.conversation',
88
85
  voiceTrigger: 'core.trigger.voice',
89
- voiceAgent: 'uipath.agent.voice',
90
- conversationalAgent: 'uipath.agent.conversational',
91
- createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
92
- endCall: 'uipath.conversational.voice.end-call',
93
86
  // The two other spellings of the SAME human task, both resolvable on this
94
87
  // tenant. `hitl()` emits the base type unless `variant` names one of these;
95
88
  // all three share the model (`bpmn:UserTask` / `Actions.HITL`), the single
@@ -126,1951 +119,7 @@ const SUBFLOW_OUTPUT = {
126
119
  source: '=result.response',
127
120
  var: 'output',
128
121
  };
129
- /** The published-agent node type family. The agent's release key is the suffix. */
130
- const AGENT_PREFIX = 'uipath.core.agent.';
131
122
  /** The inline (autonomous) agent's node type — fixed, not per-instance. */
132
- const INLINE_AGENT_TYPE = 'uipath.agent.autonomous';
133
- /**
134
- * Build the definition for one published agent — coded or low-code, which are the
135
- * SAME node.
136
- *
137
- * Unbundlable for rpa's, api-workflow's and agentic-process's reason: the node type
138
- * carries a tenant's release key and the platform synthesizes the contract from
139
- * whatever somebody published there. The constants below are transcribed from what
140
- * the platform's oracles AGREE on, and this family is the first in the wave with
141
- * BOTH a rich registry answer and a large deployed corpus, so the agreement is
142
- * checkable rather than asserted:
143
- *
144
- * - `uip maestro flow registry get uipath.core.agent.<key>` for ALL NINE agents on
145
- * the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505` (510 since flow-core's
146
- * renumber: the 54 runs from 09-01 below carry 510; `resourceNodeSortOrder`, #1045),
147
- * `supportsErrorHandling: true`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
148
- * `Orchestrator.StartAgentJob` / `v2` model, `section: 'Published'`,
149
- * `resourceSubType: 'Agent'`, `orchestratorType: 'agent'`,
150
- * `debug.runtime: 'bpmnEngine'`, `runtimeConstraints.exclude: ['api-function']`,
151
- * and the `<bindings.name>` / `<bindings.folderPath>` context placeholders;
152
- * - **61 deployed, debug-green agent instances** across 61 dated
153
- * `skill-flow-{coded,lowcode}-agent` artifacts (05-18 → 07-29): `sortOrder: 505`
154
- * in 61/61, the `<folderPath>.<name>` resourceKey in 58/61 (the 3 exceptions are
155
- * the in-solution shape — a bare key and an EMPTY folderPath — which 404'd at
156
- * job start in those runs; that is the shape of `location: 'in-solution'`,
157
- * built by `processResourceLocation`, and it starts the job when the sibling is
158
- * in the solution: 54 v1 `skill-flow-coded-agent` runs, 09-01 → 10-08, carry
159
- * `isInSolution: true`, 45 score 1.0 and none of the other 9 fails on a
160
- * job-start 404, #1012), and the descriptor
161
- * ARRAY `bindings.values` in 59/61 (2 early runs used the object form);
162
- * - flow-v1's own generator, which is where those come from:
163
- * `node-manifest-builder.ts::createModel` hard-codes `bpmn:ServiceTask` for every
164
- * published-resource node and derives `orchestratorType` as
165
- * `resourceType.toLowerCase()`.
166
- *
167
- * `shared/sample-flows/agent.flow` is explicitly NOT an oracle here: dated
168
- * 2026-05-12, it disagrees with both of the above in five places at once
169
- * (`sortOrder: 530`, a bare-GUID resourceKey, an object-shaped `bindings.values`,
170
- * and `=result.response` / `=result.Error` output sources). PR 11's rule — date a
171
- * bundled sample before trusting the layer that copied it — and this is its second
172
- * instance on that file.
173
- *
174
- * `outputs.output.source` is `=this`, which is what the definition declares. The
175
- * corpus is split (`=result.response` 45, `=this` 14, both deploying green), and the
176
- * split has a mechanical explanation: flow-v1's `applyOrchestratorActivityOutputs`
177
- * APPENDS `{name:'output', source:'=this'}` when a node declares no `output`, so
178
- * `=this` is what the platform writes for itself. We emit that one.
179
- *
180
- * The ONLY field that differs between a coded and a low-code agent is
181
- * `display.icon` — which is why one factory serves both.
182
- */
183
- function agentDef(spec) {
184
- // One location slot with the other process resources: the in-solution
185
- // model carries `projectId`, `projectName` AND `isInSolution: true`, as
186
- // flow-core's `createModel` writes for every sibling-project resource.
187
- const loc = processResourceLocation(spec);
188
- const { inSolution, resourceKey } = loc;
189
- const properties = {};
190
- for (const [k, v] of Object.entries(spec.inputs)) {
191
- properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
192
- }
193
- return {
194
- nodeType: `${AGENT_PREFIX}${spec.key}`,
195
- version: '1.0.0',
196
- category: 'agent',
197
- runtimeConstraints: { exclude: ['api-function'] },
198
- description: loc.description,
199
- tags: loc.tags,
200
- sortOrder: resourceNodeSortOrder('agent'),
201
- supportsErrorHandling: true,
202
- display: {
203
- label: spec.name,
204
- icon: spec.flavour === 'coded' ? 'coded-agent' : 'autonomous-agent',
205
- },
206
- handleConfiguration: [
207
- { position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
208
- { position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
209
- ],
210
- ...loc.definitionExtras,
211
- model: {
212
- type: 'bpmn:ServiceTask',
213
- serviceType: 'Orchestrator.StartAgentJob',
214
- version: 'v2',
215
- ...(inSolution ? {} : { section: 'Published' }),
216
- bindings: {
217
- resource: 'process',
218
- resourceSubType: 'Agent',
219
- resourceKey,
220
- orchestratorType: 'agent',
221
- values: [
222
- { name: 'name', propertyAttribute: 'name', default: spec.name },
223
- { name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
224
- ],
225
- },
226
- ...loc.modelExtras,
227
- context: [
228
- { name: 'name', type: 'string', value: '<bindings.name>' },
229
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
230
- { name: '_label', type: 'string', value: spec.name },
231
- ],
232
- },
233
- inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
234
- inputDefaults: inSolution
235
- ? Object.fromEntries(Object.keys(properties).map((name) => [name, '']))
236
- : {},
237
- outputDefinition: {
238
- output: {
239
- type: 'object',
240
- source: '=this',
241
- var: 'output',
242
- // The agent's output fields as the author declared them — the same shape
243
- // `registry get` returns when it can reach the deployed agent (nested under
244
- // `schema`, which is the spelling gen-tests reads). Without it a downstream
245
- // read is unresolvable, rightly: nothing offline could tell a real field from a
246
- // typo, and this is also what fixtures the node offline.
247
- ...(spec.returns && Object.keys(spec.returns).length
248
- ? {
249
- schema: {
250
- type: 'object',
251
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
252
- },
253
- }
254
- : {}),
255
- },
256
- error: {
257
- type: 'object',
258
- description: 'Error information if the node fails',
259
- source: '=Error',
260
- var: 'error',
261
- },
262
- },
263
- debug: { runtime: 'bpmnEngine' },
264
- };
265
- }
266
- /**
267
- * The definition for ONE Context Grounding index resource, transcribed from the
268
- * platform's own registry entry (`uip maestro flow registry get
269
- * uipath.agent.resource.context.index.<slug>.<uuid>`, read 2026-07-31 against the
270
- * tenant's "Billing Dispute SOP Index"), and confirmed against the `.flow` v1
271
- * emits for the same index.
272
- *
273
- * Per-INSTANCE, unlike `inlineAgentDef`: the index's identity is in the node type,
274
- * so every tenant index is its own type — the same shape as a published agent's
275
- * release key. What is constant is everything else, and this builds that from the
276
- * one thing an author supplies (name + uuid).
277
- *
278
- * Two details are load-bearing rather than decorative:
279
- *
280
- * * `handleConfiguration` declares a single TARGET handle `input` whose
281
- * `allowedSources` are the two agent types' `context` handles, with
282
- * `maxConnections: 1`. That is the mirror of the agent's own
283
- * `allowedTargets: [{ nodeType: 'uipath.agent.resource.context.*' }]`, and it is
284
- * what makes the designer draw the join.
285
- * * `model: { source: true }` and nothing else. A resource node has no
286
- * `bpmn:` type, no serviceType and no bindings — it is not a step, it is an
287
- * artifact hanging off one. The v1 artifact carries no `bindings[]` entry for it
288
- * at all.
289
- */
290
- function contextIndexDef(spec) {
291
- return {
292
- nodeType: contextIndexType(spec),
293
- version: '1.0.0',
294
- category: 'agent',
295
- tags: ['agent-context', 'index'],
296
- description: `Context grounding index "${spec.name}".`,
297
- sortOrder: 505,
298
- display: { label: spec.name, description: `Context grounding index "${spec.name}".`, icon: 'file-text', shape: 'circle' },
299
- handleConfiguration: [
300
- {
301
- position: 'top',
302
- handles: [{
303
- id: 'input', type: 'target', handleType: 'artifact',
304
- constraints: {
305
- maxConnections: 1,
306
- allowedSources: [
307
- { nodeType: 'uipath.agent.autonomous', handleId: 'context' },
308
- { nodeType: 'uipath.agent.conversational', handleId: 'context' },
309
- ],
310
- validationMessage: "Only Agent's context handle can connect here",
311
- },
312
- }],
313
- },
314
- ],
315
- model: { source: true },
316
- // The platform's own `inputDefinition.properties`, transcribed. Carried so the
317
- // node's inputs are CHECKABLE — without an input schema nothing can validate
318
- // this node's inputs at all, and a misspelled `resultCount` reaches the tenant
319
- // unremarked.
320
- //
321
- // The registry's `allOf` conditionals (which retrieval modes require a query,
322
- // and which of the query object's three modes is live) are deliberately NOT
323
- // copied: they are the designer FORM's logic, and the serializer already emits
324
- // a well-formed `query` for the mode it sets.
325
- inputDefinition: {
326
- type: 'object',
327
- properties: {
328
- indexId: { type: 'string' },
329
- indexName: { type: 'string' },
330
- folderKey: { type: 'string' },
331
- folderPath: { type: 'string' },
332
- retrievalMode: { type: 'string' },
333
- query: { type: 'object' },
334
- threshold: { type: 'number', minimum: 0, maximum: 1 },
335
- resultCount: { type: 'number', minimum: 1, maximum: 40 },
336
- fileExtension: { type: 'string' },
337
- folderPathPrefix: { type: 'object' },
338
- citations: { type: 'string' },
339
- outputColumns: { type: 'array' },
340
- webSearchGrounding: { type: 'object', properties: { value: { type: 'string' } } },
341
- },
342
- },
343
- };
344
- }
345
- /**
346
- * Render an index's retrieval query into the platform's MODE-TAGGED object.
347
- *
348
- * The index definition's own schema (`inputDefinition.allOf`) says `query` is a
349
- * `oneOf` over three shapes, each identified by `mode`: `prompt` → `promptValue`,
350
- * `text-builder` → `textValue`, `variable` → `argumentPath`. All four fields are
351
- * present in the platform's `inputDefaults`; only the one the mode names is read.
352
- *
353
- * A plain string is a `prompt` (what a designer types); anything expression-shaped
354
- * is `text-builder`, whose `textValue` the runtime templates — the same slot the
355
- * `folderPathPrefix` field uses, and the reason that field ships with
356
- * `mode: 'text-builder'` by default.
357
- */
358
- function renderContextQuery(query, rename) {
359
- const base = { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' };
360
- if (query === undefined || query === null)
361
- return base;
362
- const rendered = renderValue(query, rename);
363
- const asText = typeof rendered === 'string' ? rendered : JSON.stringify(rendered);
364
- if (typeof rendered === 'string' && rendered.startsWith('=js:')) {
365
- return { ...base, mode: 'text-builder', textValue: asText };
366
- }
367
- return { ...base, promptValue: asText };
368
- }
369
- /**
370
- * The `resources/<id>/resource.json` an inline agent's context index needs.
371
- *
372
- * The node is only half of a grounded agent, the same split as the agent itself
373
- * (see `inlineAgentJson`). The platform resolves an agent's capabilities from
374
- * `<source>/resources/<resource-id>/resource.json`, and `uip agent refresh` turns
375
- * those into the `bindings_v2.json` entries `uip solution resources refresh`
376
- * syncs. Keyed by the node's `inputs.source`, which is what joins the two halves.
377
- *
378
- * On the CLI path the file is the only copy: refresh strips the node and
379
- * re-derives it from here, so a `.flow` with the node and no file validates clean
380
- * and leaves the agent ungrounded. (Studio Web is the other direction — its
381
- * `mapContextResource` builds this file FROM the canvas node.)
382
- *
383
- * Shape and enums are the platform's `currentResourceStorageSchema`
384
- * (`@uipath/agents-storage-schemas`): `description`, `folderPath`, `indexName` and
385
- * all six `settings` keys are required, `fileExtension` is `{value}` rather than a
386
- * bare string, and the retrieval mode pins which extensions it accepts — hence the
387
- * per-mode default here against the node's flat `'All'`. An unparseable resource
388
- * is worse than none: `uip agent refresh` falls back to `resources: []` and drops
389
- * every other binding with it.
390
- */
391
- function contextResourceJson(cx, spec) {
392
- const mode = cx.retrievalMode ?? CONTEXT_INDEX_DEFAULTS.retrievalMode;
393
- return {
394
- $resourceType: 'context',
395
- id: spec.id,
396
- // Null rather than absent: optional in the schema, but every deployed
397
- // resource carries it, and a designer round-trip writes it back.
398
- referenceKey: null,
399
- name: cx.name,
400
- description: `Context grounding index "${cx.name}".`,
401
- contextType: 'index',
402
- // Required by the schema, and load-bearing: this file carries no `indexId`,
403
- // so `indexName` + `folderPath` is the whole locator, and `createIndexBinding`
404
- // leaves the folder off the binding when it is empty. `check` warns when the
405
- // author omits it — an index outside the deployment folder is then unresolvable.
406
- folderPath: cx.folderPath ?? '',
407
- indexName: cx.name,
408
- settings: {
409
- retrievalMode: mode,
410
- // The resource's `query` is VARIANT-tagged where the node's is MODE-tagged,
411
- // and the two vocabularies do not overlap. `prompt` — what a designer types
412
- // as guidance — is the model-invoked `dynamic`, and its text is the
413
- // `description` the model reads when deciding what to search for.
414
- // `text-builder` — an expression the runtime templates — is the pre-fetched
415
- // `static`, and its text is the `value`. Confirmed behaviourally: switching
416
- // the SDK's `query` from a string to an expression turned the same agent's
417
- // retrieval from a tool call into a pre-fetch.
418
- query: spec.query.mode === 'text-builder'
419
- ? { variant: 'static', value: String(spec.query.textValue ?? '') }
420
- : { variant: 'dynamic', description: String(spec.query.promptValue ?? '') },
421
- // `value: ''`, as the canvas writes it (flow-workbench `mapContextQueryToWire`
422
- // always sets the value), so a canvas index's resource round-trips unchanged.
423
- folderPathPrefix: { variant: 'static', value: '' },
424
- threshold: cx.threshold ?? CONTEXT_INDEX_DEFAULTS.threshold,
425
- resultCount: cx.resultCount ?? CONTEXT_INDEX_DEFAULTS.resultCount,
426
- fileExtension: { value: cx.fileExtension ?? ownEntry(CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, mode) ?? 'All' },
427
- // Mode-specific keys the schema REQUIRES of those two modes and rejects
428
- // nowhere else, carrying the node's own constants through the enum's
429
- // spelling (the node says `enabled`; the resource says `Enabled`/`Inline`).
430
- ...(mode === 'deeprag' ? { citationMode: { value: 'Inline' } } : {}),
431
- ...(mode === 'batchtransform'
432
- ? { webSearchGrounding: { value: 'Enabled' }, outputColumns: [] }
433
- : {}),
434
- },
435
- };
436
- }
437
- /** A `fieldSchema` object with every declared property marked required. */
438
- function requireAll(schema) {
439
- const names = Object.keys(schema.properties);
440
- return names.length === 0 ? schema : { ...schema, required: names };
441
- }
442
- /**
443
- * The resource `type` each deployed-resource tool kind carries in the agent
444
- * project, which is NOT its node-type namespace: `maestro` mints
445
- * `…tool.processorchestration.*` and its resource type is `processOrchestration`.
446
- * Read off the platform's `currentResourceStorageSchema`; flow-workbench's
447
- * `CANVAS_TO_STORAGE_TOOL_TYPE` maps them the same way.
448
- */
449
- const TOOL_RESOURCE_TYPE = {
450
- process: 'process', api: 'api', maestro: 'processOrchestration',
451
- flow: 'flow', agent: 'agent', function: 'function',
452
- };
453
- /** The resource `type` of a connector tool's file ({@link connectorToolResourceJson}). */
454
- const CONNECTOR_TOOL_RESOURCE_TYPE = 'integration';
455
- /**
456
- * The resource `type` of every platform built-in tool. Shared with tools the
457
- * DESIGNER writes and the SDK cannot (`load-attachments`, `create-file`, …), so
458
- * on its own it says nothing about who owns a file: `properties.toolType` does.
459
- */
460
- const INTERNAL_TOOL_RESOURCE_TYPE = 'internal';
461
- /**
462
- * Where an agent schema property points to declare "this is a FILE":
463
- * `{ $ref: JOB_ATTACHMENT_REF }`, with {@link jobAttachmentDefinition} under the
464
- * same schema's `definitions['job-attachment']`.
465
- */
466
- const JOB_ATTACHMENT_REF = '#/definitions/job-attachment';
467
- /**
468
- * The canonical `job-attachment` definition an agent schema's file field
469
- * `$ref`s into — the uipath-agents skill's `agent-definition.md` § File
470
- * Attachments ("Schema is fixed — copy verbatim"), and the same block the
471
- * platform's own analyze-attachments tool contract carries.
472
- *
473
- * It tells the agent runtime that an input is an Orchestrator attachment, so a
474
- * file tool (Analyze Files) can resolve the attachment's `ID`.
475
- *
476
- * A function, not a shared object: each schema gets its own copy, so an edit
477
- * to one emitted file cannot reach another.
478
- *
479
- * (The case serializer keeps its own copy in `case/entry-points.ts`: that one
480
- * mirrors the platform's `entry-points.json` packager byte for byte, and its
481
- * `MimeType` description differs.)
482
- */
483
- function jobAttachmentDefinition() {
484
- return {
485
- type: 'object',
486
- properties: {
487
- ID: { type: 'string', description: 'Orchestrator attachment key' },
488
- FullName: { type: 'string', description: 'File name' },
489
- MimeType: { type: 'string', description: 'MIME type, e.g. "application/pdf", "image/png"' },
490
- Metadata: {
491
- type: 'object',
492
- description: 'Dictionary<string, string> of metadata',
493
- additionalProperties: { type: 'string' },
494
- },
495
- },
496
- required: ['ID'],
497
- 'x-uipath-resource-kind': 'JobAttachment',
498
- };
499
- }
500
- /** The `properties.toolType` of the built-in HTTP-request tool's file (#935). */
501
- const HTTP_REQUEST_TOOL_TYPE = 'http-request';
502
- /**
503
- * The `resources/<id>/resource.json` a DEPLOYED-RESOURCE tool needs — the tool
504
- * half of what `contextResourceJson` does for an index, and inert for the same
505
- * reason (see that function).
506
- *
507
- * Shape from a real agent project (`WebResearchBriefingAgent`'s `CountSources`,
508
- * a `kind: 'process'` tool), cross-checked against the platform schema: this
509
- * family carries `argumentProperties` and `referenceKey` but no `iconUrl`, and
510
- * `exampleCalls` is present-and-empty. `location: 'solution'` and an empty
511
- * `properties.folderPath` are the inline-in-flow pair Studio Web's
512
- * `mapToolResource` writes and uipcli's `agent validate` enforces — the folder
513
- * itself lives in the flow's top-level `bindings[]`, which
514
- * `processResourceBindings` already writes for this tool.
515
- *
516
- * Returns `undefined` for every OTHER kind. Which kinds get a file is measured,
517
- * because `uip agent refresh` strips each resource node and re-derives it FROM
518
- * the file: where the node is `source`-only the round-trip is lossless, and where
519
- * the node carries the configuration the strip loses it and the rebuild cannot
520
- * put it back, so writing the file turns a valid flow INVALID.
521
- *
522
- * * `ixp`, `clientside` — measured to fail exactly that way (`ixp` leaves the
523
- * flow failing validate; `clientside` drops the four inputs its node needs).
524
- * * `connector` — has its own writer, {@link connectorToolResourceJson}: its
525
- * node carries the configuration in `inputs.detail`, which the strip keeps,
526
- * and the file is the one `uip agent refresh` itself derives from it.
527
- * * `mcp`, `a2a` — need the server's `availableTools` and the remote agent's
528
- * card, which are tenant reads.
529
- * * `builtin` — has its own writer, {@link builtinToolResourceJson}: its
530
- * `toolType` is a fixed per-tool constant, so the file is derivable offline.
531
- * * `httpRequest` — has its own writer, {@link httpRequestToolResourceJson}:
532
- * the file carries the request config as static `argumentProperties` and the
533
- * model-facing `inputSchema`, both derivable from the node (#935).
534
- *
535
- * Which `type`/`toolType` values compile writes — and so owns and may prune —
536
- * is {@link compileOwnsResource}, read off the same tables these writers use.
537
- */
538
- function toolResourceJson(tr, spec) {
539
- const common = {
540
- $resourceType: 'tool',
541
- id: spec.id,
542
- name: tr.name,
543
- // The runtime hands this to the MODEL as the tool's description, so an empty
544
- // one is a tool the model has to guess at from its name alone.
545
- description: tr.description ?? '',
546
- isEnabled: true,
547
- // `required` matters here where it does not on the agent's own contract: the
548
- // model reads this schema to build the call, and an argument the process needs
549
- // but the schema calls optional fails at run time with nothing said at compile
550
- // time. The corpus fixture lists every declared argument.
551
- inputSchema: requireAll(fieldSchema(tr.inputs)),
552
- outputSchema: fieldSchema(tr.returns),
553
- // No policies: `inlineAgent`'s tool refs carry none, and the field is
554
- // required-but-emptyable.
555
- guardrail: { policies: [] },
556
- settings: {},
557
- argumentProperties: {},
558
- };
559
- const resourceType = ownEntry(TOOL_RESOURCE_TYPE, tr.kind);
560
- if (resourceType !== undefined) {
561
- return {
562
- ...common,
563
- type: resourceType,
564
- location: 'solution',
565
- // The deployed resource's own key: what joins this tool to the release the
566
- // solution declares.
567
- referenceKey: tr.key,
568
- // `folderPath` is EMPTY on purpose, and the tenant folder is not lost: for
569
- // an inline-in-flow agent the folder lives in the flow's own top-level
570
- // `bindings[]`, which `processResourceBindings` already writes for this
571
- // tool. uipcli's `agent validate` says so where it enforces the pair —
572
- // *"Inline-in-flow agents store folder info in the flow's top-level
573
- // bindings[] rather than in the tool resource — Studio Web emits an empty
574
- // folderPath here"* — and every `inlineAgent` tool is inline by definition.
575
- properties: { processName: tr.name, folderPath: '', exampleCalls: [] },
576
- };
577
- }
578
- return undefined;
579
- }
580
- /**
581
- * The `properties.toolType` each platform built-in tool carries in the agent
582
- * project. Neither the node type nor the label: `analyzefiles` is the
583
- * designer's "Analyze Files" and its resource is `analyze-attachments`;
584
- * `summarize` is the designer's DeepRAG node (`manifest_node_deepRag_display_label:
585
- * "Summarize"` in the vendored workbench strings) and its resource is `deep-rag`.
586
- * Read off the platform's `builtInToolPropertiesSchemaV40` discriminated union
587
- * (vendored `flow-to-bpmn.mjs`), which also lists `load-attachments` and
588
- * `http-request`, and the uipath-agents skill's built-in-tools reference, which
589
- * owns the file's body. The runtime ignores an unknown toolType silently, so
590
- * this table is the whole contract — one that is wrong selects no tool at all.
591
- */
592
- const BUILTIN_TOOL_TYPE = {
593
- analyzefiles: 'analyze-attachments',
594
- summarize: 'deep-rag',
595
- batchtransform: 'batch-transform',
596
- };
597
- /** `inline` → `Inline`: the node's enum spelling to the resource schema's. */
598
- const enumValue = (v) => {
599
- const s = String(v ?? '');
600
- return s.charAt(0).toUpperCase() + s.slice(1);
601
- };
602
- /**
603
- * The `resources/<id>/resource.json` of a platform BUILT-IN tool (#920) — the
604
- * file the runtime builds the agent's tool list from. Without it the
605
- * `tool.builtin.*` node is inert, the same failure #806 fixed for deployed
606
- * resources: the agent runs with no built-in tool and the model never learns
607
- * it exists.
608
- *
609
- * Derived from the node's own inputs (`tdef.inputDefaults` with the author's
610
- * values over them), because those are what the file must agree with:
611
- * `uip agent refresh --inline-in-flow` shell-ifies every resource node down to
612
- * `{source, detail, itemsDescription}` and Studio Web re-hydrates the rest FROM
613
- * this file, so a value only on the node does not survive a refresh or a
614
- * designer round-trip.
615
- *
616
- * Shape: the skill's per-tool walkthroughs (`analyze-attachments.md`,
617
- * `deeprag/impl-json.md`, `batch-transform/impl-json.md`) checked against the
618
- * platform's `builtInToolResourceStorageSchemaV40`. Two vocabularies differ
619
- * from the node's, as they do for a context index (`contextResourceJson`): the
620
- * resource `query` is VARIANT-tagged where the node's is MODE-tagged, and its
621
- * enums are capitalized (`Inline`, `Enabled`). `analyze-attachments` declares
622
- * the model-facing call contract (`attachments` + `analysisTask` → `analysis`);
623
- * `deep-rag` and `batch-transform` declare empty schemas and carry their
624
- * configuration in `properties.settings`.
625
- */
626
- function builtinToolResourceJson(tool, label, inputs, spec) {
627
- const toolType = ownEntry(BUILTIN_TOOL_TYPE, tool);
628
- if (toolType === undefined)
629
- return undefined;
630
- const empty = { type: 'object', properties: {} };
631
- const common = {
632
- $resourceType: 'tool',
633
- id: spec.id,
634
- // Null, not absent: what identifies a built-in as NOT an external tool.
635
- referenceKey: null,
636
- name: label,
637
- type: INTERNAL_TOOL_RESOURCE_TYPE,
638
- description: String(inputs.description ?? ''),
639
- isEnabled: true,
640
- settings: {},
641
- guardrail: { policies: [] },
642
- argumentProperties: {},
643
- };
644
- const query = inputs.query?.mode === 'text-builder'
645
- ? { variant: 'static', value: String(inputs.query.textValue ?? '') }
646
- : { variant: 'dynamic', description: String(inputs.query?.promptValue ?? '') };
647
- if (toolType === 'analyze-attachments') {
648
- return {
649
- ...common,
650
- inputSchema: {
651
- type: 'object',
652
- properties: {
653
- attachments: {
654
- type: 'array',
655
- items: { $ref: JOB_ATTACHMENT_REF },
656
- description: String(inputs.attachmentsDescription ?? ''),
657
- },
658
- analysisTask: { type: 'string', description: String(inputs.analysisTaskDescription ?? '') },
659
- },
660
- required: ['attachments', 'analysisTask'],
661
- definitions: { 'job-attachment': jobAttachmentDefinition() },
662
- },
663
- outputSchema: {
664
- type: 'object',
665
- properties: {
666
- analysis: {
667
- type: 'string',
668
- description: 'Analysis result of the attachments (analysis, summary, extracted information, etc.)',
669
- },
670
- },
671
- required: ['analysis'],
672
- },
673
- properties: { toolType, exampleCalls: [] },
674
- };
675
- }
676
- if (toolType === 'deep-rag') {
677
- return {
678
- ...common,
679
- inputSchema: empty,
680
- outputSchema: empty,
681
- properties: {
682
- toolType,
683
- exampleCalls: [],
684
- settings: {
685
- contextType: 'attachments',
686
- query,
687
- folderPathPrefix: { variant: 'static' },
688
- fileExtension: { value: String(inputs.fileExtension?.value ?? 'pdf') },
689
- citationMode: { value: enumValue(inputs.citationMode?.value ?? 'inline') },
690
- },
691
- },
692
- };
693
- }
694
- return {
695
- ...common,
696
- inputSchema: empty,
697
- outputSchema: empty,
698
- properties: {
699
- toolType,
700
- exampleCalls: [],
701
- settings: {
702
- contextType: 'attachments',
703
- query,
704
- folderPathPrefix: { variant: 'static' },
705
- outputColumns: Array.isArray(inputs.outputColumns) ? inputs.outputColumns : [],
706
- webSearchGrounding: { value: enumValue(inputs.webSearchGrounding?.value ?? 'enabled') },
707
- },
708
- },
709
- };
710
- }
711
- /**
712
- * The six request fields of the HTTP-request tool, in the node's (and the
713
- * designer's file's) order, with the JSON Schema each takes in the file's
714
- * model-facing `inputSchema`. `headers`/`params` are name/value rows and
715
- * `timeout` is a number — the same normalization the node's text-builder
716
- * values already carry.
717
- */
718
- const HTTP_REQUEST_FIELDS = [
719
- ['url', { type: 'string' }],
720
- ['method', { type: 'string' }],
721
- ['headers', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
722
- ['params', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
723
- ['body', { type: 'string' }],
724
- ['timeout', { type: 'number' }],
725
- ];
726
- /**
727
- * The `resources/<id>/resource.json` of the built-in HTTP-request tool (#935) —
728
- * without it the deployed agent is never offered the tool (measured: one LLM
729
- * call, no tool span), and `uip agent refresh --inline-in-flow` strips the node
730
- * to `{source}`, so the request config is lost entirely.
731
- *
732
- * Derived from the node's own mode-tagged inputs, so the two halves cannot
733
- * disagree. The rules are the ones Studio Web's own file follows (measured by
734
- * saving the agent once in the designer; a file built by these rules is
735
- * JSON-equal to it, and a debug run with only this file showed the tool spans):
736
- *
737
- * * `inputSchema` declares all six fields, each with the node's `promptValue`
738
- * as its description — the text the model reads when it fills the field;
739
- * * `argumentProperties` pins every field NOT in `prompt` mode as
740
- * `$['<field>']: {variant: 'static', value: <textValue>}` — a fixed value the
741
- * model does not get to choose. The SDK writes only `prompt` and
742
- * `text-builder` fields; a `text-builder` value is the literal.
743
- */
744
- function httpRequestToolResourceJson(label, inputs, spec) {
745
- const properties = {};
746
- const argumentProperties = {};
747
- for (const [field, schema] of HTTP_REQUEST_FIELDS) {
748
- const row = (inputs[field] ?? {});
749
- properties[field] = { ...schema, description: String(row.promptValue ?? '') };
750
- if (row.mode !== undefined && row.mode !== 'prompt') {
751
- argumentProperties[`$['${field}']`] = { variant: 'static', value: row.textValue, isSensitive: false };
752
- }
753
- }
754
- return {
755
- $resourceType: 'tool',
756
- id: spec.id,
757
- canvasNodeId: spec.canvasNodeId,
758
- name: label,
759
- type: INTERNAL_TOOL_RESOURCE_TYPE,
760
- description: String(inputs.description ?? ''),
761
- isEnabled: true,
762
- inputSchema: { type: 'object', properties, required: ['url'] },
763
- outputSchema: { type: 'object', properties: {} },
764
- settings: {},
765
- guardrail: { policies: [] },
766
- argumentProperties,
767
- properties: { toolType: HTTP_REQUEST_TOOL_TYPE },
768
- };
769
- }
770
- /**
771
- * Every tool resource identity `compile` writes, read off the tables its
772
- * writers use — so a tool kind cannot be written without being owned, or owned
773
- * without being written (#935: the #920 built-in files were written as
774
- * `internal` and never pruned, because ownership was a hand-kept list in
775
- * `compile-cli.ts` that did not have them).
776
- *
777
- * * `types` — the resource `type`s compile owns OUTRIGHT: every deployed-resource
778
- * kind's, and `integration` (connector tools, since #867).
779
- * * `internalToolTypes` — `internal` is shared with designer-only built-ins
780
- * (`load-attachments`, `create-file`, `jev-classifier`), so for it ownership
781
- * is the `properties.toolType`, and only the ones compile writes.
782
- */
783
- export const COMPILE_WRITTEN_TOOL_RESOURCES = {
784
- types: new Set([...Object.values(TOOL_RESOURCE_TYPE), CONNECTOR_TOOL_RESOURCE_TYPE]),
785
- internalToolTypes: new Set([...Object.values(BUILTIN_TOOL_TYPE), HTTP_REQUEST_TOOL_TYPE]),
786
- };
787
- /**
788
- * Does `compile` own this `resources/<id>/resource.json` — would a compile have
789
- * written it, so one this compile did not write is stale rather than someone
790
- * else's? Everything it says no to belongs to the designer or to `uip agent
791
- * refresh` (escalation, mcp, designer-only built-ins, the kinds the SDK cannot
792
- * derive), and is never deleted.
793
- */
794
- export function compileOwnsResource(body) {
795
- const kind = body?.$resourceType;
796
- if (kind === 'context')
797
- return true;
798
- if (kind !== 'tool')
799
- return false;
800
- const type = String(body?.type);
801
- if (type === INTERNAL_TOOL_RESOURCE_TYPE) {
802
- const toolType = body?.properties?.toolType;
803
- return COMPILE_WRITTEN_TOOL_RESOURCES.internalToolTypes.has(String(toolType));
804
- }
805
- return COMPILE_WRITTEN_TOOL_RESOURCES.types.has(type);
806
- }
807
- /**
808
- * The .NET type an Action Center app argument of each field type carries. Studio
809
- * Web serializes the task payload through `inputSchemaDotnetTypeMapping`, which
810
- * the platform schema REQUIRES on an `actionCenter` channel; it reads the raw
811
- * type off the app's action schema, which the SDK does not fetch, so this is
812
- * the inverse of flow-workbench's `dotNetTypeToJsonSchema` for the scalars and
813
- * `System.Object` for the two composite types it has no single answer for.
814
- */
815
- const DOTNET_TYPE = {
816
- string: 'System.String', number: 'System.Double', integer: 'System.Int32',
817
- boolean: 'System.Boolean', object: 'System.Object', array: 'System.Object',
818
- };
819
- /**
820
- * The folder an app escalation names: the author's `folderPath`, or
821
- * `solution_folder` when they gave none — an app that ships in this solution.
822
- *
823
- * `solution_folder` is the platform's own spelling for that case: it is the
824
- * `Folder` that `uip solution resources list` reports for a solution-internal
825
- * app, and what the agents skill tells authors to copy into
826
- * `channels[].properties.folderName`. The SDK used to write `""`, which no
827
- * product writer produces (flow-builder-sdk#967). Measured, the two are the
828
- * same everywhere it counts: the agent tool's binding generator drops both
829
- * from the App binding (`resolveFolderPathBindingValue`), `uip agent refresh`
830
- * plus `uip solution resources refresh` derive byte-identical files from
831
- * either, and a live debug resolves the app in the debug folder for both. So
832
- * this changes the vocabulary, not the behaviour.
833
- */
834
- function escalationAppFolder(app) {
835
- return app.folderPath ? app.folderPath : 'solution_folder';
836
- }
837
- /**
838
- * The `resources/<id>/resource.json` an inline agent's ESCALATION needs — the
839
- * escalation half of what `toolResourceJson` does for a tool, and inert without
840
- * it for the same reason (#921). Measured 2026-10-05 on alpha with `uip maestro
841
- * flow debug`: the same agent, told to escalate every claim, answered *"could
842
- * not escalate: no ExpenseEscalation tool available"* with the node alone, and
843
- * created the Action Center task with this file beside it.
844
- *
845
- * Shape: the uipath-agents skill's agent-level escalation resource (it owns the
846
- * body), checked field by field against the platform's
847
- * `currentResourceStorageSchema` — which REQUIRES `description` (top level and
848
- * channel), `isAgentMemoryEnabled`, `governanceProperties`, `properties`,
849
- * `inputSchemaDotnetTypeMapping` and `channels[].properties.appVersion`.
850
- *
851
- * Unlike a tool, an escalation node CARRIES its configuration, and `uip agent
852
- * refresh --inline-in-flow` strips it to `source` either way. Writing the file
853
- * does not change that; it is what makes refresh keep the escalation at all
854
- * (`Resources: 1` and the App binding in `bindings_v2.json`, against
855
- * `Resources: 0` without it). The compiled `.flow` keeps the node's full inputs,
856
- * so `flow validate` is unaffected.
857
- */
858
- function escalationResourceJson(esc, spec) {
859
- const outcomeMapping = esc.outcomes && esc.outcomes.length
860
- ? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
861
- : {};
862
- const common = {
863
- id: spec.channelId,
864
- name: 'Channel',
865
- description: esc.description ?? '',
866
- outcomeMapping,
867
- recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
868
- taskTitle: esc.taskTitle ?? '',
869
- taskTitleV2: {
870
- type: 'textBuilder',
871
- tokens: esc.taskTitle ? [{ type: 'simpleText', rawString: esc.taskTitle }] : [],
872
- },
873
- labels: esc.labels ?? [],
874
- argumentProperties: {},
875
- };
876
- let channel;
877
- if (spec.quickFormSchema !== undefined) {
878
- // The INLINE form: the node's own `schema` (fields + outcomes) is the
879
- // channel's, and its contract is read off the field directions — what the
880
- // reviewer is shown (`input`/`inOut`) and what comes back (`output`/`inOut`).
881
- const fields = esc.fields ?? [];
882
- const props = (dirs) => Object.fromEntries(fields
883
- .filter((f) => dirs.includes(f.direction))
884
- .map((f) => [f.id, formFieldJsonSchema(f.type) ?? { type: 'string' }]));
885
- channel = {
886
- ...common,
887
- type: 'actionCenterQuickForm',
888
- inputSchema: { type: 'object', properties: props(['input', 'inOut']) },
889
- outputSchema: { type: 'object', properties: props(['output', 'inOut']) },
890
- properties: {
891
- schema: spec.quickFormSchema,
892
- isActionableMessageEnabled: false,
893
- actionableMessageMetaData: null,
894
- },
895
- };
896
- }
897
- else {
898
- const app = esc.app;
899
- const mapping = (fields) => Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, ownEntry(DOTNET_TYPE, t) ?? 'System.Object']));
900
- channel = {
901
- ...common,
902
- type: 'actionCenter',
903
- inputSchema: fieldSchema(app.inputs),
904
- outputSchema: fieldSchema(app.outputs),
905
- inputSchemaDotnetTypeMapping: mapping(app.inputs),
906
- outputSchemaDotnetTypeMapping: mapping(app.outputs),
907
- properties: {
908
- // The app's key and name are what `uip agent refresh` turns into the
909
- // App binding (`name` + `folderPath`) that `uip solution resources
910
- // refresh` imports; `folderName` is the app's Orchestrator folder.
911
- resourceKey: app.key,
912
- appName: app.name,
913
- folderName: escalationAppFolder(app),
914
- // The app's deploy version (its action schema's `version`). The SDK does
915
- // not read the action schema, so this is the platform's own default — the
916
- // storage migration that introduced the field writes `1` when absent.
917
- appVersion: app.version ?? 1,
918
- isActionableMessageEnabled: false,
919
- actionableMessageMetaData: null,
920
- },
921
- };
922
- }
923
- return {
924
- $resourceType: 'escalation',
925
- id: spec.id,
926
- name: esc.name,
927
- description: esc.description ?? '',
928
- escalationType: 0,
929
- isAgentMemoryEnabled: false,
930
- properties: {},
931
- governanceProperties: { isEscalatedAtRuntime: false },
932
- isEnabled: true,
933
- channels: [channel],
934
- };
935
- }
936
- /**
937
- * The one TARGET handle every tool resource declares: `input`, artifact-typed,
938
- * fed by the two agent types' `tool` handles. Transcribed from `registry get`
939
- * (identical across `tool.builtin.*`, `tool.connector.*` and every
940
- * per-instance kind, 2026-07-31) — the mirror of the agent's own
941
- * `allowedTargets: [{ nodeType: 'uipath.agent.resource.tool.*' }]`.
942
- */
943
- function toolInputHandle() {
944
- return {
945
- position: 'top',
946
- handles: [{
947
- id: 'input', type: 'target', handleType: 'artifact', isDefaultForType: true,
948
- constraints: {
949
- maxConnections: 1,
950
- allowedSources: [
951
- { nodeType: 'uipath.agent.autonomous', handleId: 'tool' },
952
- { nodeType: 'uipath.agent.conversational', handleId: 'tool' },
953
- ],
954
- validationMessage: "Only Agent's tool handle can connect here",
955
- },
956
- }],
957
- };
958
- }
959
- /** A `{ name: type }` field map as the JSON-schema object the platform stores. */
960
- function fieldSchema(fields) {
961
- return {
962
- type: 'object',
963
- properties: Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, { type: t }])),
964
- };
965
- }
966
- /**
967
- * The definitions for the three platform BUILT-IN tools, transcribed from
968
- * `uip maestro flow registry get uipath.agent.resource.tool.builtin.<name>`
969
- * (2026-07-31, version 1.1 all three). These are fixed types — no tenant
970
- * resource behind them — so unlike every other tool kind the def CAN be
971
- * bundled.
972
- *
973
- * One wrinkle carried faithfully rather than smoothed over: the three defs
974
- * disagree about where the node's `source` identity lives. `analyzefiles`
975
- * declares `model.source: true` (the resource-family convention);
976
- * `summarize`/`batchtransform` have NO model and declare `source` as an
977
- * ordinary input instead. Both spellings satisfy the platform's stable-UUID
978
- * requirement, so each def ships the registry's own shape.
979
- */
980
- const BUILTIN_TOOL_DEFS = {
981
- analyzefiles: {
982
- nodeType: 'uipath.agent.resource.tool.builtin.analyzefiles',
983
- version: '1.1',
984
- category: 'agent.tool.builtin',
985
- tags: ['agent-tool', 'builtin', 'analyze', 'files', 'attachments'],
986
- description: 'Extract and analyze content from files',
987
- sortOrder: 48,
988
- display: {
989
- label: 'Analyze Files',
990
- description: 'Analyze one or more files with an LLM to extract, synthesize, or answer queries about their content.',
991
- icon: 'paperclip', shape: 'circle',
992
- },
993
- handleConfiguration: [toolInputHandle()],
994
- model: { source: true },
995
- inputDefinition: {
996
- type: 'object',
997
- properties: {
998
- description: { type: 'string' },
999
- attachmentsDescription: { type: 'string' },
1000
- itemsDescription: {
1001
- type: 'array',
1002
- items: { type: 'object', properties: { label: { type: 'string' }, value: { type: 'string' } } },
1003
- },
1004
- analysisTaskDescription: { type: 'string' },
1005
- exampleCalls: { type: 'array' },
1006
- },
1007
- },
1008
- inputDefaults: {
1009
- description: 'Analyze one or more files with an LLM to extract, synthesize, or answer queries about their content.',
1010
- attachmentsDescription: 'Array of files, documents, images, or other attachments to process',
1011
- itemsDescription: [],
1012
- analysisTaskDescription: "The task, question, or instruction for processing the files (e.g., 'summarize this document', 'extract key points', 'what is in this image')",
1013
- exampleCalls: [],
1014
- },
1015
- },
1016
- summarize: {
1017
- nodeType: 'uipath.agent.resource.tool.builtin.summarize',
1018
- version: '1.1',
1019
- category: 'agent.tool.builtin',
1020
- tags: ['agent-tool', 'builtin', 'summarize', 'document', 'synthesis', 'citations'],
1021
- description: 'Synthesize across documents with citations',
1022
- sortOrder: 48,
1023
- display: {
1024
- label: 'Summarize',
1025
- description: 'Get comprehensive synthesis across up to 1,000 pages with citations.',
1026
- icon: 'database', shape: 'circle',
1027
- },
1028
- handleConfiguration: [toolInputHandle()],
1029
- inputDefinition: {
1030
- type: 'object',
1031
- properties: {
1032
- description: { type: 'string' },
1033
- source: { type: 'string' },
1034
- query: { type: 'object' },
1035
- fileExtension: { type: 'object', properties: { value: { type: 'string' } } },
1036
- citationMode: { type: 'object', properties: { value: { type: 'string' } } },
1037
- },
1038
- },
1039
- inputDefaults: {
1040
- description: 'Get comprehensive synthesis across up to 1,000 pages with citations.',
1041
- source: '',
1042
- query: { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' },
1043
- fileExtension: { value: 'pdf' },
1044
- citationMode: { value: 'inline' },
1045
- },
1046
- },
1047
- batchtransform: {
1048
- nodeType: 'uipath.agent.resource.tool.builtin.batchtransform',
1049
- version: '1.1',
1050
- category: 'agent.tool.builtin',
1051
- tags: ['agent-tool', 'builtin', 'batch-transform', 'csv', 'transform'],
1052
- description: 'Process and transform CSV files',
1053
- sortOrder: 48,
1054
- display: {
1055
- label: 'Batch Transform',
1056
- description: 'Process and transform CSV files.',
1057
- icon: 'table', shape: 'circle',
1058
- },
1059
- handleConfiguration: [toolInputHandle()],
1060
- inputDefinition: {
1061
- type: 'object',
1062
- properties: {
1063
- description: { type: 'string' },
1064
- source: { type: 'string' },
1065
- query: { type: 'object' },
1066
- outputColumns: { type: 'array' },
1067
- webSearchGrounding: { type: 'object', properties: { value: { type: 'string' } } },
1068
- },
1069
- },
1070
- inputDefaults: {
1071
- description: 'Process and transform CSV files.',
1072
- source: '',
1073
- query: { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' },
1074
- outputColumns: [],
1075
- webSearchGrounding: { value: 'enabled' },
1076
- },
1077
- },
1078
- };
1079
- /**
1080
- * The definition for one CONNECTOR-operation tool, derived from the same
1081
- * curated-library entry `connector(…)` resolves — measured equivalence, not
1082
- * hope: the library's `.v1def.json` `model` (bindings + the
1083
- * connectorKey/objectName/method context) is byte-identical to what
1084
- * `registry get uipath.agent.resource.tool.connector.<key>.<op>` returns for
1085
- * the same operation, minus `source: true` (checked 2026-07-31 against
1086
- * `uipath-uipath-airdk/web-search`). What the tool era changes is the frame:
1087
- * the node type moves one namespace over, the category gains the
1088
- * `agent.tool.` prefix over the v1def's own (which carries the connector's
1089
- * numeric id — `connector.196640` → `agent.tool.connector.196640`), the
1090
- * handle becomes the tool artifact handle, and `sortOrder` is the registry's
1091
- * 565.
1092
- */
1093
- function connectorToolDef(rc) {
1094
- const v1 = rc.v1def ?? {};
1095
- const suffix = String(rc.nodeType).replace(/^uipath\.connector\./, '');
1096
- return {
1097
- nodeType: `uipath.agent.resource.tool.connector.${suffix}`,
1098
- version: v1.version ?? '1.0.0',
1099
- category: `agent.tool.${v1.category ?? 'connector'}`,
1100
- tags: ['agent-tool', 'connector', 'activity'],
1101
- sortOrder: 565,
1102
- display: { ...jsonClone(v1.display ?? {}), shape: 'circle' },
1103
- handleConfiguration: [toolInputHandle()],
1104
- model: { ...jsonClone(v1.model ?? {}), source: true },
1105
- inputDefinition: { type: 'object', properties: {} },
1106
- inputDefaults: {},
1107
- };
1108
- }
1109
- /** The folder key a connection with no folder binding carries in its resource.json. */
1110
- const NO_FOLDER_KEY = '00000000-0000-0000-0000-000000000000';
1111
- /**
1112
- * The library's operation description without the `(<Connector Name>) ` prefix
1113
- * the v1def's `display.description` carries — the model reads this text, and
1114
- * the designer's own resource.json has the bare sentence.
1115
- */
1116
- function connectorToolDescription(rc) {
1117
- const text = String(rc.v1def?.display?.description ?? rc.entry?.display?.description ?? '');
1118
- const prefix = `(${rc.connectorName}) `;
1119
- return rc.connectorName && text.startsWith(prefix) ? text.slice(prefix.length) : text;
1120
- }
1121
- /**
1122
- * An agent-decided value: `{{prompt: "<what the model is told>"}}`, byte for byte
1123
- * what the product writes — `uip maestro flow node configure` (UiPath/cli
1124
- * `connector-service.ts`, `` `{{prompt: "${f.description || f.displayName || f.name}"}}` ``)
1125
- * and the designer's `PROMPT_WRAPPER` (@uipath/maestro-sdk) both splice the text RAW.
1126
- * The chip format has no escape: the product's reader (`PROMPT_UNWRAPPER`) slices
1127
- * off the fixed `{{prompt:` / `}}` frame and strips one pair of outer quotes, so
1128
- * any text — `"`, `\`, a newline, a `}}` — round-trips. Escaping it here made the
1129
- * model read literal backslashes. FB011 registers this as the one raw chip splice.
1130
- */
1131
- function promptChip(f) {
1132
- const text = f.description || f.displayName || f.name;
1133
- return `{{prompt: "${text}"}}`;
1134
- }
1135
- const isPromptChip = (v) => typeof v === 'string' && v.startsWith('{{prompt');
1136
- /** The text inside `{{prompt: "…"}}`, read the way the product reads it (maestro-sdk PROMPT_UNWRAPPER); `''` when `value` is not a chip. */
1137
- function chipText(value) {
1138
- if (!(value.startsWith('{{prompt:') && value.endsWith('}}')))
1139
- return '';
1140
- const inner = value.slice(9, -2).trim();
1141
- const quoted = (inner.startsWith('"') && inner.endsWith('"')) || (inner.startsWith("'") && inner.endsWith("'"));
1142
- return quoted ? inner.slice(1, -1) : inner;
1143
- }
1144
- /** A field's enum as `{ name, value }` rows, the platform's spelling; `undefined` when it has none. */
1145
- function enumRows(raw) {
1146
- if (!Array.isArray(raw) || raw.length === 0)
1147
- return undefined;
1148
- return raw.map((e) => (typeof e === 'string'
1149
- ? { name: e, value: e }
1150
- : { name: String(e?.name ?? e?.value ?? ''), value: String(e?.value ?? e?.name ?? '') }));
1151
- }
1152
- /**
1153
- * The `inputs.detail` of a connector-tool node — the same key set a
1154
- * `connector()` step's node carries, with the request buckets filled the way
1155
- * `uip maestro flow node configure` fills them for a `tool.connector.*` node
1156
- * (uipcli `buildAgentToolParamsFromMetadata`): every BODY field is
1157
- * agent-decided (`{{prompt: "<description>"}}`) except a single-value enum,
1158
- * which is its one constant (the model would otherwise guess — `google` for
1159
- * `GoogleCustomSearch`); a QUERY/PATH parameter with a declared default sends
1160
- * it, and otherwise is agent-decided too.
1161
- */
1162
- function connectorToolDetail(rc, conn, folder, seed) {
1163
- const params = Array.isArray(rc.entry?.operation?.parameters) ? rc.entry.operation.parameters : [];
1164
- const buckets = { body: {}, query: {}, path: {} };
1165
- for (const f of rc.fields ?? []) {
1166
- const loc = f.location ?? 'body';
1167
- if (loc === 'body') {
1168
- const only = enumRows(f.enum);
1169
- buckets.body[f.name] = only && only.length === 1 ? only[0].value : promptChip(f);
1170
- }
1171
- else if (loc === 'query' || loc === 'path') {
1172
- const dflt = params.find((p) => p?.name === f.name)?.defaultValue;
1173
- buckets[loc][f.name] = dflt !== undefined && dflt !== null && dflt !== '' ? dflt : promptChip(f);
1174
- }
1175
- }
1176
- const parts = rc.multipartParts ?? [];
1177
- return {
1178
- connector: rc.connectorKey,
1179
- method: transportHttpMethod(rc.httpMethod),
1180
- endpoint: rc.endpoint,
1181
- ...(rc.objectName ? { objectName: rc.objectName } : {}),
1182
- inputMetadata: parts.length
1183
- ? { type: 'multipart', multipart: { bodyFieldName: multipartBodyFieldName(parts) } }
1184
- : {},
1185
- errorState: { hasError: false },
1186
- telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
1187
- configuration: buildConfiguration(rc.entry, rc.version, {}, seed),
1188
- ...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
1189
- ...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
1190
- ...(folder ? { connectionFolderKey: folder } : {}),
1191
- bodyParameters: buckets.body,
1192
- ...(Object.keys(buckets.path).length ? { pathParameters: buckets.path } : {}),
1193
- ...(Object.keys(buckets.query).length ? { queryParameters: buckets.query } : {}),
1194
- ...(parts.length ? { multipartParameters: parts.map((p) => ({ name: p.name, dataType: p.dataType })) } : {}),
1195
- };
1196
- }
1197
- /** Walk (creating) the object chain for a dotted field name; returns the parent schema and the leaf key. */
1198
- function nestedSlot(root, name) {
1199
- const segments = name.split('.');
1200
- let parent = root;
1201
- for (const seg of segments.slice(0, -1)) {
1202
- let child = parent.properties[seg];
1203
- if (!child || child.type !== 'object') {
1204
- child = { type: 'object', properties: {} };
1205
- parent.properties[seg] = child;
1206
- }
1207
- parent = child;
1208
- }
1209
- return { parent, leaf: segments[segments.length - 1] };
1210
- }
1211
- /**
1212
- * The `resources/<id>/resource.json` of a CONNECTOR tool (#867) — the file the
1213
- * agent runtime registers the tool from, and the one that puts the connection
1214
- * under the solution's Resources.
1215
- *
1216
- * The shape is uipcli's `mapConnectorToolToResource` (the writer behind `uip
1217
- * agent refresh --inline-in-flow`, itself ported from flow-workbench's
1218
- * `agent-connector-tool` mapper), in its metadata-enriched form: field types,
1219
- * enums, `required` and the response fields come from the curated library,
1220
- * which carries the same declarations that command fetches from Integration
1221
- * Service. Where that writer and a Studio Web export differ, this follows the
1222
- * writer: `enum`/`oneOf` only on agent-decided fields (a static field with an
1223
- * enum breaks the runtime's model build), no `additionalProperties`, and
1224
- * array-of-object response fields (`results[*]`) left out.
1225
- *
1226
- * The values come from the node's own `detail` — the same source refresh reads —
1227
- * so a later refresh rewrites this file to itself.
1228
- */
1229
- function connectorToolResourceJson(ctx, id) {
1230
- const { rc, detail } = ctx;
1231
- const valueOf = (raw) => raw !== null && typeof raw === 'object' ? JSON.stringify(raw) : String(raw ?? '');
1232
- const bucketOf = { body: 'bodyParameters', query: 'queryParameters', path: 'pathParameters' };
1233
- const fields = [];
1234
- for (const f of rc.fields ?? []) {
1235
- const loc = f.location ?? 'body';
1236
- if (loc === 'multipart')
1237
- continue;
1238
- const value = valueOf(detail[bucketOf[loc]]?.[f.name]);
1239
- fields.push({
1240
- name: f.name,
1241
- fieldLocation: loc,
1242
- type: f.type || 'string',
1243
- ...(loc === 'body' && enumRows(f.enum)
1244
- ? { enumValues: enumRows(f.enum) } : {}),
1245
- required: f.required === true,
1246
- description: f.description ?? '',
1247
- ...(f.displayName ? { displayName: f.displayName } : {}),
1248
- value,
1249
- isPrompt: isPromptChip(value),
1250
- });
1251
- }
1252
- for (const part of (detail.multipartParameters ?? [])) {
1253
- fields.push({
1254
- name: part.name, fieldLocation: 'multipart', type: part.dataType || 'string',
1255
- required: false, description: '', value: '{{prompt}}', isPrompt: true,
1256
- });
1257
- }
1258
- const inputSchema = { type: 'object', properties: {} };
1259
- for (const f of fields) {
1260
- if (f.fieldLocation === 'multipart')
1261
- continue;
1262
- const { parent, leaf } = nestedSlot(inputSchema, f.name);
1263
- const prop = { type: f.type, title: f.displayName ?? leaf, description: f.description };
1264
- if (f.isPrompt && f.enumValues) {
1265
- prop.enum = f.enumValues.map((e) => e.value);
1266
- prop.oneOf = f.enumValues.map((e) => ({ const: e.value, title: e.name }));
1267
- }
1268
- parent.properties[leaf] = prop;
1269
- if (f.required)
1270
- (parent.required ??= []).push(leaf);
1271
- }
1272
- const outputSchema = { type: 'object', properties: {}, definitions: {} };
1273
- for (const f of (rc.entry?.outputSchema?.fields ?? [])) {
1274
- if (typeof f?.name !== 'string' || f.name.includes('[*]'))
1275
- continue;
1276
- const { parent, leaf } = nestedSlot(outputSchema, f.name);
1277
- parent.properties[leaf] = {
1278
- type: f.type || 'string',
1279
- title: f.displayName ?? leaf,
1280
- ...(f.description ? { description: f.description } : {}),
1281
- };
1282
- }
1283
- const multipart = detail.inputMetadata?.type === 'multipart';
1284
- return {
1285
- $resourceType: 'tool',
1286
- name: ctx.name,
1287
- description: ctx.description,
1288
- location: 'external',
1289
- type: CONNECTOR_TOOL_RESOURCE_TYPE,
1290
- inputSchema,
1291
- outputSchema,
1292
- settings: {},
1293
- properties: {
1294
- toolPath: rc.endpoint ?? '',
1295
- objectName: rc.objectName ?? '',
1296
- toolDisplayName: ctx.name,
1297
- toolDescription: ctx.description,
1298
- // The operation KEY (`GETBYID` for a get-by-id), not the transport verb:
1299
- // the runtime sends it to Integration Service to pick the operation.
1300
- method: rc.httpMethod,
1301
- connection: {
1302
- id: detail.connectionId ?? '',
1303
- name: rc.connectorName,
1304
- elementInstanceId: 0,
1305
- apiBaseUri: '',
1306
- state: 'enabled',
1307
- isDefault: false,
1308
- connector: { key: rc.connectorKey, name: rc.connectorName, image: '' },
1309
- folder: { key: detail.connectionFolderKey ?? NO_FOLDER_KEY, path: '' },
1310
- },
1311
- parameters: fields.map((f, i) => ({
1312
- name: f.name,
1313
- displayName: f.displayName ?? f.name,
1314
- type: f.type,
1315
- fieldLocation: f.fieldLocation,
1316
- value: f.value,
1317
- // uipcli's fallback: an undescribed agent-decided field keeps the chip's own text.
1318
- description: f.description || (f.isPrompt ? chipText(f.value) : ''),
1319
- position: f.required ? 'primary' : 'secondary',
1320
- sortOrder: i + 1,
1321
- required: f.required,
1322
- fieldVariant: f.isPrompt ? 'dynamic' : 'static',
1323
- isCascading: false,
1324
- dynamic: f.isPrompt,
1325
- enumValues: f.enumValues ?? null,
1326
- loadReferenceOptionsByDefault: null,
1327
- dynamicBehavior: [],
1328
- reference: null,
1329
- })),
1330
- bodyStructure: multipart
1331
- ? { contentType: 'multipart', jsonBodySection: detail.inputMetadata.multipart?.bodyFieldName ?? 'body' }
1332
- : { contentType: 'json' },
1333
- },
1334
- guardrail: { policies: [] },
1335
- iconUrl: ctx.iconUrl,
1336
- id,
1337
- isEnabled: true,
1338
- };
1339
- }
1340
- /**
1341
- * The tool kinds that wrap a DEPLOYED tenant resource, and everything that
1342
- * differs between them. Transcribed from `registry get` over one live example
1343
- * of each (2026-07-31): the five agree on the whole frame (`bpmn:ServiceTask`,
1344
- * `version: 'v2'`, `section: 'Published'`, the `<bindings.name>` /
1345
- * `<bindings.folderPath>` context, `source: true`, version `1.0.0`, the tool
1346
- * artifact handle, `tags: ['agent-tool']`) and differ in exactly these rows.
1347
- */
1348
- const PROCESS_TOOL_META = {
1349
- process: { ns: 'process', serviceType: 'Orchestrator.StartJob', subType: 'Process', orchestratorType: 'process', category: 'agent.tool.rpa', icon: 'rpa', sortOrder: 550 },
1350
- api: { ns: 'api', serviceType: 'Orchestrator.ExecuteApiWorkflowAsync', subType: 'Api', orchestratorType: 'api', category: 'agent.tool.api', icon: 'api', sortOrder: 555 },
1351
- maestro: { ns: 'processorchestration', serviceType: 'Orchestrator.StartAgenticProcess', subType: 'ProcessOrchestration', orchestratorType: 'processorchestration', category: 'agent.tool.agentic-process', icon: 'agentic-process', sortOrder: 558 },
1352
- flow: { ns: 'flow', serviceType: 'Orchestrator.StartFlowProcess', subType: 'Flow', orchestratorType: 'flow', category: 'agent.tool.flow', icon: 'flow-project', sortOrder: 559 },
1353
- agent: { ns: 'agent', serviceType: 'Orchestrator.StartAgentJob', subType: 'Agent', orchestratorType: 'agent', category: 'agent.tool.agent', icon: 'autonomous-agent', sortOrder: 560 },
1354
- // A published FUNCTION as an agent tool (Phase 5): same shape as its
1355
- // siblings, with the function family's service type and binding subtype
1356
- // (see `publishedFunctionDef`, transcribed from the platform's manifest).
1357
- function: { ns: 'function', serviceType: 'Orchestrator.ExecuteFunctionAsync', subType: 'Function', orchestratorType: 'function', category: 'agent.tool.function', icon: 'function', sortOrder: 557 },
1358
- };
1359
- /**
1360
- * The definition for one per-instance tool resource. Like `rpaWorkflowDef`
1361
- * and its siblings — whose model half this shares verb for verb — the def
1362
- * cannot be bundled: the node type carries the deployed resource's own key,
1363
- * and the contract (`inputSchema`/`outputSchema` in `inputDefaults`, which is
1364
- * where the registry's tenant-synthesized copy puts it) comes from the author
1365
- * because offline nothing else can know it.
1366
- */
1367
- function processToolDef(kind, spec) {
1368
- const meta = PROCESS_TOOL_META[kind];
1369
- const inputProps = Object.keys(spec.inputs ?? {});
1370
- return {
1371
- nodeType: `uipath.agent.resource.tool.${meta.ns}.${spec.key}`,
1372
- version: '1.0.0',
1373
- category: meta.category,
1374
- tags: ['agent-tool'],
1375
- sortOrder: meta.sortOrder,
1376
- display: { label: spec.name, icon: meta.icon, shape: 'circle' },
1377
- handleConfiguration: [toolInputHandle()],
1378
- model: {
1379
- type: 'bpmn:ServiceTask',
1380
- serviceType: meta.serviceType,
1381
- version: 'v2',
1382
- section: 'Published',
1383
- bindings: {
1384
- resource: 'process',
1385
- resourceSubType: meta.subType,
1386
- resourceKey: `${spec.folderPath}.${spec.name}`,
1387
- orchestratorType: meta.orchestratorType,
1388
- values: [
1389
- { name: 'name', propertyAttribute: 'name', default: spec.name },
1390
- { name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
1391
- ],
1392
- },
1393
- context: [
1394
- { name: 'name', type: 'string', value: '<bindings.name>' },
1395
- { name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
1396
- { name: '_label', type: 'string', value: spec.name },
1397
- ],
1398
- source: true,
1399
- },
1400
- // The registry's per-tenant copy declares the resource's arguments here
1401
- // when it can read the deployed resource; offline the author's declaration
1402
- // is the only source (`{}` when none — the registry's own empty shape).
1403
- inputDefinition: inputProps.length ? fieldSchema(spec.inputs) : {},
1404
- inputDefaults: {
1405
- ...Object.fromEntries(inputProps.map((k) => [k, null])),
1406
- inputSchema: fieldSchema(spec.inputs),
1407
- outputSchema: fieldSchema(spec.returns),
1408
- },
1409
- };
1410
- }
1411
- /**
1412
- * The definition for one published IxP project AS A TOOL.
1413
- *
1414
- * The same registry family as `ixpExtractDef`'s one namespace over, and the
1415
- * comparison that prices it: this def has **no `model` key at all** — which is
1416
- * not special, it is the shape `tool.builtin.summarize` and
1417
- * `tool.builtin.batchtransform` already carry. So an IxP tool needs no bindings
1418
- * and no resource identity beyond the node type; the agent's own resource list
1419
- * is what points at it. (G16 deferred this family as "`model: null`" — measured
1420
- * again on 2026-07-31 with a fresh registry cache, the key is absent, and the
1421
- * only genuinely new thing is the 6-field `inputDefaults` below.)
1422
- *
1423
- * Transcribed from `registry get uipath.agent.resource.tool.ixp.<slug>.<projectId>`
1424
- * (2026-07-31, post-`pull --force`). Note the tail is the project's OWN id here,
1425
- * where an extraction STEP's type carries the project VERSION id.
1426
- */
1427
- function ixpToolDef(spec) {
1428
- const slug = spec.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
1429
- const attachmentDescription = spec.attachment?.description ?? 'File to extract data from.';
1430
- return {
1431
- nodeType: `uipath.agent.resource.tool.ixp.${slug}.${spec.projectId}`,
1432
- version: '1.0.0',
1433
- category: 'agent.tool.ixp',
1434
- tags: ['agent-tool', 'ixp', 'document-understanding'],
1435
- description: '',
1436
- sortOrder: 575,
1437
- display: {
1438
- label: spec.name,
1439
- description: spec.description ?? '',
1440
- icon: 'file-text',
1441
- shape: 'circle',
1442
- },
1443
- handleConfiguration: [toolInputHandle()],
1444
- form: {
1445
- id: 'ixp-tool-form',
1446
- title: 'IXP Tool',
1447
- sections: [{
1448
- id: 'ixp-settings',
1449
- title: 'IXP Settings',
1450
- collapsible: true,
1451
- defaultExpanded: true,
1452
- fields: [
1453
- { name: 'inputs.name', type: 'text', label: 'Name' },
1454
- { name: 'inputs.description', type: 'textarea', label: 'Description', placeholder: 'Enter tool description' },
1455
- {
1456
- name: 'inputs.versionTag', type: 'select', label: 'Model',
1457
- placeholder: 'Select a version tag',
1458
- dataSource: {
1459
- type: 'fetch',
1460
- url: `uipath://ixp/projects/${spec.projectId}/tags`,
1461
- method: 'GET',
1462
- transform: 'data.map(tag => ({ label: tag.name, value: tag.name }))',
1463
- },
1464
- },
1465
- {
1466
- name: 'inputs.attachmentConfig', type: 'custom', component: 'ixp-attachment-editor',
1467
- label: '', componentProps: { defaultDescription: attachmentDescription },
1468
- },
1469
- ],
1470
- }],
1471
- },
1472
- // The tool namespace's OWN `inputDefinition` — the same 11-property
1473
- // catalogue, but with `required: ["fileRef"]` and no `minLength`s, because a
1474
- // tool has no model or folder to resolve at design time (measured: the
1475
- // extractor's and the tool's copies are NOT equal, even post-pull) — plus
1476
- // ONE deliberate widening, three keys.
1477
- //
1478
- // The registry's copy declares neither `name` nor `description`, and its own
1479
- // `form` binds `inputs.name` and `inputs.description` as the tool's first two
1480
- // fields, so a node that omits them is a tool with no name. `source` is the
1481
- // third: the platform's validator requires a stable uuid there on every
1482
- // agent-resource node (MST-9265), and its own model-less builtin tool defs
1483
- // declare it as an input for exactly that reason. Widening is lesson 42's
1484
- // other branch, taken here because — unlike the extractor's `description` —
1485
- // these three are load-bearing rather than inert.
1486
- inputDefinition: {
1487
- ...jsonClone(IXP_DEF.toolInputDefinition),
1488
- properties: {
1489
- ...jsonClone(IXP_DEF.toolInputDefinition.properties),
1490
- source: { type: 'string' },
1491
- name: { type: 'string' },
1492
- description: { type: 'string' },
1493
- },
1494
- },
1495
- inputDefaults: {
1496
- name: spec.name,
1497
- description: spec.description ?? '',
1498
- projectName: spec.name,
1499
- projectId: spec.projectId,
1500
- versionTag: spec.versionTag ?? '',
1501
- attachmentConfig: {
1502
- mode: spec.attachment?.mode ?? 'prompt',
1503
- description: attachmentDescription,
1504
- argumentPath: spec.attachment?.argumentPath ?? '',
1505
- },
1506
- },
1507
- };
1508
- }
1509
- /**
1510
- * The definition for an ESCALATION — the `coded-action-app` variant, which is
1511
- * the one this tenant's registry serves. Transcribed from `registry get
1512
- * uipath.agent.resource.escalation.coded-action-app` (2026-07-31, v1.1).
1513
- *
1514
- * Node-type note, because it is the one place this card had to choose between
1515
- * eras: the deployed corpus (2026-05-22) carries the BARE
1516
- * `uipath.agent.resource.escalation`, today's registry resolves only the
1517
- * suffixed variants, and the agent definition's own handle accepts both
1518
- * (`allowedTargets: [{escalation}, {escalation.*}]`). New emission follows
1519
- * what the registry can still answer for.
1520
- */
1521
- function escalationDef() {
1522
- return {
1523
- nodeType: 'uipath.agent.resource.escalation.coded-action-app',
1524
- version: '1.1',
1525
- category: 'agent',
1526
- tags: ['agentic', 'ai', 'agent', 'escalation'],
1527
- description: 'Escalate to a human for review or action',
1528
- sortOrder: 5,
1529
- display: {
1530
- label: 'Action App Escalation',
1531
- description: 'Escalation point for human intervention',
1532
- icon: 'user-round', shape: 'circle',
1533
- },
1534
- handleConfiguration: [
1535
- {
1536
- position: 'bottom',
1537
- handles: [{
1538
- id: 'input', type: 'target', handleType: 'artifact',
1539
- constraints: {
1540
- maxConnections: 1,
1541
- allowedSources: [
1542
- { nodeType: 'uipath.agent.autonomous', handleId: 'escalation' },
1543
- { nodeType: 'uipath.agent.conversational', handleId: 'escalation' },
1544
- ],
1545
- validationMessage: "Only Agent's escalation handle can connect here",
1546
- },
1547
- }],
1548
- },
1549
- ],
1550
- model: { source: true },
1551
- // ONE deliberate widening over the registry's copy, documented rather than
1552
- // silent: the registry's inputDefinition declares only {name, description,
1553
- // type} while its own inputDefaults — and every deployed escalation node in
1554
- // the corpus — carry six more keys (app, recipients, outcomeMapping,
1555
- // _additionalProps, _notifications, _appInputs). A def that under-declares
1556
- // what the node carries makes a validator flag OUR OWN emission — it sets
1557
- // inputs its type does not declare (measured: v1's deployed escalation
1558
- // artifact draws the same six warnings). Declaring them keeps
1559
- // the node checkable, which is the same reason CX's context def carries a
1560
- // full schema (its comment: "without it a misspelled field reaches the
1561
- // tenant unremarked").
1562
- inputDefinition: {
1563
- type: 'object',
1564
- properties: {
1565
- name: { type: 'string', minLength: 1 },
1566
- description: { type: 'string' },
1567
- type: { type: 'string' },
1568
- app: { type: ['object', 'null'] },
1569
- recipients: { type: 'array' },
1570
- outcomeMapping: { type: ['object', 'null'] },
1571
- _additionalProps: { type: 'object' },
1572
- _notifications: { type: 'boolean' },
1573
- _appInputs: { type: ['object', 'null'] },
1574
- },
1575
- required: ['name'],
1576
- },
1577
- inputDefaults: {
1578
- name: 'Escalation',
1579
- description: '',
1580
- type: 'app-task',
1581
- app: null,
1582
- recipients: [],
1583
- outcomeMapping: null,
1584
- _additionalProps: { taskTitle: '', priority: 'medium', labels: [] },
1585
- _notifications: false,
1586
- _appInputs: null,
1587
- },
1588
- };
1589
- }
1590
- /**
1591
- * The definition for ONE MCP-server tool resource, mirrored from the platform's
1592
- * own mapper (`flow-core` `transformMcpServer`, read from the workbench dist —
1593
- * the registry serves the family per instance, tenant-side). The node type is
1594
- * minted from the server's name and key with the mapper's own slug rule.
1595
- */
1596
- function mcpToolDef(spec) {
1597
- const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
1598
- return {
1599
- nodeType: `uipath.agent.resource.tool.mcp.${slug(spec.name)}.${slug(spec.key)}`,
1600
- version: '1.0.0',
1601
- category: 'agent.tool.mcp',
1602
- tags: ['agent-tool', 'mcp', 'mcp-server'],
1603
- description: spec.description ?? '',
1604
- sortOrder: 585,
1605
- display: { label: spec.name, description: spec.description ?? '', icon: 'mcp', shape: 'circle' },
1606
- handleConfiguration: [toolInputHandle()],
1607
- model: { source: true },
1608
- inputDefaults: {
1609
- name: spec.name,
1610
- description: spec.description ?? '',
1611
- referenceKey: spec.key,
1612
- folderPath: spec.folderPath ?? '',
1613
- folderKey: spec.folderKey ?? '',
1614
- mcpType: spec.mcpType ?? '',
1615
- serviceName: spec.serviceName ?? '',
1616
- // The mapper's field name for the canonical server SLUG (historical).
1617
- serverUrl: spec.slug ?? '',
1618
- selectedTools: spec.selectedTools ?? [],
1619
- toolCatalog: spec.selectedTools ?? [],
1620
- discoveryMode: { type: 'cached', refreshSchemaBeforeCall: true },
1621
- },
1622
- };
1623
- }
1624
- /**
1625
- * The definition for ONE remote-A2A tool resource, mirrored from
1626
- * `transformRemoteA2aResource` — same provenance and slug rule as the MCP def.
1627
- */
1628
- function a2aToolDef(spec) {
1629
- const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
1630
- return {
1631
- nodeType: `uipath.agent.resource.tool.a2a.${slug(spec.name)}.${slug(spec.key)}`,
1632
- version: '1.0.0',
1633
- category: 'agent.tool.remote-a2a',
1634
- tags: ['agent-tool', 'remote-a2a', 'a2a', 'agent'],
1635
- description: spec.description ?? '',
1636
- sortOrder: 590,
1637
- display: { label: spec.name, description: spec.description ?? '', icon: 'a2a', shape: 'circle' },
1638
- handleConfiguration: [toolInputHandle()],
1639
- model: { source: true },
1640
- inputDefaults: {
1641
- name: spec.name,
1642
- description: spec.description ?? '',
1643
- slug: spec.slug,
1644
- cachedAgentCard: spec.cachedAgentCard ?? null,
1645
- referenceKey: spec.key,
1646
- resourceType: spec.resourceType ?? null,
1647
- folderPath: spec.folderPath ?? '',
1648
- folderKey: spec.folderKey ?? '',
1649
- resourceFolderPath: spec.resourceFolderPath ?? '',
1650
- },
1651
- outputDefinition: {},
1652
- };
1653
- }
1654
- /**
1655
- * The definition for ONE episodic-memory resource, mirrored from
1656
- * `transformEpisodicMemory`. The memory's identity rides ON the
1657
- * inputDefinition (`memoryId`/`folderKey`), verbatim — that is where the
1658
- * platform's own mapper puts it.
1659
- */
1660
- function memoryResourceDef(spec) {
1661
- const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
1662
- return {
1663
- nodeType: `uipath.agent.resource.memory.${slug(spec.name)}.${slug(spec.id)}`,
1664
- version: '1.0.0',
1665
- category: 'agent',
1666
- tags: ['agent-memory', 'memory'],
1667
- description: spec.description ?? '',
1668
- sortOrder: 15,
1669
- display: { label: spec.name, description: spec.description ?? '', icon: 'database', shape: 'circle' },
1670
- handleConfiguration: [{
1671
- position: 'bottom',
1672
- handles: [{
1673
- id: 'input', type: 'target', handleType: 'artifact',
1674
- constraints: {
1675
- maxConnections: 1,
1676
- allowedSources: [
1677
- { nodeType: 'uipath.agent.autonomous', handleId: 'memory' },
1678
- { nodeType: 'uipath.agent.conversational', handleId: 'memory' },
1679
- ],
1680
- validationMessage: "Only Agent's memory handle can connect here",
1681
- },
1682
- }],
1683
- }],
1684
- model: { source: true },
1685
- inputDefinition: {
1686
- memoryId: spec.id,
1687
- folderKey: spec.folderKey ?? '',
1688
- type: 'object',
1689
- properties: {
1690
- name: { type: 'string' },
1691
- description: { type: 'string' },
1692
- dynamicFewShotLearning: { type: 'boolean' },
1693
- semanticSimilarity: { type: 'number', minimum: 0, maximum: 1 },
1694
- kValue: { type: 'number' },
1695
- searchMode: { type: 'string' },
1696
- },
1697
- },
1698
- inputDefaults: {
1699
- name: spec.name,
1700
- description: spec.description ?? '',
1701
- dynamicFewShotLearning: true,
1702
- semanticSimilarity: 0,
1703
- kValue: 3,
1704
- searchMode: 'hybrid',
1705
- },
1706
- };
1707
- }
1708
- /** Numeric dotted compare: '1.10' > '1.9'. */
1709
- function compareVersions(a, b) {
1710
- const pa = a.split('.').map(Number);
1711
- const pb = b.split('.').map(Number);
1712
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1713
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1714
- if (d !== 0)
1715
- return d;
1716
- }
1717
- return 0;
1718
- }
1719
- /**
1720
- * The inline agent's bundled versions, oldest first, and the newest of them.
1721
- * The newest bundled pair is the default, so there is no version literal in
1722
- * this file: when the registry serves a newer version, `check:definitions`
1723
- * reports the missing pair, `sync:definitions` adds it, and the default follows.
1724
- */
1725
- const INLINE_AGENT_VERSIONS = Object.keys(DEFS)
1726
- .filter((k) => k.startsWith(`${INLINE_AGENT_TYPE}@`))
1727
- .map((k) => k.slice(INLINE_AGENT_TYPE.length + 1))
1728
- .sort(compareVersions);
1729
- const INLINE_AGENT_DEFAULT_VERSION = INLINE_AGENT_VERSIONS[INLINE_AGENT_VERSIONS.length - 1];
1730
- /** True when a definition declares the agent's `memory` artifact handle. */
1731
- function declaresMemoryHandle(def) {
1732
- return (def?.handleConfiguration ?? []).some((g) => (g?.handles ?? []).some((h) => h?.id === 'memory'));
1733
- }
1734
- /**
1735
- * The definition for the INLINE autonomous agent: the bundled pair for
1736
- * `version` (the newest by default), plus an explicit overlay of the details
1737
- * the registry payload does not carry.
1738
- *
1739
- * This is the one node type in the agent family that is the SAME on every
1740
- * tenant — it carries its own configuration instead of referencing a resource —
1741
- * so `uip maestro flow registry get uipath.agent.autonomous` is a direct oracle
1742
- * and the bundle entry is the registry's definition as last synced
1743
- * (`sync:definitions` writes it through unchanged; `check:definitions --deep`
1744
- * reports content that has moved since). Versions and content both come from
1745
- * there.
1746
- *
1747
- * The overlay, and nothing else:
1748
- *
1749
- * * `outputDefinition.output.source` is `=result.response`. The registry's
1750
- * definition declares no `source`; the deployed corpus (29 `uipath.agent.autonomous` nodes, 05-18 → 07-29) has
1751
- * `=result.response` in 27 of 27 that declare outputs, and the engine's own
1752
- * inline dispatch returns `{ response: { …declared keys } }`. Without it the
1753
- * node's `output` is the whole result envelope rather than the declared keys.
1754
- * It is applied at every version; the registry payload has no `source`.
1755
- * * The `memory` handle is kept only when the flow attaches memory. It is
1756
- * LOCAL content (#431) on the bundled 1.4, absent from the registry's 1.4
1757
- * (`check.ts` AGENT_MEMORY_NOT_AUTHORABLE), so an agent without memory gets
1758
- * the registry's handle set.
1759
- *
1760
- * NO per-instance schema goes on the definition: `definitions[]` is keyed by
1761
- * node TYPE, and every inline agent in a flow shares this one entry. A flow
1762
- * with two agents (an analyst and a writer, say) therefore cannot have both
1763
- * contracts here — whichever was emitted last would win, and every read against
1764
- * the other resolved to the wrong shape (GA row 7's port: 25 undeclared-field
1765
- * errors on a correct flow). The authored contract goes on the NODE
1766
- * (`node.outputs.output.schema`, in the `inlineAgent` arm), where a reader that
1767
- * refines per node picks it up.
1768
- *
1769
- * The bundled 1.2 is the pre-#956 default, kept so a flow this SDK compiled
1770
- * before — and a decompile of one, which pins `{ version: '1.2' }` — still
1771
- * compiles. The corpus also carries 1.1 and 1.3; `shared/sample-flows/
1772
- * inline-agent.flow` says 1.0, which appears in none of the 29.
1773
- */
1774
- function inlineAgentDef(version, withMemory, resolve = (t, v) => DEFS[`${t}@${v}`]) {
1775
- const bundled = resolve(INLINE_AGENT_TYPE, version);
1776
- if (!bundled)
1777
- return undefined;
1778
- const def = jsonClone(bundled);
1779
- const output = def.outputDefinition?.output;
1780
- if (output)
1781
- output.source = '=result.response';
1782
- if (!withMemory) {
1783
- for (const group of def.handleConfiguration ?? []) {
1784
- if (Array.isArray(group?.handles))
1785
- group.handles = group.handles.filter((h) => h?.id !== 'memory');
1786
- }
1787
- def.handleConfiguration = (def.handleConfiguration ?? []).filter((g) => g?.handles?.length);
1788
- }
1789
- return def;
1790
- }
1791
- /**
1792
- * Classify a RENDERED input value (the output of {@link renderValue}).
1793
- *
1794
- * A rendered value is an expression exactly when it starts with `=js:` — the
1795
- * same test the rest of the serializer applies, so a literal string that
1796
- * happens to begin with `=js:` is treated as one everywhere.
1797
- */
1798
- function classifyAgentInput(rendered) {
1799
- if (typeof rendered !== 'string' || !rendered.startsWith('=js:')) {
1800
- return { kind: 'literal', text: String(rendered) };
1801
- }
1802
- const flatName = flatNameOf(rendered);
1803
- return flatName === undefined
1804
- ? { kind: 'expression', binding: rendered }
1805
- : { kind: 'ref', flatName, binding: rendered };
1806
- }
1807
- /**
1808
- * The platform-facing plan for one inline agent's declared inputs.
1809
- *
1810
- * Built once and used by all three agent-facing sites (the node's
1811
- * `agentInputVariables`, the sidecar's `inputSchema`, and both prompts), so they
1812
- * cannot drift apart.
1813
- *
1814
- * `refs` is DEDUPED BY FLAT NAME and keeps author order. Two logical names bound
1815
- * to the same reference are one JobArgument on the platform (v1 dedupes on a
1816
- * seen-set, `:490-522`), so emitting two descriptors would claim an argument that
1817
- * will not exist.
1818
- */
1819
- function agentInputPlan(inputs, rename) {
1820
- const byName = new Map();
1821
- const refs = [];
1822
- const seen = new Set();
1823
- for (const [name, val] of Object.entries(inputs)) {
1824
- const b = classifyAgentInput(renderValue(val, rename));
1825
- byName.set(name, b);
1826
- if (b.kind === 'ref' && !seen.has(b.flatName)) {
1827
- seen.add(b.flatName);
1828
- refs.push({ flatName: b.flatName, binding: b.binding });
1829
- }
1830
- }
1831
- return { byName, refs };
1832
- }
1833
- /**
1834
- * Render an agent prompt for the SIDECAR — the author's placeholders rewritten to
1835
- * the names the runtime will actually have.
1836
- *
1837
- * The three cases mirror {@link AgentInputBinding}; the fourth (a placeholder
1838
- * naming nothing `inputs` declares) is left ALONE rather than deleted, exactly as
1839
- * the node's renderer leaves it, so it reaches the model as visible literal text
1840
- * and `check`'s `INLINE_AGENT_UNDECLARED_PLACEHOLDER` can name it.
1841
- *
1842
- * `contentTokens` is NOT rewritten separately — {@link promptTokens} runs over the
1843
- * rewritten string, so the designer's chips and the runtime's template can't
1844
- * disagree about a name.
1845
- */
1846
- function renderAgentSidecarPrompt(template, plan) {
1847
- return template.replace(INPUT_PLACEHOLDER, (whole, name) => {
1848
- const b = plan.get(name);
1849
- if (!b)
1850
- return whole;
1851
- if (b.kind === 'literal')
1852
- return b.text;
1853
- if (b.kind === 'ref')
1854
- return `{{input.${b.flatName}}}`;
1855
- return whole;
1856
- });
1857
- }
1858
- /**
1859
- * The `version` every `agent.json` sidecar declares — the agent tool's
1860
- * `AGENT_SCHEMA_VERSION` (uipath-cli `packages/agent-tool/src/templates/agent-template.ts`).
1861
- *
1862
- * It is not a free label. The agents backend reads an on-file `outputSchema`
1863
- * only at `version >= 1.2.0` (UiPath/Agents#5705), and since uipath-cli#3753
1864
- * `uip agent init` writes `1.2.0` and `uip agent validate` refuses anything else
1865
- * with `AgentValidationOutdated` ("Agent version "1.1.0" is outdated; latest is
1866
- * "1.2.0""). The bump is a version change only: `uip agent refresh` rewrites
1867
- * the field and nothing else. All three writers below share this one constant
1868
- * so they cannot fall behind separately (flow-builder-sdk#962).
1869
- */
1870
- const AGENT_DEFINITION_VERSION = '1.2.0';
1871
- /**
1872
- * The `agent.json` an inline agent's `source` directory must hold.
1873
- *
1874
- * The node is only half of an inline agent. flow-v1's `handleInlineAgentJob` emits
1875
- * `entryPoint: "<source>/agent.json"` into the BPMN context, and the runtime
1876
- * resolves the agent's real configuration — model settings, the message list, the
1877
- * input/output schemas — from that file relative to the project. A `.flow` with an
1878
- * inline-agent node and no such file references a definition that is not there.
1879
- *
1880
- * Shape transcribed from deployed agents under `runs/` (e.g. the four DevCon
1881
- * billing agents, `type: 'lowCode'`), which is also what
1882
- * `uip agent init --inline-in-flow` scaffolds. The `version` is
1883
- * {@link AGENT_DEFINITION_VERSION}, not the `1.1.0` those older agents carry. `contentTokens` mirrors `content` as
1884
- * one `simpleText` run: the designer uses those tokens to render a prompt with
1885
- * inline variable chips, and a single run is the faithful encoding of a plain
1886
- * string.
1887
- */
1888
- /**
1889
- * The `agent.json` sidecar for a text CONVERSATIONAL agent.
1890
- *
1891
- * The inline agent's shape minus the argument plumbing: the turn arrives as the
1892
- * conversation transcript, so there is no `inputSchema` keyed by flat names and
1893
- * no `outputSchema` (the definition declares
1894
- * `uipath__agent_response_messages`). `metadata.isConversational` is what tells
1895
- * the platform's own storage layer which kind of agent this is.
1896
- */
1897
- function conversationalAgentJson(spec) {
1898
- return {
1899
- version: AGENT_DEFINITION_VERSION,
1900
- settings: {
1901
- model: spec.model,
1902
- // Required by the platform's agent.json schema, same as the inline
1903
- // agent's — a debug run without both is refused before it starts.
1904
- maxTokens: spec.maxTokenPerResponse ?? 16384,
1905
- temperature: spec.temperature ?? 0,
1906
- // `basic-v2` is the NON-conversational inline agent's engine (the other
1907
- // `agent.json` writer in this file, 100 lines down), and it was copied in
1908
- // with the rest of that shape. Every instance of THIS node is
1909
- // conversational — `metadata.isConversational` below says so
1910
- // unconditionally — and `voiceAgentJson`, the sibling conversational
1911
- // family, already spells it the other way. Measured across the 2026-09-23
1912
- // A/B run: every product-written `agent.json` with `isConversational: true`
1913
- // carries `conversational-v1` (3 of 3) and every one with `false` carries
1914
- // `basic-v2` (6 of 6). The SDK's own voice path agreed (2 of 2); only this
1915
- // path disagreed, and its task graded the sidecar as not conversational.
1916
- engine: 'conversational-v1',
1917
- ...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
1918
- mode: 'standard',
1919
- },
1920
- projectId: spec.source,
1921
- id: spec.source,
1922
- name: spec.name,
1923
- metadata: { storageVersion: '50.0.0', isConversational: true, showProjectCreationExperience: false },
1924
- type: 'lowCode',
1925
- guardrails: spec.guardrails ?? [],
1926
- messages: [
1927
- { role: 'system', content: spec.systemPrompt, contentTokens: promptTokens(spec.systemPrompt) },
1928
- ],
1929
- };
1930
- }
1931
- /**
1932
- * The `agent.json` sidecar for a VOICE agent.
1933
- *
1934
- * The text agent's shape with voice-specific settings. The turn arrives as
1935
- * audio, while optional flow context is delivered through `inputSchema` and
1936
- * `{{input.…}}` system-prompt placeholders. There is no `outputSchema` (the
1937
- * definition declares what comes back), and `settings.voice` carries the
1938
- * audio configuration in the storage layer's own `StoredVoiceSettings` shape
1939
- * (`model` / `maxTokens` / `temperature` / `persona`) — with the platform's
1940
- * catalog defaults filled in, because its own writer treats a partial voice
1941
- * block as incomplete.
1942
- */
1943
- function voiceAgentJson(spec) {
1944
- // The platform's catalog default (`DEFAULT_CONVERSATIONAL_VOICE_SETTINGS`):
1945
- // the live Gemini model, its first persona, temperature 0.
1946
- const DEFAULT_MODEL = 'gemini-3.1-flash-live-preview';
1947
- const DEFAULT_MAX_TOKENS = 65536;
1948
- const DEFAULT_PERSONA = 'Aoede';
1949
- const v = spec.voice ?? {};
1950
- return {
1951
- version: AGENT_DEFINITION_VERSION,
1952
- settings: {
1953
- engine: 'conversational-v1',
1954
- mode: 'voice',
1955
- voice: {
1956
- model: v.model ?? DEFAULT_MODEL,
1957
- maxTokens: v.maxTokens ?? DEFAULT_MAX_TOKENS,
1958
- temperature: v.temperature ?? 0,
1959
- persona: v.persona ?? DEFAULT_PERSONA,
1960
- },
1961
- ...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
1962
- },
1963
- projectId: spec.source,
1964
- id: spec.source,
1965
- name: spec.name,
1966
- ...(spec.plan.refs.length === 0 ? {} : {
1967
- inputSchema: {
1968
- type: 'object',
1969
- properties: Object.fromEntries(spec.plan.refs.map((r) => [r.flatName, {}])),
1970
- },
1971
- }),
1972
- metadata: { storageVersion: '50.0.0', isConversational: true, showProjectCreationExperience: false },
1973
- type: 'lowCode',
1974
- guardrails: [],
1975
- messages: (() => {
1976
- const system = renderAgentSidecarPrompt(spec.systemPrompt, spec.plan.byName);
1977
- return [{ role: 'system', content: system, contentTokens: promptTokens(system) }];
1978
- })(),
1979
- };
1980
- }
1981
- function inlineAgentJson(spec) {
1982
- return {
1983
- version: AGENT_DEFINITION_VERSION,
1984
- settings: {
1985
- model: spec.model,
1986
- // `maxTokens` and `temperature` are REQUIRED by the platform's own
1987
- // agent.json schema, not optional as this used to treat them: a debug run of
1988
- // an agent without both is refused before it starts —
1989
- // settings.maxTokens → "expected number, received undefined"
1990
- // settings.temperature → "expected number, received undefined"
1991
- // (measured on the product runtime, GA row 7's port). Every SDK-authored
1992
- // inline agent hit it, and no local rung could: an offline runner calls no
1993
- // model and never reads agent.json, so the sidecar's platform contract had
1994
- // never been exercised.
1995
- //
1996
- // The defaults are what `uip agent init --inline-in-flow` writes and what
1997
- // v1's own reference agents for this scenario carry.
1998
- maxTokens: spec.maxTokenPerResponse ?? 16384,
1999
- temperature: spec.temperature ?? 0,
2000
- engine: 'basic-v2',
2001
- ...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
2002
- mode: spec.mode ?? 'standard',
2003
- },
2004
- // Also required, and also derived rather than authored: the agent's own
2005
- // directory uuid is its `projectId` and `id` (v1's scaffold writes the same
2006
- // value in all three places, and it is the node's `inputs.source`).
2007
- projectId: spec.source,
2008
- id: spec.source,
2009
- name: spec.name,
2010
- // Keyed by FLAT NAME, because that is what the runtime's JobArguments are
2011
- // keyed by (see the dialect note above). Literal- and expression-bound
2012
- // inputs are absent on purpose: neither has a JobArgument, so declaring one
2013
- // would promise an argument that never arrives.
2014
- //
2015
- // Each property is left EMPTY here and typed once the scope closes — see
2016
- // `typeAgentInputSchemas`, which also explains why a wrong type is fatal
2017
- // rather than untidy.
2018
- inputSchema: {
2019
- type: 'object',
2020
- properties: Object.fromEntries(spec.plan.refs.map((r) => [r.flatName, {}])),
2021
- },
2022
- outputSchema: {
2023
- type: 'object',
2024
- properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
2025
- required: Object.keys(spec.returns),
2026
- },
2027
- metadata: { storageVersion: '50.0.0', isConversational: false, showProjectCreationExperience: false },
2028
- type: 'lowCode',
2029
- guardrails: spec.guardrails ?? [],
2030
- // `content` and `contentTokens` are the half the platform keeps EXACTLY as
2031
- // authored (the CLI regenerates neither), so this is where the flatName
2032
- // rewrite has to land. Tokens are derived from the rewritten string rather
2033
- // than rewritten in parallel — the designer renders from the tokens and the
2034
- // runtime templates the content, and the two must name the same thing.
2035
- messages: (() => {
2036
- const system = renderAgentSidecarPrompt(spec.systemPrompt, spec.plan.byName);
2037
- const user = renderAgentSidecarPrompt(spec.userPrompt, spec.plan.byName);
2038
- return [
2039
- { role: 'system', content: system, contentTokens: promptTokens(system) },
2040
- { role: 'user', content: user, contentTokens: promptTokens(user) },
2041
- ];
2042
- })(),
2043
- };
2044
- }
2045
- /** `{{input.<name>}}` — the agent-template placeholder, in both prompt directions. */
2046
- const INPUT_PLACEHOLDER = /\{\{\s*input\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\}/g;
2047
- /**
2048
- * Split an agent prompt into the `contentTokens` the designer renders it from.
2049
- *
2050
- * The designer shows a prompt as prose with inline variable CHIPS, and `contentTokens`
2051
- * is that structure: alternating `simpleText` runs and `variable` entries whose
2052
- * `rawString` is `input.<name>`. Transcribed from deployed agents under `runs/` (the
2053
- * DevCon billing agents), where a prompt reading
2054
- * `"…Customer Name: {{input.customerName}}\nInvoice Number: {{input.invoiceNumber}}"`
2055
- * carries exactly that alternation.
2056
- *
2057
- * A prompt with no placeholders is one `simpleText` run, which is what a
2058
- * variable-free prompt looks like there too.
2059
- */
2060
- function promptTokens(content) {
2061
- const tokens = [];
2062
- let last = 0;
2063
- for (const m of content.matchAll(INPUT_PLACEHOLDER)) {
2064
- const at = m.index ?? 0;
2065
- if (at > last)
2066
- tokens.push({ type: 'simpleText', rawString: content.slice(last, at) });
2067
- tokens.push({ type: 'variable', rawString: `input.${m[1]}` });
2068
- last = at + m[0].length;
2069
- }
2070
- if (last < content.length)
2071
- tokens.push({ type: 'simpleText', rawString: content.slice(last) });
2072
- return tokens.length ? tokens : [{ type: 'simpleText', rawString: content }];
2073
- }
2074
123
  /**
2075
124
  * The parts every `uipath.ixp.*` definition shares, bundled verbatim from
2076
125
  * `uip maestro flow registry get` (2026-07-31, after `registry pull --force`).
@@ -2437,14 +486,6 @@ function scheduleInputs(inputs, nodeId) {
2437
486
  ...(isPreset ? {} : { timerValue: every }),
2438
487
  };
2439
488
  }
2440
- /**
2441
- * `Approve` → `approve`; a name that slugifies to nothing falls back to its index.
2442
- * The same rule that names the `outcome-<id>` ports (`outcomeSlug`), so an id
2443
- * this writes into `schema.outcomes` is always one a port can name.
2444
- */
2445
- function outcomeId(name, index) {
2446
- return outcomeSlug(name, index);
2447
- }
2448
489
  /** A required connector input left empty — the one draft code with no error class of its own. */
2449
490
  export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
2450
491
  /**
@@ -2508,10 +549,6 @@ function nodeVariables(nodes) {
2508
549
  }
2509
550
  return out;
2510
551
  }
2511
- /** Deep-substitute the `{nodeId}` placeholder used in definition model templates. */
2512
- function subNodeId(obj, nodeId) {
2513
- return JSON.parse(JSON.stringify(obj).split('{nodeId}').join(nodeId));
2514
- }
2515
552
  const NO_RENAME = { nodeIds: new Map(), errorEnvelopes: new Map() };
2516
553
  /**
2517
554
  * Append the flow's `.stickyNotes()` as real `stickyNote` nodes.
@@ -2806,37 +843,29 @@ function describeDeclared(bindings) {
2806
843
  /**
2807
844
  * The node type a typed action compiles to, when that type's definition is
2808
845
  * bundled in `core-definitions.json`. `undefined` for a family whose definition
2809
- * is built per tenant or per call (connector, rpaWorkflow, apiWorkflow,
2810
- * agenticProcess, publishedFunction, agent, ixpExtract) and for a spec this SDK
2811
- * does not lower by kind.
846
+ * is built per tenant or per call (connector, ixpExtract) and for a spec this
847
+ * SDK does not lower by kind.
2812
848
  *
2813
849
  * A registered kind (flow-node-registry.ts) is not listed: its type is the
2814
850
  * registry's.
2815
851
  */
2816
852
  export function bundledActionNodeType(spec) {
2817
853
  switch (spec?.kind) {
2818
- case 'inlineAgent': return INLINE_AGENT_TYPE;
2819
- case 'subflow':
2820
- case 'createOutgoingCall':
2821
- case 'endCall':
2822
- case 'voiceAgent':
2823
- case 'conversationalAgent':
2824
- return NODE_TYPE[spec.kind];
854
+ case 'subflow': return NODE_TYPE.subflow;
2825
855
  default: return undefined;
2826
856
  }
2827
857
  }
2828
858
  /**
2829
- * The definition serialize synthesizes for a family that has no bundled one:
2830
- * a published process resource (`rpaWorkflow`, `apiWorkflow`,
2831
- * `agenticProcess`, `publishedFunction`, `agent`) or an `ixpExtract` step.
2832
- * Their node type carries a tenant's key, so the SDK builds the definition
2833
- * per call, transcribed from what `registry get` returns. `undefined` for
2834
- * every other kind (a bundled family, a connector, a container).
859
+ * The definition serialize synthesizes for a kind that has no bundled one and
860
+ * is not on the node-type registry: an `ixpExtract` step. Its node type carries
861
+ * a tenant's key, so the SDK builds the definition per call, transcribed from
862
+ * what `registry get` returns. `undefined` for every other kind (a bundled
863
+ * family, a connector, a container, a registered kind — the published process
864
+ * resources and the agent build theirs in nodes/process/flow/).
2835
865
  *
2836
866
  * The emit path builds its definition here, and `check` asks this same
2837
867
  * definition whether the node has an error port (#1040), so the two cannot
2838
- * disagree. For instance `publishedFunction(...)` yields
2839
- * `uipath.core.function.<key>@1.0.0` with `supportsErrorHandling: true`.
868
+ * disagree.
2840
869
  *
2841
870
  * Throws what the family's builder throws for an unusable spec (an
2842
871
  * `ixpExtract` project that is not a node type).
@@ -2844,39 +873,10 @@ export function bundledActionNodeType(spec) {
2844
873
  export function perCallDefinition(spec) {
2845
874
  const s = spec?.inputs ?? {};
2846
875
  switch (spec?.kind) {
2847
- case 'agent':
2848
- return agentDef({
2849
- key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
2850
- inputs: s.inputs ?? {}, returns: s.returns, flavour: s.flavour,
2851
- });
2852
876
  case 'ixpExtract': return ixpExtractDef(s);
2853
877
  default: return undefined;
2854
878
  }
2855
879
  }
2856
- /**
2857
- * The definition version serialize emits for a typed action step that is not
2858
- * yet on the node-type registry: the author's `{ version }` when given,
2859
- * otherwise the version the step's inputs select, otherwise `undefined` (the
2860
- * pinned default). One such family picks a version itself: `inlineAgent`
2861
- * takes the newest bundled version, not the first bundled entry.
2862
- *
2863
- * The emit paths read their version here, and `check` resolves the definition
2864
- * it asks about with this same version (#1041), so the two cannot pick
2865
- * different definitions. For instance `inlineAgent({ ... })` gives the newest
2866
- * bundled version in both. A registered family (e.g. `dataFabricRead`, whose
2867
- * `resultMode` selects `core.datafabric.read@1.4`) gets the same guarantee from
2868
- * the registry's `resolveVersion`, which check and serialize both call.
2869
- */
2870
- export function actionVersion(spec, explicit) {
2871
- if (explicit !== undefined)
2872
- return explicit;
2873
- // Keyed by the node type `bundledActionNodeType` already picks, so the
2874
- // version rule adds no second dispatch on the step's kind.
2875
- const type = bundledActionNodeType(spec);
2876
- if (type === INLINE_AGENT_TYPE)
2877
- return INLINE_AGENT_DEFAULT_VERSION;
2878
- return undefined;
2879
- }
2880
880
  /**
2881
881
  * The bundled definition of `nodeType` that `makeNode` resolves: the author's
2882
882
  * exact `{ version }` when given, otherwise the pinned default (the first
@@ -4404,19 +2404,6 @@ export function serialize(built, opts = {}) {
4404
2404
  function successPort(type) {
4405
2405
  if (type === NODE_TYPE.loop)
4406
2406
  return 'success';
4407
- // The inline agent's forward handle is `success`, not `output` — its definition
4408
- // declares `success` (with `isDefaultForType`) + `error` on the right, and 29 of
4409
- // 29 deployed instances wire the outgoing edge to `success`. The PUBLISHED agent
4410
- // family is the opposite (`output` + `error`), which is a reminder that "the
4411
- // agent family" is two node shapes, not one.
4412
- if (type === INLINE_AGENT_TYPE)
4413
- return 'success';
4414
- // The voice family's own handles: the agent and both call actions declare
4415
- // `success` on the right (the two service tasks unlike their text-only
4416
- // conversational siblings, which declare `output`).
4417
- if (type === NODE_TYPE.voiceAgent || type === NODE_TYPE.createOutgoingCall
4418
- || type === NODE_TYPE.endCall || type === NODE_TYPE.conversationalAgent)
4419
- return 'success';
4420
2407
  // Every extractor's definition declares `success` (+ `error`, gated on
4421
2408
  // `errorHandlingEnabled`) on the right — so the forward edge is `success`,
4422
2409
  // as it is in all 151 deployed IxP instances.
@@ -4494,6 +2481,16 @@ export function serialize(built, opts = {}) {
4494
2481
  legacyCompletedNodes.add(id);
4495
2482
  else if (o.trackOutcomes && OUTCOME_HANDLE_NODE_TYPES.has(o.typeName))
4496
2483
  hitlNodes.set(id, node);
2484
+ for (const a of contribution.attach ?? []) {
2485
+ const def = a.definition;
2486
+ const rnode = makeNode(def.nodeType, a.id, a.label, { parentId: o.parentId, def });
2487
+ rnode.display = { ...rnode.display, shape: 'circle', ...(a.display ?? {}) };
2488
+ // The resource model lives on the DEFINITION only.
2489
+ delete rnode.model;
2490
+ rnode.inputs = { ...a.inputs };
2491
+ scope.nodes.push(rnode);
2492
+ addEdge(scope, { node: id, port: a.port }, a.id, 'input');
2493
+ }
4497
2494
  for (const t of o.tails)
4498
2495
  addEdge(scope, t, id);
4499
2496
  if (contribution.fanOut) {
@@ -4552,7 +2549,9 @@ export function serialize(built, opts = {}) {
4552
2549
  renameRefs: (js) => renameRefs(js, scope.rename),
4553
2550
  stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
4554
2551
  mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
4555
- bind: (b) => { bound.push(b); },
2552
+ // Into the flow's bindings at once as well, so `ctx.bindings` shows a pair
2553
+ // to the step's next resource (two tools on one process share it).
2554
+ bind: (b) => { bound.push(b); resourceBindings.set(b.id, b); },
4556
2555
  // At a carried version the node's own definition IS the carried one,
4557
2556
  // whatever version the implementation asks for (it was written for a
4558
2557
  // bundled one), so its defaults and outputs come from the file's entry.
@@ -4560,7 +2559,7 @@ export function serialize(built, opts = {}) {
4560
2559
  ? resolved.carried
4561
2560
  : bundledDefinition(typeName, v) ?? carriedFor(typeName, v)),
4562
2561
  bindings: resourceBindings,
4563
- resolveBinding: (name, attribute, at) => resolveBinding(opts, name, attribute, at),
2562
+ resolveBinding: ((name, attribute, at, target) => resolveBinding(opts, name, attribute, at, target)),
4564
2563
  bindConnection: (c) => {
4565
2564
  const { entries } = connectionResourceBindings({
4566
2565
  def: { model: c.model },
@@ -4570,7 +2569,15 @@ export function serialize(built, opts = {}) {
4570
2569
  ...(c.resourceKey !== undefined ? { resourceKey: c.resourceKey } : {}),
4571
2570
  }, resourceBindings);
4572
2571
  bound.push(...entries);
2572
+ for (const b of entries)
2573
+ resourceBindings.set(b.id, b);
4573
2574
  },
2575
+ agentSource: (carried) => agentFolder(carried, step.name),
2576
+ sidecar: (file, explicitSource) => addSidecar(file, explicitSource),
2577
+ typeAgentInputs: (schema, refs) => { scope.agentSchemas.push({ schema: schema, refs: [...refs] }); },
2578
+ resourceNodeId: (explicit, seed, at) => resourceNodeId(scope, explicit, seed, at),
2579
+ emitted: (typeName, v) => emittedDefs.get(`${typeName}@${v}`),
2580
+ library: opts.library,
4574
2581
  };
4575
2582
  const bound = [];
4576
2583
  const contribution = impl.serialize(spec, ctx);
@@ -4604,7 +2611,12 @@ export function serialize(built, opts = {}) {
4604
2611
  + `${JSON.stringify(nodeType)}, which is not a name under "${prefix}".`);
4605
2612
  }
4606
2613
  if (impl.definition === 'per-tenant') {
4607
- const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
2614
+ // At a version the flow carries, the carried definition answers: the
2615
+ // type's declaration speaks for the versions it registers, and an older
2616
+ // tenant definition (an inline agent at 1.1) may have no error handle.
2617
+ const port = resolved.carried !== undefined
2618
+ ? definitionHasErrorPort(def ?? resolved.carried)
2619
+ : typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
4608
2620
  // The declaration answers for check, which has no definition; here the
4609
2621
  // definition is in hand, and the two must agree (#1011).
4610
2622
  if (port && def !== undefined && !definitionHasErrorPort(def)) {
@@ -4792,734 +2804,6 @@ export function serialize(built, opts = {}) {
4792
2804
  addEdge(scope, t, id);
4793
2805
  return [{ node: id, port: successPort(rc.nodeType) }];
4794
2806
  }
4795
- if (spec.kind === 'agent') {
4796
- // A published agent is the FOURTH member of the process-resource family, and
4797
- // it goes down the same path for the same reason the other three share one:
4798
- // the node body, the binding pair and the model-stripping are identical, and
4799
- // what differs is the definition it carries. Kept as its own branch only
4800
- // because `inputs` is required here (the empty-input floor — see
4801
- // `AgentInputs.inputs`) and because the emitted binding subtype is `Agent`.
4802
- const s = spec.inputs;
4803
- const def = perCallDefinition(spec);
4804
- // The pair's key and folder come off the definition, as for rpa / agentic
4805
- // steps: one place decides the in-solution shape.
4806
- const pairFolder = def.model.bindings.values.find((v) => v.name === 'folderPath').default;
4807
- const { entries } = processResourceBindings({ name: s.name, folderPath: pairFolder, resourceSubType: 'Agent', resourceKey: def.model.bindings.resourceKey }, resourceBindings);
4808
- for (const b of entries)
4809
- resourceBindings.set(b.id, b);
4810
- const id = stepUid(scope, step.name);
4811
- const node = makeNode(def.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def });
4812
- // The resource model stays on the DEFINITION only, as it does for the other
4813
- // three published families — 58 of 61 deployed agent instances carry no
4814
- // node-level `model` at all.
4815
- delete node.model;
4816
- // Expression inputs stay `=js:` strings here, published or in-solution, and
4817
- // the format migration wraps them as `{ type: 'jsExpression', expression }`
4818
- // — the shape flow-core writes for an in-solution agent (52 of 54 v1
4819
- // expression inputs, 09-01 → 10-08; the other 2 are bare `=js:` strings,
4820
- // none is a `{{ }}` template). A `{ type: 'literal',
4821
- // expression: '{{ … }}' }` template used to be emitted instead (#1013): it
4822
- // ran, but it is the same envelope as a literal string, so decompile read
4823
- // it back as one.
4824
- node.inputs = renderMap(s.inputs, scope.rename);
4825
- scope.nodes.push(node);
4826
- for (const t of tails)
4827
- addEdge(scope, t, id);
4828
- return [{ node: id, port: successPort(def.nodeType) }];
4829
- }
4830
- if (spec.kind === 'inlineAgent') {
4831
- const s = spec.inputs;
4832
- const returns = s.returns && Object.keys(s.returns).length
4833
- ? s.returns
4834
- // The definition's own default. Deliberately kept rather than made
4835
- // required: it is what the platform scaffolds, so a flow that reads
4836
- // `content` is a real (if toy) flow. `check` says so.
4837
- : { content: 'string' };
4838
- // ONE default version for every call shape — the newest bundled pair (see
4839
- // `inlineAgentDef`). `mode` and `memory` used to pick the version too (1.3
4840
- // and 1.4), so a plain agent got a superseded 1.2 and each option moved
4841
- // the node to another version as a side effect (#956). Both now live in
4842
- // the sidecar and on edges; neither selects a definition. An explicit
4843
- // `{ version }` (what decompile writes back) resolves exactly.
4844
- const version = actionVersion(spec, step.options?.version);
4845
- // `definitions[]` holds one entry per type@version, so a flow where any
4846
- // agent attaches memory keeps the handle for all of them.
4847
- const withMemory = s.memory !== undefined
4848
- || declaresMemoryHandle(emittedDefs.get(`${INLINE_AGENT_TYPE}@${version}`));
4849
- const def = inlineAgentDef(version, withMemory, definitionFor);
4850
- if (!def) {
4851
- throw new Error(`"${step.name}" requests ${INLINE_AGENT_TYPE}@${version}, but this SDK resolves only `
4852
- + `${INLINE_AGENT_VERSIONS.map((v) => `${INLINE_AGENT_TYPE}@${v}`).join(', ')}. Versions are exact — `
4853
- + `omit { version } to use the default (${INLINE_AGENT_DEFAULT_VERSION}), request a version this SDK ships, `
4854
- + `or carry that version's definition with .definitions([...]).`);
4855
- }
4856
- if (s.memory !== undefined && !declaresMemoryHandle(def)) {
4857
- throw new Error(`"${step.name}" attaches \`memory\`, but ${INLINE_AGENT_TYPE}@${version} declares no \`memory\` handle. `
4858
- + (version === INLINE_AGENT_DEFAULT_VERSION
4859
- ? `Pin a version that has it with inlineAgent({ ..., version: '<x.y>' }) `
4860
- + `(bundled with memory: ${INLINE_AGENT_VERSIONS.filter((v) => declaresMemoryHandle(DEFS[`${INLINE_AGENT_TYPE}@${v}`])).join(', ') || 'none'}), or drop \`memory\`.`
4861
- : `Drop { version: '${version}' } to use ${INLINE_AGENT_DEFAULT_VERSION}, or drop \`memory\`.`));
4862
- }
4863
- const id = stepUid(scope, step.name);
4864
- // `source` is the agent's directory name; see `agentFolder`.
4865
- const source = agentFolder(s.source, step.name);
4866
- const node = makeNode(def.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def });
4867
- // Unlike the published families the inline agent KEEPS no node-level model:
4868
- // 29 of 29 deployed instances carry none (the def's `model.source: true` is
4869
- // what tells the serializer the node has a `source` input).
4870
- delete node.model;
4871
- // The per-instance CONTRACT lands here, on the node, because the definition
4872
- // is shared by every inline agent in the flow and cannot hold it (see
4873
- // `inlineAgentDef`). `makeNode` copied the definition's generic `output`
4874
- // descriptor; this adds the declared keys to that copy, which is the shape
4875
- // an instance-refining reader picks up.
4876
- if (Object.keys(returns).length && node.outputs?.output) {
4877
- node.outputs.output.schema = {
4878
- type: 'object',
4879
- properties: Object.fromEntries(Object.entries(returns).map(([k, t]) => [k, { type: t }])),
4880
- };
4881
- }
4882
- const nodeInputs = s.inputs ?? {};
4883
- // What the PLATFORM will call each of these — computed once and shared by
4884
- // the descriptor array below and the sidecar. See `agentInputPlan`.
4885
- const plan = agentInputPlan(nodeInputs, scope.rename);
4886
- // A SHELL node (#927): the prompts and the model live only in the
4887
- // `<source>/agent.json` sidecar written below, never on the node. Studio
4888
- // Web decides which copy wins by `hasEmbeddedAgentContent`
4889
- // (@uipath/flow-schema): any `systemPrompt`/`userPrompt` string on the node
4890
- // makes the `.flow` authoritative and the sidecar is skipped, so a node copy
4891
- // shadowed the sidecar and lost the input tokens and the tool configuration
4892
- // on the first save. With no prompt strings the converter also keeps the
4893
- // `agentInputVariables` below as written (`canPruneAgentInputs` is false),
4894
- // and `uip maestro flow debug` reconciles them against the sidecar's
4895
- // `inputSchema`.
4896
- //
4897
- // The run settings (`mode`, `temperature`, `maxTokenPerResponse`,
4898
- // `maxIterations`, `guardrails`) live in the sidecar too. The node keeps
4899
- // exactly the keys Studio Web's own strip keeps (`toAgentShellInputs`,
4900
- // @uipath/flow-schema): anything else is discarded on hydrate, and while
4901
- // present it makes Studio Web "trust the .flow" when the sidecar folder is
4902
- // missing (`flowNodeHasRealInputs`), flushing a prompt-less agent.json.
4903
- // `modelMaxTokens` is written nowhere: the designer derives it from the
4904
- // selected model.
4905
- node.inputs = {
4906
- source,
4907
- // The two DESCRIPTOR ARRAYS. `agentInputVariables` entries carry the
4908
- // binding expression; `agentOutputVariables` entries are name + type only,
4909
- // and the runtime's structured final call fills exactly these keys.
4910
- //
4911
- // `id` is the FLAT NAME, not the author's — the name the runtime's
4912
- // JobArguments are keyed by, and the key the CLI reconciles against the
4913
- // sidecar's `inputSchema`. One entry per flatName, so two logical names
4914
- // bound to the same reference claim one argument rather than two.
4915
- agentInputVariables: plan.refs.map((r) => ({
4916
- id: r.flatName,
4917
- type: 'string',
4918
- binding: r.binding,
4919
- })),
4920
- agentOutputVariables: Object.entries(returns).map(([k, t]) => ({ id: k, type: t })),
4921
- };
4922
- scope.nodes.push(node);
4923
- // The other half of the artifact: the agent's own configuration, which the
4924
- // platform resolves at `<source>/agent.json`. Collected here and written by
4925
- // compile-cli beside the `.flow`.
4926
- const agentJson = inlineAgentJson({
4927
- model: s.model,
4928
- // The AGENT's dialect: `{{input.x}}` placeholders, rewritten from the
4929
- // author's logical names to the FLAT NAMES the runtime's JobArguments
4930
- // are keyed by (board G-20). `inlineAgentJson` does the rewrite so the
4931
- // schema keys, the message text and the tokens all come off one plan.
4932
- systemPrompt: s.systemPrompt,
4933
- userPrompt: s.userPrompt,
4934
- plan,
4935
- returns,
4936
- temperature: s.temperature,
4937
- maxTokenPerResponse: s.maxTokenPerResponse,
4938
- maxIterations: s.maxIterations,
4939
- mode: s.mode,
4940
- guardrails: s.guardrails,
4941
- source,
4942
- name: step.name,
4943
- });
4944
- addSidecar({ path: `${source}/agent.json`, json: agentJson });
4945
- // The schema's property TYPES need this scope's finished variables, which
4946
- // do not exist yet — park them (the `properties` object is filled in
4947
- // place when the scope closes). See `typeAgentInputSchemas`.
4948
- scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
4949
- // GROUNDING — one resource node per context index, joined to the agent's own
4950
- // `context` handle. An ARTIFACT, not a step: it takes no incoming control
4951
- // flow, produces no output, and never reaches the End node. The edge runs OUT
4952
- // of the agent (source port `context`) INTO the resource's `input` port,
4953
- // which is the direction both definitions declare.
4954
- //
4955
- // Emitted after the agent node so the resource sits beside it in the
4956
- // designer's default layout rather than in front of it.
4957
- const contexts = s.context === undefined ? []
4958
- : Array.isArray(s.context) ? s.context : [s.context];
4959
- for (const cx of contexts) {
4960
- // `check`'s AGENT_CONTEXT_NO_ID, again for `--no-check`: a blank id builds
4961
- // the type `…context.index.<slug>.`, which no tenant serves, and nothing
4962
- // after this point would say so.
4963
- if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
4964
- throw Object.assign(new Error(`inline agent "${step.name}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
4965
- + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
4966
- }
4967
- const cdef = contextIndexDef({ name: cx.name, id: cx.id });
4968
- // `<step>Context`, deduped by `uid` for a second index on the same agent,
4969
- // unless the author placed it (`nodeId`, which decompile writes for a
4970
- // node the canvas named). Not slugged from the index NAME: an id is the
4971
- // author's handle on the node and a 40-character one helps nobody.
4972
- const cid = resourceNodeId(scope, cx.nodeId, `${step.name}Context`, `inline agent "${step.name}": context index "${cx.name}"`);
4973
- const cnode = makeNode(cdef.nodeType, cid, cx.label ?? cx.name, { parentId, def: cdef });
4974
- // A resource is drawn as a circle, and `makeNode` copies only the icon.
4975
- cnode.display = { ...cnode.display, shape: 'circle' };
4976
- // Same rule as the agent's: the resource `model` lives on the DEFINITION.
4977
- // What the NODE carries is the index's own configuration, and the platform's
4978
- // `inputDefaults` are the shape a designer-placed node has — so an omitted
4979
- // option emits the designer's value rather than nothing.
4980
- delete cnode.model;
4981
- // ONE identity, spent twice: the node's `inputs.source` and the sidecar
4982
- // resource's `id` are the same uuid, because that is what joins them. The
4983
- // platform validator says so in its own refusal text — *"the resource id
4984
- // from the parent inline agent's `resources[]`"*.
4985
- const contextResourceId = cx.source ?? derivedContextSource(built.id, step.name, cx.id);
4986
- const contextQuery = renderContextQuery(cx.query, scope.rename);
4987
- cnode.inputs = {
4988
- // REQUIRED, and the platform's validator is what says so — measured
4989
- // 2026-07-31, omitting it: *"Node type
4990
- // uipath.agent.resource.context.index.… requires a stable UUID at
4991
- // inputs.source … Set \"inputs\": { \"source\": \"<uuid>\" } on the
4992
- // node"* (MST-9265). It is the resource's own identity inside the
4993
- // project, so it is derived from flow + step + index rather than random,
4994
- // exactly like the agent's `source`: a recompile must not be a diff.
4995
- // An author's `source` (a canvas directory decompile carried) wins.
4996
- source: contextResourceId,
4997
- indexId: cx.id,
4998
- indexName: cx.name,
4999
- ...(cx.folderKey === undefined ? {} : { folderKey: cx.folderKey }),
5000
- ...(cx.folderPath === undefined ? {} : { folderPath: cx.folderPath }),
5001
- retrievalMode: cx.retrievalMode ?? 'semantic',
5002
- // The platform's `query` is a MODE-TAGGED object, not a bare string:
5003
- // `{mode, textValue, promptValue, argumentPath}`, with the mode saying
5004
- // which field is live. A plain string is the `prompt` mode; a flow
5005
- // expression is `text-builder`, whose `textValue` the runtime templates.
5006
- query: contextQuery,
5007
- threshold: cx.threshold ?? 0,
5008
- resultCount: cx.resultCount ?? 3,
5009
- fileExtension: cx.fileExtension ?? 'All',
5010
- folderPathPrefix: { mode: 'text-builder', textValue: '', promptValue: '', argumentPath: '' },
5011
- citations: 'enabled',
5012
- outputColumns: [],
5013
- webSearchGrounding: { value: 'enabled' },
5014
- };
5015
- scope.nodes.push(cnode);
5016
- addEdge(scope, { node: id, port: 'context' }, cid, 'input');
5017
- // The other half — without it the node is inert: see
5018
- // `contextResourceJson`.
5019
- addSidecar({
5020
- path: `${source}/resources/${contextResourceId}/resource.json`,
5021
- json: contextResourceJson(cx, { id: contextResourceId, query: contextQuery }),
5022
- }, cx.source !== undefined);
5023
- }
5024
- // TOOLS — one resource node per tool, joined to the agent's `tool` handle;
5025
- // the same artifact shape as context (no incoming control flow, no output,
5026
- // never reaches End — a graph walk drops it).
5027
- //
5028
- // Every tool node carries `inputs.source` — the platform's validator
5029
- // requires "a stable UUID at inputs.source … the resource id from the
5030
- // parent inline agent's resources[] for agent-tool resource nodes"
5031
- // (MST-9265's own wording) — derived, so a recompile is not a diff.
5032
- const toolRefs = s.tools === undefined ? []
5033
- : Array.isArray(s.tools) ? s.tools : [s.tools];
5034
- for (const tr of toolRefs) {
5035
- let tdef;
5036
- let label;
5037
- let extraInputs = {};
5038
- let connectorTool;
5039
- if (tr.kind === 'builtin') {
5040
- const def = ownEntry(BUILTIN_TOOL_DEFS, tr.tool);
5041
- tdef = def === undefined ? undefined : jsonClone(def);
5042
- if (!tdef)
5043
- throw new Error(`inline agent "${step.name}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
5044
- label = tdef.display.label;
5045
- // The designer-placed node carries the def's `inputDefaults` with the
5046
- // author's values over them — CX's context rule, kept per builtin
5047
- // (each def declares different fields, so overrides apply only where
5048
- // the def has the field).
5049
- extraInputs = jsonClone(tdef.inputDefaults);
5050
- // summarize/batchtransform declare `source` among their defaults (as
5051
- // an empty string); the derived uuid below must win, not be clobbered.
5052
- delete extraInputs.source;
5053
- if (tr.description !== undefined)
5054
- extraInputs.description = tr.description;
5055
- if ('query' in extraInputs && tr.query !== undefined) {
5056
- extraInputs.query = renderContextQuery(tr.query, scope.rename);
5057
- }
5058
- if ('fileExtension' in extraInputs && tr.fileExtension !== undefined) {
5059
- extraInputs.fileExtension = { value: tr.fileExtension };
5060
- }
5061
- if ('citationMode' in extraInputs && tr.citationMode !== undefined) {
5062
- extraInputs.citationMode = { value: tr.citationMode };
5063
- }
5064
- }
5065
- else if (tr.kind === 'ixp') {
5066
- tdef = ixpToolDef(tr);
5067
- label = tr.name;
5068
- // The node carries the def's own `inputDefaults` (name / description /
5069
- // project identity / attachment config) with the derived `source` over
5070
- // them — the builtin-tool rule, not the process-tool one, because this
5071
- // family's configuration lives on the NODE rather than in a binding:
5072
- // its def declares no `model` at all, so there is no resource identity
5073
- // to bind and nothing to add to `bindings[]`.
5074
- extraInputs = jsonClone(tdef.inputDefaults);
5075
- }
5076
- else if (tr.kind === 'connector') {
5077
- if (!opts.library)
5078
- throw new Error(`inline agent "${step.name}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
5079
- const rc = opts.library.resolve(tr.connector, tr.operation, tr.version, tr.object);
5080
- // An entry `prepare` synthesized from Integration Service compiles in
5081
- // BPMN only — for a tool as for a step: `connectorToolDef` reads the
5082
- // same definition, and a synthesized one has no form or handles.
5083
- const noDefinition = synthesizedDefinitionRefusal(rc);
5084
- if (noDefinition)
5085
- throw noDefinition;
5086
- tdef = connectorToolDef(rc);
5087
- label = tr.name ?? rc.v1def?.display?.label ?? tr.operation;
5088
- // The connection rides the same three places it does for a
5089
- // `connector()` step, because the platform reads it from the same
5090
- // three (#867):
5091
- // * the node's `inputs.detail` — what `uip maestro flow node configure`
5092
- // writes on a `tool.connector.*` node, what `uip agent refresh
5093
- // --inline-in-flow` builds the tool's resource.json FROM, and what
5094
- // its shell-ify pass keeps ("where a connector tool's request config
5095
- // lives"). It is also what `bindings_v2.json` is derived from.
5096
- // * the top-level `bindings[]` pair the def's `model.context`
5097
- // placeholders (`<bindings.… connection>` / `<bindings.FolderKey>`)
5098
- // name.
5099
- // * the agent's `resources/<id>/resource.json` (below).
5100
- // Emitting none of them left the tool with no connection anywhere.
5101
- const where = `inline agent "${step.name}"'s connector tool "${tr.connector}/${tr.operation}"`;
5102
- const target = { key: tr.connector, action: tr.operation };
5103
- const conn = resolveBinding(opts, tr.connection, 'connection', where, target);
5104
- const folder = resolveBinding(opts, tr.folder, 'folder', where, target);
5105
- const { entries: connBindings } = connectionResourceBindings({ def: rc.v1def, connectorKey: rc.connectorKey, connectionId: conn, folderKey: folder }, resourceBindings);
5106
- for (const b of connBindings)
5107
- resourceBindings.set(b.id, b);
5108
- const detail = connectorToolDetail(rc, conn, folder, `${step.name}Tool`);
5109
- extraInputs = { detail };
5110
- connectorTool = {
5111
- rc, detail, name: label,
5112
- description: tr.description ?? connectorToolDescription(rc),
5113
- iconUrl: String(tdef.display?.icon ?? ''),
5114
- };
5115
- }
5116
- else if (tr.kind === 'mcp') {
5117
- // Per-instance def minted like the ixp family's; the node carries the
5118
- // def's own defaults (server identity + tool selection) — the
5119
- // configuration lives on the node, not in a binding.
5120
- tdef = mcpToolDef(tr);
5121
- label = tr.name;
5122
- extraInputs = jsonClone(tdef.inputDefaults);
5123
- }
5124
- else if (tr.kind === 'a2a') {
5125
- tdef = a2aToolDef(tr);
5126
- label = tr.name;
5127
- extraInputs = jsonClone(tdef.inputDefaults);
5128
- }
5129
- else if (tr.kind === 'clientside') {
5130
- // A static bundled def: the flow declares only the CONTRACT — the
5131
- // calling application owns the implementation.
5132
- tdef = jsonClone(DEFS['uipath.agent.resource.tool.clientside@1.0']);
5133
- label = tr.name;
5134
- extraInputs = {
5135
- ...jsonClone(tdef.inputDefaults),
5136
- name: tr.name,
5137
- ...(tr.description !== undefined ? { description: tr.description } : {}),
5138
- ...(tr.inputs !== undefined ? { inputSchema: fieldSchema(tr.inputs) } : {}),
5139
- ...(tr.returns !== undefined ? { outputSchema: fieldSchema(tr.returns) } : {}),
5140
- };
5141
- }
5142
- else if (tr.kind === 'httpRequest') {
5143
- // Each field is a mode triple: a FIXED author value rides as
5144
- // text-builder (headers/params as name/value rows, timeout numeric —
5145
- // the storage layer's own normalization), an omitted one keeps the
5146
- // definition's prompt-mode default so the MODEL fills it at call time.
5147
- tdef = jsonClone(DEFS['uipath.agent.resource.tool.builtin.httprequest@1.0']);
5148
- label = tr.name ?? tdef.display.label;
5149
- extraInputs = jsonClone(tdef.inputDefaults);
5150
- const fix = (field, value) => {
5151
- const row = extraInputs[field];
5152
- extraInputs[field] = { ...row, mode: 'text-builder', textValue: value };
5153
- };
5154
- if (tr.url !== undefined)
5155
- fix('url', tr.url);
5156
- if (tr.method !== undefined)
5157
- fix('method', tr.method);
5158
- if (tr.headers !== undefined)
5159
- fix('headers', tr.headers);
5160
- if (tr.params !== undefined)
5161
- fix('params', tr.params);
5162
- if (tr.body !== undefined)
5163
- fix('body', tr.body);
5164
- if (tr.timeout !== undefined)
5165
- fix('timeout', tr.timeout);
5166
- // Always set, falling back to the definition's own text: the resource
5167
- // file hands this to the MODEL as the tool's description, and the node
5168
- // must carry the same one (#935).
5169
- extraInputs.description = tr.description ?? String(tdef.description ?? '');
5170
- }
5171
- else {
5172
- const meta = ownEntry(PROCESS_TOOL_META, tr.kind);
5173
- if (!meta)
5174
- throw new Error(`inline agent "${step.name}": unknown tool kind "${tr.kind}".`);
5175
- tdef = processToolDef(tr.kind, tr);
5176
- label = tr.name;
5177
- // The definition's `model.context` names `<bindings.name>` /
5178
- // `<bindings.folderPath>` — the same pair every published-resource
5179
- // STEP references — so the entries must exist at the top level
5180
- // (v1's current authoring doc says so for process tools; a def
5181
- // whose bindings resolve to nothing is a dangling reference).
5182
- const { entries } = processResourceBindings({ name: tr.name, folderPath: tr.folderPath, resourceSubType: meta.subType }, resourceBindings);
5183
- for (const b of entries)
5184
- resourceBindings.set(b.id, b);
5185
- // The NODE stays minimal — `source` only. Both witnesses agree: the
5186
- // deployed corpus carries `inputs: {}` on every per-instance tool, and
5187
- // v1's current authoring doc says "inputs.source only, no instance
5188
- // model". The contract (inputSchema/outputSchema) lives on the
5189
- // DEFINITION's inputDefaults, where the registry's tenant-synthesized
5190
- // copy puts it — emitting it on the node too would set inputs the
5191
- // def's own schema does not declare (our own output would be flagged).
5192
- }
5193
- const tid = resourceNodeId(scope, tr.nodeId, `${step.name}Tool`, `inline agent "${step.name}": ${tr.kind} tool "${label}"`);
5194
- const tnode = makeNode(tdef.nodeType, tid, label, { parentId, def: tdef });
5195
- tnode.display = { ...tnode.display, shape: 'circle' };
5196
- // `uip agent refresh` reads the tool's description off the node's display
5197
- // first, so the node carries the one the resource.json does.
5198
- if (connectorTool)
5199
- tnode.display.description = connectorTool.description;
5200
- // The resource model lives on the DEFINITION only (CX's rule for
5201
- // context, and what the deployed corpus carries for tools).
5202
- delete tnode.model;
5203
- const toolResourceId = tr.source ?? derivedToolSource(built.id, step.name, tr);
5204
- tnode.inputs = {
5205
- source: toolResourceId,
5206
- ...extraInputs,
5207
- };
5208
- scope.nodes.push(tnode);
5209
- addEdge(scope, { node: id, port: 'tool' }, tid, 'input');
5210
- // The other half, for the kinds whose file is DERIVABLE — see
5211
- // `toolResourceJson` for which are not, and why each is left to the
5212
- // designer rather than guessed.
5213
- const toolResource = connectorTool
5214
- ? connectorToolResourceJson(connectorTool, toolResourceId)
5215
- : tr.kind === 'builtin'
5216
- ? builtinToolResourceJson(tr.tool, label, tnode.inputs, { id: toolResourceId })
5217
- : tr.kind === 'httpRequest'
5218
- ? httpRequestToolResourceJson(label, tnode.inputs, { id: toolResourceId, canvasNodeId: tid })
5219
- : toolResourceJson(tr, { id: toolResourceId });
5220
- if (toolResource !== undefined) {
5221
- addSidecar({
5222
- path: `${source}/resources/${toolResourceId}/resource.json`,
5223
- json: toolResource,
5224
- }, tr.source !== undefined);
5225
- }
5226
- }
5227
- // ESCALATION — the agent's TOP handle; an Action Center app task a human
5228
- // completes. The node's inputs are the platform's `inputDefaults` shape
5229
- // with the author's values — the exact shape the deployed corpus carries.
5230
- const escalations = s.escalation === undefined ? []
5231
- : Array.isArray(s.escalation) ? s.escalation : [s.escalation];
5232
- for (const esc of escalations) {
5233
- if (esc.variant === 'quick-form') {
5234
- // The INLINE-form escalation: the same schema rows a human task's
5235
- // quick form carries, on the bundled `.quick-form` definition (the
5236
- // tenant registry does not serve the family).
5237
- const qdef = jsonClone(DEFS['uipath.agent.resource.escalation.quick-form@1.2']);
5238
- const qid = resourceNodeId(scope, esc.nodeId, `${step.name}Escalation`, `inline agent "${step.name}": escalation "${esc.name}"`);
5239
- const qnode = makeNode(qdef.nodeType, qid, esc.name, { parentId, def: qdef });
5240
- qnode.display = { ...qnode.display, shape: 'circle' };
5241
- delete qnode.model;
5242
- const rows = (esc.fields ?? []).map((f) => {
5243
- const required = f.direction === 'input' ? undefined : f.required;
5244
- return {
5245
- id: f.id,
5246
- label: f.label ?? f.id,
5247
- type: wireFormFieldType(f.type),
5248
- direction: f.direction,
5249
- ...(typeof required === 'boolean' ? { required } : {}),
5250
- // The field's settings (`controlType`, a picker's `options`,
5251
- // `defaultValue`, `colSpan`, …), as the human task's rows carry
5252
- // them (#1235, #1250).
5253
- ...formFieldSettingsWire(f),
5254
- };
5255
- });
5256
- const qsource = esc.source ?? derivedEscalationSource(built.id, step.name, esc.name);
5257
- qnode.inputs = {
5258
- source: qsource,
5259
- name: esc.name,
5260
- description: esc.description ?? '',
5261
- type: 'quick-form',
5262
- schema: {
5263
- // The runtime refuses a quick form without one (`AGENT_STARTUP.
5264
- // INVALID_TOOL_CONFIG`: "has a quick form schema without a
5265
- // schemaId", measured 2026-10-05) — the same stable id a human
5266
- // task's form carries.
5267
- schemaId: schemaId(built.id, qid),
5268
- fields: rows,
5269
- outcomes: (esc.outcomes ?? []).map((o, i) => ({
5270
- id: outcomeId(o, i), name: o, type: 'string', isPrimary: i === 0, action: 'Continue',
5271
- })),
5272
- },
5273
- recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
5274
- outcomeMapping: esc.outcomes && esc.outcomes.length
5275
- ? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
5276
- : null,
5277
- _additionalProps: {
5278
- taskTitle: esc.taskTitle ?? '',
5279
- priority: esc.priority ?? 'medium',
5280
- labels: esc.labels ?? [],
5281
- },
5282
- _notifications: false,
5283
- };
5284
- scope.nodes.push(qnode);
5285
- addEdge(scope, { node: id, port: 'escalation' }, qid, 'input');
5286
- // The other half — without it the escalation is inert: see
5287
- // `escalationResourceJson`.
5288
- addSidecar({
5289
- path: `${source}/resources/${qsource}/resource.json`,
5290
- json: escalationResourceJson(esc, {
5291
- id: qsource,
5292
- channelId: fnv1aX4Uuid(`${built.id}/${step.name}/escalation/${esc.name}/channel`),
5293
- quickFormSchema: qnode.inputs.schema,
5294
- }),
5295
- }, esc.source !== undefined);
5296
- continue;
5297
- }
5298
- const app = esc.app;
5299
- const edef = escalationDef();
5300
- const eid = resourceNodeId(scope, esc.nodeId, `${step.name}Escalation`, `inline agent "${step.name}": escalation "${esc.name}"`);
5301
- const enode = makeNode(edef.nodeType, eid, esc.name, { parentId, def: edef });
5302
- enode.display = { ...enode.display, shape: 'circle' };
5303
- delete enode.model;
5304
- const esource = esc.source ?? derivedEscalationSource(built.id, step.name, esc.name);
5305
- enode.inputs = {
5306
- source: esource,
5307
- name: esc.name,
5308
- description: esc.description ?? '',
5309
- type: 'app-task',
5310
- app: {
5311
- key: app.key,
5312
- name: app.name,
5313
- appName: app.name,
5314
- displayName: app.name,
5315
- folderPath: escalationAppFolder(app),
5316
- inputSchema: fieldSchema(app.inputs),
5317
- outputSchema: fieldSchema(app.outputs),
5318
- },
5319
- // `{type: 3, value: <email>}` is the deployed corpus's recipient
5320
- // shape (type 3 = email address).
5321
- recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
5322
- // Every deployed outcome maps to 'continue' — the agent resumes with
5323
- // the reviewer's answer whichever outcome they pick.
5324
- outcomeMapping: esc.outcomes && esc.outcomes.length
5325
- ? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
5326
- : null,
5327
- _additionalProps: {
5328
- taskTitle: esc.taskTitle ?? '',
5329
- priority: esc.priority ?? 'medium',
5330
- labels: esc.labels ?? [],
5331
- },
5332
- _notifications: false,
5333
- _appInputs: null,
5334
- };
5335
- scope.nodes.push(enode);
5336
- addEdge(scope, { node: id, port: 'escalation' }, eid, 'input');
5337
- addSidecar({
5338
- path: `${source}/resources/${esource}/resource.json`,
5339
- json: escalationResourceJson(esc, {
5340
- id: esource,
5341
- channelId: fnv1aX4Uuid(`${built.id}/${step.name}/escalation/${esc.name}/channel`),
5342
- }),
5343
- }, esc.source !== undefined);
5344
- }
5345
- // MEMORY — one episodic-memory resource on the agent's `memory` handle
5346
- // (the 1.4 definition selected above). Same artifact shape as context.
5347
- if (s.memory !== undefined) {
5348
- const m = s.memory;
5349
- const mdef = memoryResourceDef(m);
5350
- const mid = uid(scope, `${step.name}Memory`);
5351
- const mnode = makeNode(mdef.nodeType, mid, m.name, { parentId, def: mdef });
5352
- mnode.display = { ...mnode.display, shape: 'circle' };
5353
- delete mnode.model;
5354
- mnode.inputs = {
5355
- source: fnv1aX4Uuid(`${built.id}/${step.name}/memory/${m.id}`),
5356
- name: m.name,
5357
- description: m.description ?? '',
5358
- dynamicFewShotLearning: m.dynamicFewShotLearning ?? true,
5359
- semanticSimilarity: m.semanticSimilarity ?? 0,
5360
- kValue: m.kValue ?? 3,
5361
- searchMode: m.searchMode ?? 'hybrid',
5362
- };
5363
- scope.nodes.push(mnode);
5364
- addEdge(scope, { node: id, port: 'memory' }, mid, 'input');
5365
- // No `features/<id>/feature.json` until `check` stops refusing memory
5366
- // (AGENT_MEMORY_NOT_AUTHORABLE); its shape maps cleanly off `AgentMemoryRef`
5367
- // but cannot be exercised while the handle does not exist.
5368
- }
5369
- for (const t of tails)
5370
- addEdge(scope, t, id);
5371
- return [{ node: id, port: successPort(def.nodeType) }];
5372
- }
5373
- if (spec.kind === 'conversationalAgent') {
5374
- // The text CONVERSATIONAL agent: the inline agent's cluster shape (a
5375
- // minted `source` plus an `agent.json` sidecar) with the family's own
5376
- // turn wiring. The per-turn bindings live in `conversationalAgentSettings`
5377
- // — the platform's serializer reads that sub-object and turns it into the
5378
- // engine's `fpsProperties`, so the SDK writes the designer's shape and
5379
- // lets the platform do that translation.
5380
- const s = spec.inputs;
5381
- const id = stepUid(scope, step.name);
5382
- const source = agentFolder(s.source, step.name);
5383
- const node = makeNode(NODE_TYPE.conversationalAgent, id, step.name, { parentId, requestedVersion: step.options?.version });
5384
- delete node.model;
5385
- const st = s.settings ?? {};
5386
- // The FOUR individual fields are the runtime's source of truth — the
5387
- // platform's own editor says so ("they always reflect the current
5388
- // configuration regardless of mode; `mode` and `context` exist only to
5389
- // restore the editor UI"), and its validator refuses a node without
5390
- // `conversationId` even when a context binding is present. So `'simple'`
5391
- // mode DERIVES them from the context exactly as `deriveFieldsFromContext`
5392
- // does, appending each field's own context path (note `latestExchangeId`,
5393
- // which is not the field's own name).
5394
- const rendered = (v) => renderValue(toExpr(v), scope.rename);
5395
- const ctxExpr = st.context === undefined ? undefined : toExpr(st.context);
5396
- const derive = (contextPath) => {
5397
- if (ctxExpr === undefined || ctxExpr.literal)
5398
- return undefined;
5399
- return renderValue(new Expr(`${ctxExpr.js}.${contextPath}`), scope.rename);
5400
- };
5401
- const field = (authored, contextPath) => authored !== undefined ? rendered(authored) : derive(contextPath);
5402
- const settings = {
5403
- mode: st.mode ?? 'simple',
5404
- ...(st.context !== undefined ? { context: rendered(st.context) } : {}),
5405
- };
5406
- for (const [key, contextPath] of [
5407
- ['conversationId', 'conversationId'],
5408
- ['exchangeId', 'latestExchangeId'],
5409
- ['messages', 'messages'],
5410
- ['userSettings', 'userSettings'],
5411
- ]) {
5412
- const v = field(st[key], contextPath);
5413
- if (v !== undefined)
5414
- settings[key] = v;
5415
- }
5416
- // A SHELL node, like the inline agent's (#927): exactly the keys
5417
- // `toAgentShellInputs` keeps. The system prompt, the model and the run
5418
- // settings live only in the sidecar (`metadata.isConversational` there is
5419
- // the family flag). `endExchange` is written nowhere: the platform retired
5420
- // it (the node's v1.7 migration drops it, and the converter always sends
5421
- // `conversationalService.endExchange = ''`).
5422
- node.inputs = {
5423
- source,
5424
- conversationalAgentSettings: settings,
5425
- };
5426
- scope.nodes.push(node);
5427
- addSidecar({
5428
- path: `${source}/agent.json`,
5429
- json: conversationalAgentJson({
5430
- model: s.model, systemPrompt: s.systemPrompt, temperature: s.temperature,
5431
- maxTokenPerResponse: s.maxTokenPerResponse, maxIterations: s.maxIterations,
5432
- guardrails: s.guardrails, source, name: step.name,
5433
- }),
5434
- });
5435
- for (const t of tails)
5436
- addEdge(scope, t, id);
5437
- return [{ node: id, port: successPort(NODE_TYPE.conversationalAgent) }];
5438
- }
5439
- if (spec.kind === 'createOutgoingCall' || spec.kind === 'endCall') {
5440
- // The two VOICE service tasks. Both keep the service model on the
5441
- // DEFINITION (the family convention) and write the author's fields over
5442
- // the definition's own empty defaults.
5443
- const type = spec.kind === 'createOutgoingCall' ? NODE_TYPE.createOutgoingCall : NODE_TYPE.endCall;
5444
- const id = stepUid(scope, step.name);
5445
- const node = makeNode(type, id, step.name, { parentId, requestedVersion: step.options?.version });
5446
- delete node.model;
5447
- const defaults = jsonClone(defByType.get(type)?.inputDefaults ?? {});
5448
- node.inputs = spec.kind === 'createOutgoingCall'
5449
- ? {
5450
- ...defaults,
5451
- from: renderValue(toExpr(spec.inputs.from), scope.rename),
5452
- to: typeof spec.inputs.to === 'string'
5453
- ? { type: 'literal', expression: spec.inputs.to, fieldType: 'string' }
5454
- : renderValue(toExpr(spec.inputs.to), scope.rename),
5455
- }
5456
- : {
5457
- ...defaults,
5458
- // The whole call-context OBJECT, not a scalar — bound from a
5459
- // trigger or a create-call step.
5460
- callContext: renderValue(toExpr(spec.inputs.callContext), scope.rename),
5461
- };
5462
- scope.nodes.push(node);
5463
- for (const t of tails)
5464
- addEdge(scope, t, id);
5465
- return [{ node: id, port: successPort(type) }];
5466
- }
5467
- if (spec.kind === 'voiceAgent') {
5468
- // A VOICE agent: the inline agent's cluster shape (a `source` uuid plus an
5469
- // `agent.json` sidecar the platform resolves at `<source>/agent.json`),
5470
- // with the voice family's own inputs. There is no prompt templating and
5471
- // no declared `returns` — the turn comes from the live call, and the
5472
- // definition declares what comes back.
5473
- const s = spec.inputs;
5474
- const id = stepUid(scope, step.name);
5475
- const source = agentFolder(s.source, step.name);
5476
- const node = makeNode(NODE_TYPE.voiceAgent, id, step.name, { parentId, requestedVersion: step.options?.version });
5477
- // Same rule as the inline agent's: the resource model lives on the
5478
- // definition (its `model.source: true` is what makes `source` an input).
5479
- delete node.model;
5480
- const nodeInputs = s.inputs ?? {};
5481
- const plan = agentInputPlan(nodeInputs, scope.rename);
5482
- // A SHELL node (#927): the system prompt and the `voice` settings live only
5483
- // in the sidecar's `messages` and `settings.voice`. On a voice node either
5484
- // one makes Studio Web treat the `.flow` as authoritative and skip the
5485
- // sidecar (`hasEmbeddedAgentContent`). `uip maestro flow debug` builds the
5486
- // voice agent from the sidecar (`packageInlineAgents`).
5487
- // The node keeps exactly the keys `toAgentShellInputs` keeps;
5488
- // `maxIterations` and `metadata.isConversational` are in the sidecar.
5489
- node.inputs = {
5490
- source,
5491
- callContext: renderValue(toExpr(s.callContext), scope.rename),
5492
- ...(plan.refs.length === 0 ? {} : {
5493
- agentInputVariables: plan.refs.map((r) => ({
5494
- id: r.flatName,
5495
- type: 'string',
5496
- binding: r.binding,
5497
- })),
5498
- }),
5499
- };
5500
- scope.nodes.push(node);
5501
- // The sidecar half, in the voice dialect: `settings.voice` carries the
5502
- // audio configuration (the storage layer's own StoredVoiceSettings
5503
- // shape), and the messages carry the system prompt alone.
5504
- addSidecar({
5505
- path: `${source}/agent.json`,
5506
- json: voiceAgentJson({
5507
- systemPrompt: s.systemPrompt,
5508
- plan,
5509
- voice: s.voice,
5510
- maxIterations: s.maxIterations,
5511
- source,
5512
- name: step.name,
5513
- }),
5514
- });
5515
- if (plan.refs.length > 0) {
5516
- const agentJson = sidecars[sidecars.length - 1].json;
5517
- scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
5518
- }
5519
- for (const t of tails)
5520
- addEdge(scope, t, id);
5521
- return [{ node: id, port: successPort(NODE_TYPE.voiceAgent) }];
5522
- }
5523
2807
  if (spec.kind === 'ixpExtract') {
5524
2808
  const s = spec.inputs;
5525
2809
  const def = perCallDefinition(spec); // ixpExtractDef: throws on an unparseable project