@uipath/maestro-builder-sdk 6.41.2 → 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 (73) 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/cli/commands/registry.js +15 -0
  6. package/dist/compile-cli.js +2 -1
  7. package/dist/core/actions.d.ts +1 -713
  8. package/dist/core/actions.js +0 -193
  9. package/dist/core/agent-sidecar.d.ts +186 -0
  10. package/dist/core/agent-sidecar.js +409 -0
  11. package/dist/core/agent-tool-resources.d.ts +62 -0
  12. package/dist/core/agent-tool-resources.js +81 -0
  13. package/dist/core/cli-spelling.d.ts +9 -0
  14. package/dist/core/cli-spelling.js +9 -0
  15. package/dist/{config.js → core/connector-configuration.js} +2 -2
  16. package/dist/core/library-cache.d.ts +0 -9
  17. package/dist/core/library-cache.js +0 -9
  18. package/dist/core/library.d.ts +47 -0
  19. package/dist/core/library.js +135 -34
  20. package/dist/core/list-envelope.d.ts +36 -1
  21. package/dist/core/list-envelope.js +24 -19
  22. package/dist/core/lookup-store.d.ts +4 -3
  23. package/dist/core/lookup-store.js +46 -38
  24. package/dist/core/node-id-placeholder.d.ts +8 -0
  25. package/dist/core/node-id-placeholder.js +10 -0
  26. package/dist/core/read-text.d.ts +26 -0
  27. package/dist/core/read-text.js +43 -0
  28. package/dist/core/ts-literal.d.ts +6 -0
  29. package/dist/core/ts-literal.js +15 -0
  30. package/dist/core/typed-raw-node.js +0 -5
  31. package/dist/decompile.js +34 -822
  32. package/dist/flow-node-registry.js +2 -1
  33. package/dist/flow-node-type.d.ts +130 -6
  34. package/dist/flow-sdk.d.ts +1 -0
  35. package/dist/flow-sdk.js +1 -0
  36. package/dist/nodes/agent/flow/check.d.ts +88 -0
  37. package/dist/nodes/agent/flow/check.js +873 -0
  38. package/dist/nodes/agent/flow/factories.d.ts +134 -0
  39. package/dist/nodes/agent/flow/factories.js +143 -0
  40. package/dist/nodes/agent/flow/family.d.ts +3 -0
  41. package/dist/nodes/agent/flow/family.js +8 -0
  42. package/dist/nodes/agent/flow/index.d.ts +3 -0
  43. package/dist/nodes/agent/flow/index.js +3 -0
  44. package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
  45. package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
  46. package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
  47. package/dist/nodes/agent/flow/inline-agent.js +526 -0
  48. package/dist/nodes/agent/flow/node-type.d.ts +20 -0
  49. package/dist/nodes/agent/flow/node-type.js +422 -0
  50. package/dist/nodes/agent/flow/resources.d.ts +502 -0
  51. package/dist/nodes/agent/flow/resources.js +1409 -0
  52. package/dist/nodes/agent/flow/spec.d.ts +448 -0
  53. package/dist/nodes/agent/flow/spec.js +1 -0
  54. package/dist/nodes/process/flow/check.d.ts +40 -2
  55. package/dist/nodes/process/flow/check.js +156 -0
  56. package/dist/nodes/process/flow/factories.d.ts +58 -3
  57. package/dist/nodes/process/flow/factories.js +59 -2
  58. package/dist/nodes/process/flow/node-type.d.ts +4 -1
  59. package/dist/nodes/process/flow/node-type.js +181 -10
  60. package/dist/nodes/process/flow/spec.d.ts +112 -5
  61. package/dist/registry/describe-cli.d.ts +10 -0
  62. package/dist/registry/describe-cli.js +41 -0
  63. package/dist/registry/library-reader.d.ts +12 -2
  64. package/dist/registry/library-reader.js +17 -16
  65. package/dist/registry/lookup-specs.d.ts +17 -1
  66. package/dist/registry/lookup-specs.js +3 -7
  67. package/dist/registry/path-cli.js +2 -1
  68. package/dist/registry/search-cli.js +2 -1
  69. package/dist/serialize.d.ts +9 -54
  70. package/dist/serialize.js +47 -2763
  71. package/package.json +2 -2
  72. /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
  73. /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
package/dist/decompile.js CHANGED
@@ -35,10 +35,7 @@ import { DEFAULT_TRIGGER_ID, Expr } from './core/expr.js';
35
35
  import { FLOW_NODE_TYPES } from './flow-node-registry.js';
36
36
  import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
37
37
  import { commentText } from './core/comment-text.js';
38
- import { tsLiteral, tsNumber, tsText } from './core/ts-literal.js';
39
- import { ownEntry } from './core/own-entry.js';
40
- import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexSlug, parseContextIndexType, } from './core/context-index.js';
41
- import { derivedContextSource, derivedEscalationSource, derivedResourceNodeId, derivedToolSource, } from './core/agent-resource-identity.js';
38
+ import { tsKey as key, tsLiteral, tsNumber, tsObject as objLit, tsOptions as opts, tsText } from './core/ts-literal.js';
42
39
  import { wireExprBody } from './core/wire-expr.js';
43
40
  import { hasExits } from './core/step-ports.js';
44
41
  import { parseIxpProjectType } from './core/actions.js';
@@ -51,7 +48,6 @@ import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
51
48
  import { contributionPort, forwardFanOut } from './core/definition-handles.js';
52
49
  import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
53
50
  import { eventFilterFromTreeLeaf } from './core/event-filters.js';
54
- import { formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, liftFormFieldSettings, sourceFormFieldType, } from './core/form-field-type.js';
55
51
  /**
56
52
  * The trigger id of the flow currently being decompiled, so a reference through a
57
53
  * RENAMED trigger is still recognized as `input('x')` rather than falling back to
@@ -263,12 +259,7 @@ const T = {
263
259
  triggerForm: 'core.trigger.form',
264
260
  triggerConversation: 'core.trigger.conversation',
265
261
  hitl: 'uipath.human-in-the-loop',
266
- inlineAgent: 'uipath.agent.autonomous',
267
- conversationalAgent: 'uipath.agent.conversational',
268
- voiceAgent: 'uipath.agent.voice',
269
262
  triggerVoice: 'core.trigger.voice',
270
- createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
271
- endCall: 'uipath.conversational.voice.end-call',
272
263
  decision: 'core.logic.decision',
273
264
  switch: 'core.logic.switch',
274
265
  merge: 'core.logic.merge',
@@ -287,28 +278,6 @@ function isTrigger(type) {
287
278
  function boolLit(v) {
288
279
  return v ? 'true' : 'false';
289
280
  }
290
- /** An object key: a bare identifier when legal, otherwise a quoted string. */
291
- function key(k) {
292
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : tsLiteral(k);
293
- }
294
- /** Render an options object from present entries; '' when nothing is present. */
295
- function opts(entries) {
296
- const parts = entries.filter(([, v]) => v !== undefined).map(([k, v]) => `${key(k)}: ${v}`);
297
- return parts.length ? `{ ${parts.join(', ')} }` : '';
298
- }
299
- /**
300
- * Plain JSON data as source, in the `{ k: v }` / `[a, b]` spelling the
301
- * printers use (a form field's `options`, `rules`, `defaultValue`).
302
- */
303
- function dataSource(v) {
304
- if (Array.isArray(v))
305
- return `[${v.map(dataSource).join(', ')}]`;
306
- if (v !== null && typeof v === 'object') {
307
- const parts = Object.entries(v).filter(([, x]) => x !== undefined).map(([k, x]) => `${key(k)}: ${dataSource(x)}`);
308
- return parts.length ? `{ ${parts.join(', ')} }` : '{}';
309
- }
310
- return tsLiteral(v);
311
- }
312
281
  /** A collector for indented source lines. */
313
282
  class Emit {
314
283
  indentUnit;
@@ -445,12 +414,6 @@ function emitVal(v, imp, inputNames) {
445
414
  function emitBareExpr(raw, imp, inputNames) {
446
415
  return emitRef(raw, imp, inputNames);
447
416
  }
448
- /** Render an object literal `{ k: <src>, … }` from pre-rendered value sources; '' when empty. */
449
- function objLit(entries) {
450
- if (entries.length === 0)
451
- return '';
452
- return `{ ${entries.map(([k, v]) => `${key(k)}: ${v}`).join(', ')} }`;
453
- }
454
417
  /** A map of `{name: value}` inputs (headers / query / connector fields) → object-literal source. */
455
418
  function emitValMap(m, imp, inputNames) {
456
419
  if (!m || typeof m !== 'object')
@@ -652,6 +615,26 @@ function liftRegistered(node, impl = registeredImpl(node), graph) {
652
615
  throw new Error(`decompile: ${message}`);
653
616
  warnOnce(message);
654
617
  },
618
+ sidecar: (path) => runReadSidecar?.(path),
619
+ flowId: runFlowId,
620
+ definitionOf: (n) => runDefs.get(`${n.type}@${n.typeVersion}`) ?? runDefs.get(n.type),
621
+ attached: (port, limit = Infinity) => {
622
+ const found = [];
623
+ for (const e of graph?.outEdges(node.id) ?? []) {
624
+ if (e.sourcePort !== port || found.length >= limit)
625
+ continue;
626
+ const n = graph.byId.get(e.targetNodeId);
627
+ if (n === undefined)
628
+ continue;
629
+ graph.accounted.add(e);
630
+ found.push(n);
631
+ }
632
+ return found;
633
+ },
634
+ attachedIds: (port) => (graph?.outEdges(node.id) ?? [])
635
+ .filter((e) => e.sourcePort === port && graph.byId.has(e.targetNodeId))
636
+ .map((e) => e.targetNodeId),
637
+ absorb: (id, compiledId) => { runAbsorbed.set(id, compiledId); },
655
638
  outPorts: () => (graph?.outEdges(node.id) ?? []).map((e) => e.sourcePort),
656
639
  convergedFanOut: () => graph?.convergedFanOut(node.id) === true,
657
640
  };
@@ -713,35 +696,12 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
713
696
  if (lifted !== undefined)
714
697
  return lifted;
715
698
  switch (node.type) {
716
- case T.createOutgoingCall:
717
- case T.endCall: {
718
- const i = (node.inputs ?? {});
719
- const v = (x) => {
720
- const raw = typeof x === 'string' ? x : String(x?.expression ?? x ?? '');
721
- return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames)
722
- : raw.startsWith('$vars.') ? emitBareExpr(raw, imp, inputNames)
723
- : tsLiteral(raw);
724
- };
725
- if (node.type === T.createOutgoingCall) {
726
- return `${imp.use('createOutgoingCall')}(${opts([['from', v(i.from)], ['to', v(i.to)]])})`;
727
- }
728
- return `${imp.use('endCall')}(${opts([['callContext', v(i.callContext)]])})`;
729
- }
730
- case T.inlineAgent:
731
- case T.conversationalAgent:
732
- case T.voiceAgent:
733
- return emitAgentCluster(node, imp, inputNames, graph);
734
699
  default:
735
700
  // A connector action node: `uipath.connector.<key>.<action>` (but NOT the
736
701
  // event nodes `uipath.connector.event.*` / `.trigger.*`, which are triggers).
737
702
  if (node.type.startsWith('uipath.connector.') && !node.type.startsWith('uipath.connector.event.') && !node.type.startsWith('uipath.connector.trigger.')) {
738
703
  return emitConnector(node, imp, inputNames, o);
739
704
  }
740
- {
741
- const published = emitPublishedResource(node, imp, inputNames);
742
- if (published !== undefined)
743
- return published;
744
- }
745
705
  // A mid-flow connector EVENT wait (`uipath.connector.event.<key>.<event>`
746
706
  // placed as a step rather than as the start node).
747
707
  if (node.type.startsWith('uipath.connector.event.')) {
@@ -837,688 +797,6 @@ function connectorObjectName(configuration) {
837
797
  * Returns `undefined` when the node is not one of them, so the caller can fall
838
798
  * through to the next arm.
839
799
  */
840
- /** The `maxIterations` Studio Web writes into every inline agent's `agent.json` (flow-workbench `getDefaultAgentContent()`). */
841
- const STUDIO_WEB_DEFAULT_MAX_ITERATIONS = 25;
842
- /**
843
- * The agent configuration a SHELL agent node keeps in its sidecar, read from
844
- * `<source>/agent.json` in the node's own input names: the prompts, the model,
845
- * the run settings (`temperature`, `maxTokenPerResponse`, `maxIterations`,
846
- * `mode`), the `guardrails` and (voice) the `voice` settings. Empty when the
847
- * node carries prompts itself (an embedded node, e.g. a `.flow` saved by Studio
848
- * Web with the self-contained flag on), or when no sidecar can be read.
849
- *
850
- * A run setting equal to the default a sidecar writer fills in (`temperature`
851
- * 0, `maxTokens` 16384, `mode` `'standard'`) is not recovered: recompiling
852
- * without it writes the same sidecar, and recovering it would add an argument
853
- * the author never wrote to every agent. `mode: 'standard'` is the exception on
854
- * a 1.3 node, where naming it is what selected that definition. The same goes
855
- * for the inline agent's `maxIterations` 25: the SDK omits it when unset, but
856
- * Studio Web always writes it (its `getDefaultAgentContent()` default), so
857
- * every Studio Web-saved agent would otherwise decompile with `maxIterations: 25`.
858
- */
859
- function sidecarAgentInputs(node) {
860
- const inputs = (node.inputs ?? {});
861
- if (typeof inputs.systemPrompt === 'string' || typeof inputs.userPrompt === 'string')
862
- return {};
863
- const source = unwrapSource(inputs.source);
864
- if (!source)
865
- return {};
866
- const path = `${source}/agent.json`;
867
- const raw = runReadSidecar?.(path);
868
- if (raw === undefined) {
869
- warnOnce(`agent step "${node.id}" carries no prompts and ${path} was not found beside the .flow, `
870
- + 'so its prompts, model and run settings decompile empty. Keep <source>/agent.json beside the .flow '
871
- + '(download the whole project, not just the .flow).');
872
- return {};
873
- }
874
- let agent;
875
- try {
876
- agent = JSON.parse(raw);
877
- }
878
- catch {
879
- warnOnce(`agent step "${node.id}": ${path} is not valid JSON, so its prompts, model and run settings decompile empty.`);
880
- return {};
881
- }
882
- const messages = (Array.isArray(agent?.messages) ? agent.messages : []);
883
- const content = (role) => {
884
- const c = messages.find((m) => m.role === role)?.content;
885
- return typeof c === 'string' ? c : undefined;
886
- };
887
- const settings = (agent?.settings ?? {});
888
- const system = content('system');
889
- const user = content('user');
890
- const isVoice = node.type === T.voiceAgent;
891
- const numberUnless = (v, dflt) => typeof v === 'number' && v !== dflt ? v : undefined;
892
- // The voice agent's temperature and token cap live in `settings.voice`, not here.
893
- const temperature = isVoice ? undefined : numberUnless(settings.temperature, 0);
894
- const maxTokenPerResponse = isVoice ? undefined : numberUnless(settings.maxTokens, 16384);
895
- // Studio Web's default for the inline agent; the other families keep any value.
896
- const maxIterations = node.type === T.inlineAgent
897
- ? numberUnless(settings.maxIterations, STUDIO_WEB_DEFAULT_MAX_ITERATIONS)
898
- : typeof settings.maxIterations === 'number' ? settings.maxIterations : undefined;
899
- // `mode` is a harness choice on the inline agent only; the conversational and
900
- // voice sidecars always carry their family's fixed value. `'standard'` is the
901
- // sidecar's default, so only another value is the author's. (Naming it no
902
- // longer selects a definition version — the node's `{ version }` carries that,
903
- // flow-builder-sdk#956.)
904
- const mode = node.type === T.inlineAgent && typeof settings.mode === 'string'
905
- && settings.mode !== 'standard'
906
- ? settings.mode : undefined;
907
- return {
908
- ...(system === undefined ? {} : { systemPrompt: system }),
909
- ...(user === undefined ? {} : { userPrompt: user }),
910
- ...(typeof settings.model === 'string' ? { model: settings.model } : {}),
911
- ...(temperature === undefined ? {} : { temperature }),
912
- ...(maxTokenPerResponse === undefined ? {} : { maxTokenPerResponse }),
913
- ...(maxIterations === undefined ? {} : { maxIterations }),
914
- ...(mode === undefined ? {} : { mode }),
915
- ...(Array.isArray(agent?.guardrails) && agent.guardrails.length > 0 ? { guardrails: agent.guardrails } : {}),
916
- ...(isVoice && settings.voice && typeof settings.voice === 'object' ? { voice: settings.voice } : {}),
917
- };
918
- }
919
- /**
920
- * Where a context-index resource node keeps its identity, read in the order
921
- * that holds for every writer.
922
- *
923
- * SDK compile writes `indexId` / `indexName` / `folderPath` onto the node. The
924
- * canvas and the CLI do not: their node carries only `inputs.source`, and the
925
- * identity lives in two other places.
926
- *
927
- * - The node TYPE, `…context.index.<slug>.<uuid>`: the uuid is the index id
928
- * (the `ContextIndexRef.id` JSDoc says to read it there).
929
- * - The resource sidecar `<agent source>/resources/<inputs.source>/resource.json`,
930
- * whose `name` is the index's real name (the one the slug is made from) and
931
- * whose `folderPath` is its folder. `display.label` is only what the canvas
932
- * shows, and a canvas rename changes it.
933
- *
934
- * So the id comes from the type (then `inputs.indexId`). The name is the first
935
- * of `inputs.indexName`, sidecar `name`, sidecar `indexName`, label that slugs to
936
- * the type's slug, else the first one present in that order. Compile derives
937
- * the type from `slug(name)` + id, so when the chosen name does not slug back to
938
- * the type's slug the recompiled node gets a different type, and that is warned.
939
- */
940
- function contextIndexIdentity(agent, c, rPlain) {
941
- const fromType = parseContextIndexType(c.type);
942
- const sidecar = contextResourceSidecar(agent, c);
943
- const sidecarText = (k) => typeof sidecar?.[k] === 'string' && sidecar[k] !== '' ? sidecar[k] : undefined;
944
- const label = typeof c.display?.label === 'string' && c.display.label !== '' ? c.display.label : undefined;
945
- // The canvas can set both sidecar `name` and `indexName`, and they can differ.
946
- // The one that slugs to the type's slug is the one the type was built from, so
947
- // it wins; the fixed order is the fallback when none matches.
948
- const candidates = [rPlain(c, 'indexName'), sidecarText('name'), sidecarText('indexName'), label]
949
- .filter((n) => n !== undefined);
950
- const name = (fromType?.slug ? candidates.find((n) => contextIndexSlug(n) === fromType.slug) : undefined)
951
- ?? candidates[0] ?? '';
952
- const id = (fromType?.id || undefined) ?? rPlain(c, 'indexId') ?? '';
953
- if (fromType !== undefined && fromType.slug !== '' && contextIndexSlug(name) !== fromType.slug) {
954
- warnOnce(`context index "${c.id}" on agent "${agent.id}": its node type names the index "${fromType.slug}", `
955
- + `but the name decompile recovered, ${JSON.stringify(name)}, slugs to "${contextIndexSlug(name)}", so compile `
956
- + `will write a different node type. Set \`name\` to the index's real name`
957
- + (sidecar === undefined ? ', or keep the resources/ folder beside the .flow so decompile can read it.' : '.'));
958
- }
959
- return { name, id, folderPath: rPlain(c, 'folderPath') ?? sidecarText('folderPath'), sidecar };
960
- }
961
- /** The retrieval modes `ContextIndexRef.retrievalMode` can hold. */
962
- const CONTEXT_RETRIEVAL_MODES = new Set(['semantic', 'structured', 'deeprag', 'batchtransform']);
963
- /** The `settings` keys of a context `resource.json` that {@link contextIndexSettings} reads or checks. */
964
- const CONTEXT_SETTINGS_KEYS = new Set([
965
- 'retrievalMode', 'query', 'folderPathPrefix', 'threshold', 'resultCount', 'fileExtension',
966
- 'citationMode', 'webSearchGrounding', 'outputColumns',
967
- ]);
968
- /**
969
- * A context index's retrieval settings, as the `context: [...]` entry's option
970
- * rows (`retrievalMode`, `query`, `threshold`, `resultCount`, `fileExtension`),
971
- * each left out when it is what compile writes anyway.
972
- *
973
- * The inverse of `contextResourceJson` (serialize.ts). Read from the sidecar's
974
- * `settings` first: a node the canvas or the CLI placed keeps its inputs minimal
975
- * (`inputs.source`), so `resource.json` is the only copy, and it is what the
976
- * runtime reads. The node's own inputs (the SDK writes both) answer when there
977
- * is no sidecar. Before #1183 neither was read, so a no-edit round trip reset
978
- * every setting to the SDK default without a word.
979
- *
980
- * The query is variant-tagged in the sidecar and mode-tagged on the node:
981
- *
982
- * - `dynamic` / `prompt` → a plain string (the guidance the model reads);
983
- * - `static` / `text-builder` whose text is a `=js:` expression → that
984
- * expression, which compile writes back as `text-builder` / `static`;
985
- * - `static` with `''` is the canvas's spelling of "no query", as the SDK's
986
- * `dynamic` with `''` is: left out.
987
- *
988
- * A setting the SDK cannot express (a `static` query of plain text, an
989
- * `argument` query, deeprag citations off, batchtransform web grounding off or
990
- * output columns, a folder-path prefix, an unknown mode or key) is reported:
991
- * a warning, or an error under `--strict`. Compile would write the SDK's
992
- * value instead, so it is never dropped silently.
993
- */
994
- function contextIndexSettings(agent, c, sidecar) {
995
- const settings = sidecar?.settings && typeof sidecar.settings === 'object' && !Array.isArray(sidecar.settings)
996
- ? sidecar.settings : undefined;
997
- const ni = (c.inputs ?? {});
998
- const where = `context index "${c.id}" on agent "${agent.id}"`;
999
- const lossy = (what) => {
1000
- const message = `${where}: ${what}, which \`context: [...]\` cannot express, so compile writes the SDK's `
1001
- + `value instead. Edit that setting in the canvas after merging, or keep the original resource.json.`;
1002
- if (runStrict)
1003
- throw new Error(`decompile: ${message}`);
1004
- warnOnce(message);
1005
- };
1006
- const textOf = (v) => (typeof v === 'string' ? v : undefined);
1007
- const numOf = (v) => {
1008
- const raw = unwrapPlain(v);
1009
- return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;
1010
- };
1011
- const valueOf = (v) => (v && typeof v === 'object' ? v.value : undefined);
1012
- // Retrieval mode.
1013
- const rawMode = textOf(settings?.retrievalMode) ?? textOf(unwrapSource(ni.retrievalMode));
1014
- let mode = CONTEXT_INDEX_DEFAULTS.retrievalMode;
1015
- if (rawMode !== undefined && rawMode !== '') {
1016
- if (CONTEXT_RETRIEVAL_MODES.has(rawMode))
1017
- mode = rawMode;
1018
- else
1019
- lossy(`its retrieval mode is ${JSON.stringify(rawMode)}`);
1020
- }
1021
- const sidecarQuery = (() => {
1022
- const q = settings?.query;
1023
- if (!q || typeof q !== 'object')
1024
- return undefined;
1025
- if (q.variant === 'dynamic')
1026
- return { kind: 'prompt', text: textOf(q.description) ?? '' };
1027
- if (q.variant === 'static')
1028
- return { kind: 'text', text: textOf(q.value) ?? '' };
1029
- if (q.variant === 'argument')
1030
- return { kind: 'argument', text: textOf(q.value) ?? '' };
1031
- lossy(`its query has variant ${JSON.stringify(q.variant)}`);
1032
- return undefined;
1033
- })();
1034
- const nodeQuery = (() => {
1035
- const q = ni.query;
1036
- if (typeof q === 'string')
1037
- return q === '' ? undefined : { kind: q.startsWith('=js:') ? 'text' : 'prompt', text: q };
1038
- if (!q || typeof q !== 'object')
1039
- return undefined;
1040
- const t = q;
1041
- if (t.mode === 'text-builder')
1042
- return { kind: 'text', text: textOf(t.textValue) ?? '' };
1043
- if (t.mode === 'variable')
1044
- return { kind: 'argument', text: textOf(t.argumentPath) ?? '' };
1045
- return { kind: 'prompt', text: textOf(t.promptValue) ?? '' };
1046
- })();
1047
- const query = sidecarQuery ?? nodeQuery;
1048
- let querySrc;
1049
- if (query?.kind === 'prompt' && query.text !== '')
1050
- querySrc = tsLiteral(query.text);
1051
- else if (query?.kind === 'text' && query.text.startsWith('=js:'))
1052
- querySrc = tsLiteral(query.text);
1053
- else if (query?.kind === 'text' && query.text !== '')
1054
- lossy(`its query is static text (${JSON.stringify(query.text)})`);
1055
- else if (query?.kind === 'argument')
1056
- lossy(`its query is the agent argument ${JSON.stringify(query.text)}`);
1057
- // Numbers and the file extension.
1058
- const threshold = numOf(settings?.threshold) ?? numOf(ni.threshold);
1059
- const resultCount = numOf(settings?.resultCount) ?? numOf(ni.resultCount);
1060
- const fileExtension = textOf(valueOf(settings?.fileExtension)) ?? textOf(unwrapSource(ni.fileExtension));
1061
- // The node's flat `All` is the platform's node default for every mode; only
1062
- // the resource's per-mode default is what compile would write anyway.
1063
- const defaultExtension = ownEntry(CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, mode) ?? 'All';
1064
- const extensionSrc = fileExtension === undefined || fileExtension === '' || fileExtension === defaultExtension
1065
- || (settings?.fileExtension === undefined && fileExtension === 'All')
1066
- ? undefined : tsLiteral(fileExtension);
1067
- // What compile writes as constants; anything else has no field.
1068
- if (mode === 'deeprag') {
1069
- const citation = textOf(valueOf(settings?.citationMode)) ?? textOf(unwrapSource(ni.citations));
1070
- if (citation !== undefined && !['Inline', 'enabled'].includes(citation))
1071
- lossy(`its citation mode is ${JSON.stringify(citation)}`);
1072
- }
1073
- if (mode === 'batchtransform') {
1074
- const grounding = textOf(valueOf(settings?.webSearchGrounding)) ?? textOf(valueOf(ni.webSearchGrounding));
1075
- if (grounding !== undefined && !['Enabled', 'enabled'].includes(grounding))
1076
- lossy(`its web search grounding is ${JSON.stringify(grounding)}`);
1077
- const columns = settings?.outputColumns ?? ni.outputColumns;
1078
- if (Array.isArray(columns) && columns.length > 0)
1079
- lossy(`it has ${columns.length} output column(s)`);
1080
- }
1081
- // The folder-path prefix: variant-tagged in the sidecar, mode-tagged on the node.
1082
- const field = (v, k) => (v && typeof v === 'object' ? textOf(v[k]) : undefined);
1083
- const prefixText = settings?.folderPathPrefix !== undefined
1084
- ? field(settings.folderPathPrefix, 'value') ?? field(settings.folderPathPrefix, 'description') ?? ''
1085
- : field(ni.folderPathPrefix, 'textValue') ?? field(ni.folderPathPrefix, 'promptValue') ?? '';
1086
- if (prefixText !== '')
1087
- lossy(`its folder-path prefix is ${JSON.stringify(prefixText)}`);
1088
- for (const k of Object.keys(settings ?? {})) {
1089
- if (!CONTEXT_SETTINGS_KEYS.has(k))
1090
- lossy(`its resource.json settings carry ${JSON.stringify(k)}`);
1091
- }
1092
- return [
1093
- ['query', querySrc],
1094
- ['retrievalMode', mode !== CONTEXT_INDEX_DEFAULTS.retrievalMode ? tsLiteral(mode) : undefined],
1095
- ['threshold', threshold !== undefined && threshold !== CONTEXT_INDEX_DEFAULTS.threshold ? tsNumber(threshold) : undefined],
1096
- ['resultCount', resultCount !== undefined && resultCount !== CONTEXT_INDEX_DEFAULTS.resultCount ? tsNumber(resultCount) : undefined],
1097
- ['fileExtension', extensionSrc],
1098
- ];
1099
- }
1100
- /**
1101
- * A context resource's `resource.json` sidecar, parsed; `undefined` when it is
1102
- * not beside the `.flow`, or (with a warning) when it is not a JSON object.
1103
- */
1104
- function contextResourceSidecar(agent, c) {
1105
- const agentSource = unwrapSource(agent.inputs?.source);
1106
- const resourceSource = unwrapSource(c.inputs?.source);
1107
- if (!agentSource || !resourceSource)
1108
- return undefined;
1109
- const path = `${agentSource}/resources/${resourceSource}/resource.json`;
1110
- const raw = runReadSidecar?.(path);
1111
- if (raw === undefined)
1112
- return undefined;
1113
- try {
1114
- const json = JSON.parse(raw);
1115
- if (json && typeof json === 'object' && !Array.isArray(json))
1116
- return json;
1117
- }
1118
- catch {
1119
- // Reported below with the non-object case.
1120
- }
1121
- warnOnce(`context index "${c.id}": ${path} is not valid JSON, so its name and folder decompile from the node.`);
1122
- return undefined;
1123
- }
1124
- /**
1125
- * An AGENT and its attached resources — the inline autonomous agent, the
1126
- * conversational agent, and the voice agent.
1127
- *
1128
- * The cluster is a node plus artifact nodes on its `tool` / `context` /
1129
- * `escalation` / `memory` handles, so this walks those edges and recovers each
1130
- * resource as an argument rather than as a step (see {@link ARTIFACT_PORTS}).
1131
- *
1132
- * **The author's input NAMES are not recoverable, and that is by design.** The
1133
- * platform does not send an agent the names you declared: it keys each argument
1134
- * by the reference PATH, `__`-joined (`$vars.prep.output` → `prep__output`), and
1135
- * only those flat names survive in the emitted file. Recovery therefore uses
1136
- * them for both the `inputs` keys and the `{{input.…}}` placeholders — the same
1137
- * arguments reach the model, spelled the platform's way. (Node ids are
1138
- * preserved exactly; only these logical names change.)
1139
- */
1140
- function emitAgentCluster(node, imp, inputNames, graph) {
1141
- // A key on the node wins over the sidecar's copy of it.
1142
- const i = { ...sidecarAgentInputs(node), ...(node.inputs ?? {}) };
1143
- const plain = (v) => {
1144
- const raw = unwrapSource(v);
1145
- return raw === undefined || raw === '' ? undefined : raw;
1146
- };
1147
- const num = (v) => {
1148
- const raw = unwrapPlain(v);
1149
- return typeof raw === 'number' ? String(raw) : undefined;
1150
- };
1151
- // Prompts. A sidecar prompt already speaks `{{input.<flatName>}}`, so this
1152
- // leaves it alone. An EMBEDDED node's prompt (Studio Web with the
1153
- // self-contained flag on, or an SDK before #927) carries each input as its
1154
- // rendered binding instead; the bindings in `agentInputVariables` are exactly
1155
- // what to put back — longest first, so a shorter binding cannot eat a longer
1156
- // one's prefix.
1157
- const vars = (Array.isArray(i.agentInputVariables) ? i.agentInputVariables : []);
1158
- const refs = vars
1159
- .filter((v) => typeof v.id === 'string' && typeof v.binding === 'string')
1160
- .map((v) => ({ flatName: v.id, binding: v.binding }))
1161
- .sort((a, b) => b.binding.length - a.binding.length);
1162
- const unrender = (text) => {
1163
- let out = text;
1164
- for (const r of refs)
1165
- out = out.split(r.binding).join(`{{input.${r.flatName}}}`);
1166
- return out;
1167
- };
1168
- const promptSrc = (v) => {
1169
- const raw = plain(v);
1170
- return raw === undefined ? undefined : tsLiteral(unrender(raw));
1171
- };
1172
- const inputRows = refs
1173
- // Emit in the file's own order, not the length-sorted one.
1174
- .slice()
1175
- .sort((a, b) => vars.findIndex((v) => v.id === a.flatName) - vars.findIndex((v) => v.id === b.flatName))
1176
- .map((r) => {
1177
- return [r.flatName, emitBareExpr(wireExprBody(r.binding), imp, inputNames)];
1178
- });
1179
- const outVars = (Array.isArray(i.agentOutputVariables) ? i.agentOutputVariables : []);
1180
- const returns = outVars.length
1181
- ? objLit(outVars.map((v) => [String(v.id ?? ''), tsLiteral(String(v.type ?? 'string'))]))
1182
- : undefined;
1183
- // The resources, from the agent's own artifact edges. Each edge read here is
1184
- // carried by the agent spec (edge accounting, #1125); `limit` is for a port
1185
- // the spec carries only one resource on.
1186
- const resource = (port, limit = Infinity) => {
1187
- const found = [];
1188
- for (const e of graph?.outEdges(node.id) ?? []) {
1189
- if (e.sourcePort !== port || found.length >= limit)
1190
- continue;
1191
- const n = graph.byId.get(e.targetNodeId);
1192
- if (n === undefined)
1193
- continue;
1194
- graph.accounted.add(e);
1195
- found.push(n);
1196
- }
1197
- return found;
1198
- };
1199
- // Record what compile will call each folded resource node. Serialize mints
1200
- // `uid(scope, \`${step.name}<Kind>\`)` per resource, in spec order, and the
1201
- // spec order is this edge order: the first is `<agent><Kind>`, the n-th
1202
- // `<agent><Kind><n>` (claimId's dedupe). Only the first memory is recovered,
1203
- // so every further memory maps to `undefined`.
1204
- //
1205
- // When a port's nodes do not ALL carry those ids (the canvas or the CLI named
1206
- // them), every resource on the port is written with its own `nodeId`, and
1207
- // compile keeps the file's ids. All of them, not only the odd one: compile
1208
- // numbers the derived ids in order, so a derived id after an explicit one
1209
- // would come out one lower than the file's.
1210
- // This reads the edges directly, not through `resource()`: it must see EVERY
1211
- // artifact edge, including ones the spec does not carry, and it must not
1212
- // account for any edge.
1213
- const keepIds = new Set();
1214
- for (const [port, kind, emittedCount] of [
1215
- ['context', 'Context', Infinity], ['tool', 'Tool', Infinity],
1216
- ['escalation', 'Escalation', Infinity], ['memory', 'Memory', 1],
1217
- ]) {
1218
- const targets = (graph?.outEdges(node.id) ?? [])
1219
- .filter((e) => e.sourcePort === port && graph.byId.has(e.targetNodeId))
1220
- .map((e) => e.targetNodeId);
1221
- const keep = port !== 'memory'
1222
- && targets.some((id, k) => id !== derivedResourceNodeId(node.id, kind, k));
1223
- targets.forEach((id, k) => {
1224
- if (keep)
1225
- keepIds.add(id);
1226
- runAbsorbed.set(id, keep ? id : k >= emittedCount ? undefined : derivedResourceNodeId(node.id, kind, k));
1227
- });
1228
- }
1229
- /**
1230
- * The `nodeId` / `source` entries for resource node `r`: each only when it
1231
- * differs from what compile derives (`derivedSource`), so a flow compile
1232
- * wrote decompiles without them.
1233
- */
1234
- const placement = (r, derivedSource) => {
1235
- const source = unwrapSource((r.inputs ?? {}).source);
1236
- return [
1237
- ['nodeId', keepIds.has(r.id) ? tsLiteral(r.id) : undefined],
1238
- ['source', source !== undefined && source !== '' && source !== derivedSource ? tsLiteral(source) : undefined],
1239
- ];
1240
- };
1241
- /** A tool's options plus its placement; `seed` is the ref compile derives the tool's directory from. */
1242
- const toolOpts = (t, seed, entries) => opts([...entries, ...placement(t, derivedToolSource(runFlowId, node.id, seed))]);
1243
- const rInputs = (n) => (n.inputs ?? {});
1244
- const rPlain = (n, k) => {
1245
- const raw = unwrapSource(rInputs(n)[k]);
1246
- return raw === undefined || raw === '' ? undefined : raw;
1247
- };
1248
- const toolSrc = resource('tool').map((t) => {
1249
- const ti = rInputs(t);
1250
- const type = t.type;
1251
- if (type === 'uipath.agent.resource.tool.clientside') {
1252
- const schemaTypes = (v) => {
1253
- const props = v?.properties;
1254
- if (!props || Object.keys(props).length === 0)
1255
- return undefined;
1256
- return objLit(Object.entries(props).map(([k, x]) => [k, tsLiteral(String(x?.type ?? 'string'))]));
1257
- };
1258
- return toolOpts(t, { kind: 'clientside', name: rPlain(t, 'name') ?? '' }, [
1259
- ['kind', tsLiteral('clientside')],
1260
- ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1261
- ['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
1262
- ['inputs', schemaTypes(ti.inputSchema)],
1263
- ['returns', schemaTypes(ti.outputSchema)],
1264
- ]);
1265
- }
1266
- if (type === 'uipath.agent.resource.tool.builtin.httprequest') {
1267
- // A field is authored only when it left prompt mode (text-builder).
1268
- const fixed = (k) => {
1269
- const row = ti[k];
1270
- if (!row || row.mode !== 'text-builder')
1271
- return undefined;
1272
- const v = row.textValue;
1273
- return typeof v === 'number' ? String(v) : typeof v === 'string' ? tsLiteral(v) : JSON.stringify(v);
1274
- };
1275
- // `name` is not recovered, so compile derives the directory from the default.
1276
- return toolOpts(t, { kind: 'httpRequest' }, [
1277
- ['kind', tsLiteral('httpRequest')],
1278
- ['url', fixed('url')], ['method', fixed('method')],
1279
- ['headers', fixed('headers')], ['params', fixed('params')],
1280
- ['body', fixed('body')], ['timeout', fixed('timeout')],
1281
- ]);
1282
- }
1283
- if (type.startsWith('uipath.agent.resource.tool.mcp.')) {
1284
- return toolOpts(t, { kind: 'mcp', name: '', key: rPlain(t, 'referenceKey') ?? '' }, [
1285
- ['kind', tsLiteral('mcp')],
1286
- ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1287
- ['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
1288
- // The mapper's field name for the canonical server slug.
1289
- ['slug', rPlain(t, 'serverUrl') !== undefined ? tsLiteral(rPlain(t, 'serverUrl')) : undefined],
1290
- ['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
1291
- ['mcpType', rPlain(t, 'mcpType') !== undefined ? tsLiteral(rPlain(t, 'mcpType')) : undefined],
1292
- ]);
1293
- }
1294
- if (type.startsWith('uipath.agent.resource.tool.a2a.')) {
1295
- return toolOpts(t, { kind: 'a2a', name: '', slug: '', key: rPlain(t, 'referenceKey') ?? '' }, [
1296
- ['kind', tsLiteral('a2a')],
1297
- ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1298
- ['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
1299
- ['slug', tsLiteral(rPlain(t, 'slug') ?? '')],
1300
- ['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
1301
- ]);
1302
- }
1303
- if (type.startsWith('uipath.agent.resource.tool.builtin.')) {
1304
- const tool = type.slice('uipath.agent.resource.tool.builtin.'.length);
1305
- return toolOpts(t, { kind: 'builtin', tool }, [
1306
- ['kind', tsLiteral('builtin')],
1307
- ['tool', tsLiteral(tool)],
1308
- ['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
1309
- ]);
1310
- }
1311
- if (type.startsWith('uipath.agent.resource.tool.ixp.')) {
1312
- const tail = type.slice('uipath.agent.resource.tool.ixp.'.length);
1313
- const projectId = tail.slice(tail.indexOf('.') + 1);
1314
- return toolOpts(t, { kind: 'ixp', name: '', projectId }, [
1315
- ['kind', tsLiteral('ixp')],
1316
- ['projectId', tsLiteral(projectId)],
1317
- ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1318
- ]);
1319
- }
1320
- // The per-instance resource kinds (process / api / flow / maestro / agent /
1321
- // function): the key is the node type's tail, the rest is a binding pair.
1322
- const PROCESS_NS = [
1323
- ['uipath.agent.resource.tool.processorchestration.', 'maestro'],
1324
- ['uipath.agent.resource.tool.process.', 'process'],
1325
- ['uipath.agent.resource.tool.function.', 'function'],
1326
- ['uipath.agent.resource.tool.api.', 'api'],
1327
- ['uipath.agent.resource.tool.flow.', 'flow'],
1328
- ['uipath.agent.resource.tool.agent.', 'agent'],
1329
- ];
1330
- const ns = PROCESS_NS.find(([prefix]) => type.startsWith(prefix));
1331
- if (ns) {
1332
- const def = runDefs.get(`${type}@${t.typeVersion}`) ?? runDefs.get(type);
1333
- const values = (def?.model?.bindings?.values ?? []);
1334
- const bound = (n) => {
1335
- const row = values.find((v) => v.name === n);
1336
- return typeof row?.default === 'string' ? row.default : undefined;
1337
- };
1338
- const key = type.slice(ns[0].length);
1339
- return toolOpts(t, { kind: 'process', name: '', folderPath: '', key }, [
1340
- ['kind', tsLiteral(ns[1])],
1341
- ['key', tsLiteral(key)],
1342
- ['name', tsLiteral(bound('name') ?? rPlain(t, 'name') ?? '')],
1343
- ['folderPath', tsLiteral(bound('folderPath') ?? '')],
1344
- ]);
1345
- }
1346
- return opts([['kind', tsLiteral('builtin')], ['tool', tsLiteral('summarize')]]); // unreachable in practice
1347
- });
1348
- const contextSrc = resource('context').map((c) => {
1349
- const { name, id, folderPath, sidecar } = contextIndexIdentity(node, c, rPlain);
1350
- // Compile labels the node with `name`; a canvas rename is kept as `label`.
1351
- const label = typeof c.display?.label === 'string' && c.display.label !== '' && c.display.label !== name
1352
- ? c.display.label : undefined;
1353
- return opts([
1354
- ['name', tsLiteral(name)],
1355
- ['id', tsLiteral(id)],
1356
- ['folderPath', folderPath !== undefined ? tsLiteral(folderPath) : undefined],
1357
- ['folderKey', rPlain(c, 'folderKey') !== undefined ? tsLiteral(rPlain(c, 'folderKey')) : undefined],
1358
- // `query` and the retrieval settings, from resource.json or the node (#1183).
1359
- ...contextIndexSettings(node, c, sidecar),
1360
- ['label', label !== undefined ? tsLiteral(label) : undefined],
1361
- ...placement(c, derivedContextSource(runFlowId, node.id, id)),
1362
- ]);
1363
- });
1364
- const escalationSrc = resource('escalation').map((e) => {
1365
- const ei = rInputs(e);
1366
- const recipients = Array.isArray(ei.recipients)
1367
- ? ei.recipients.map((r) => tsText(r.value))
1368
- : [];
1369
- const outcomeNames = Object.keys((ei.outcomeMapping ?? {}));
1370
- const isQuickForm = e.type === 'uipath.agent.resource.escalation.quick-form';
1371
- const rows = isQuickForm
1372
- ? (ei.schema?.fields ?? [])
1373
- : [];
1374
- const app = ei.app;
1375
- const extra = (ei._additionalProps ?? {});
1376
- return opts([
1377
- ['variant', isQuickForm ? tsLiteral('quick-form') : undefined],
1378
- ['name', tsLiteral(rPlain(e, 'name') ?? '')],
1379
- ['description', rPlain(e, 'description') !== undefined ? tsLiteral(rPlain(e, 'description')) : undefined],
1380
- ['fields', rows.length ? `[${rows.map((f) => {
1381
- const id = String(f.id ?? '');
1382
- const direction = String(f.direction ?? 'output');
1383
- // Every row key the source cannot carry is reported, never dropped
1384
- // silently: a warning, an error under --strict (#1250).
1385
- const loss = formFieldRowLoss(f, 'escalation');
1386
- if (loss.length > 0) {
1387
- const message = formFieldRowLossMessage(`quick-form escalation ${JSON.stringify(rPlain(e, 'name') ?? e.id)} on agent ${JSON.stringify(node.id)}`, id, loss);
1388
- if (runStrict)
1389
- throw new Error(`decompile: ${message}`);
1390
- warnOnce(message);
1391
- }
1392
- return opts([
1393
- ['id', tsText(f.id)],
1394
- // serialize defaults a row's label to its id.
1395
- ['label', typeof f.label === 'string' && f.label !== id ? tsLiteral(f.label) : undefined],
1396
- ['type', tsLiteral(sourceFormFieldType(f.type ?? 'string'))],
1397
- ['direction', tsLiteral(direction)],
1398
- ['required', typeof f.required === 'boolean' && direction !== 'input' ? String(f.required) : undefined],
1399
- ...formFieldSettingsSource(liftFormFieldSettings(f), dataSource),
1400
- ]);
1401
- }).join(', ')}]` : undefined],
1402
- ['app', app === undefined ? undefined : opts([
1403
- ['key', tsText(app.key)],
1404
- ['name', tsText(app.name)],
1405
- ['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? tsLiteral(app.folderPath) : undefined],
1406
- ])],
1407
- ['recipients', recipients.length ? `[${recipients.join(', ')}]` : undefined],
1408
- ['outcomes', outcomeNames.length ? `[${outcomeNames.map((o) => tsLiteral(o)).join(', ')}]` : undefined],
1409
- ['taskTitle', typeof extra.taskTitle === 'string' && extra.taskTitle !== '' ? tsLiteral(extra.taskTitle) : undefined],
1410
- // 'medium' is the platform default.
1411
- ['priority', typeof extra.priority === 'string' && extra.priority !== 'medium' ? tsLiteral(extra.priority) : undefined],
1412
- ...placement(e, derivedEscalationSource(runFlowId, node.id, rPlain(e, 'name') ?? '')),
1413
- ]);
1414
- });
1415
- const mem = resource('memory', 1)[0];
1416
- const memorySrc = mem === undefined ? undefined : (() => {
1417
- // The memory's id is the node type's tail; the def carries it too.
1418
- const tail = mem.type.slice('uipath.agent.resource.memory.'.length);
1419
- const id = tail.slice(tail.indexOf('.') + 1);
1420
- const mi = rInputs(mem);
1421
- return opts([
1422
- ['name', tsLiteral(rPlain(mem, 'name') ?? '')],
1423
- ['id', tsLiteral(id)],
1424
- ['description', rPlain(mem, 'description') !== undefined ? tsLiteral(rPlain(mem, 'description')) : undefined],
1425
- // The platform defaults: few-shot on, similarity 0, k 3, hybrid search.
1426
- ['dynamicFewShotLearning', unwrapPlain(mi.dynamicFewShotLearning) === false ? 'false' : undefined],
1427
- ['semanticSimilarity', num(mi.semanticSimilarity) !== undefined && num(mi.semanticSimilarity) !== '0' ? num(mi.semanticSimilarity) : undefined],
1428
- ['kValue', num(mi.kValue) !== undefined && num(mi.kValue) !== '3' ? num(mi.kValue) : undefined],
1429
- ['searchMode', rPlain(mem, 'searchMode') !== undefined && rPlain(mem, 'searchMode') !== 'hybrid'
1430
- ? tsLiteral(rPlain(mem, 'searchMode')) : undefined],
1431
- ]);
1432
- })();
1433
- const guardrails = Array.isArray(i.guardrails) && i.guardrails.length > 0
1434
- ? JSON.stringify(i.guardrails)
1435
- : undefined;
1436
- const list = (items) => (items.length ? `[${items.join(', ')}]` : undefined);
1437
- if (node.type === T.voiceAgent) {
1438
- const v = (i.voice ?? {});
1439
- const voiceSrc = Object.keys(v).length === 0 ? undefined : opts([
1440
- ['model', typeof v.model === 'string' ? tsLiteral(v.model) : undefined],
1441
- ['persona', typeof v.persona === 'string' ? tsLiteral(v.persona) : undefined],
1442
- ['temperature', num(v.temperature)],
1443
- ['maxTokens', num(v.maxTokens)],
1444
- ]);
1445
- return `${imp.use('voiceAgent')}(${opts([
1446
- ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1447
- ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1448
- ['callContext', (() => {
1449
- const raw = plain(i.callContext) ?? '';
1450
- const js = wireExprBody(raw);
1451
- // Anything but a `$vars.` reference stays the wire text verbatim.
1452
- return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : tsLiteral(raw);
1453
- })()],
1454
- ['voice', voiceSrc],
1455
- ['maxIterations', num(i.maxIterations)],
1456
- ['source', tsLiteral(plain(i.source) ?? '')],
1457
- ])})`;
1458
- }
1459
- if (node.type === T.conversationalAgent) {
1460
- const st = (i.conversationalAgentSettings ?? {});
1461
- const settingExpr = (k) => {
1462
- // `unwrapSource` rather than a string test: since the envelope reaches
1463
- // these five keys (migrate's `NESTED_BINDING_INPUTS`), a bare string is
1464
- // only what a pre-1.6 file carries. Reading strings alone made every
1465
- // modern file decompile to a conversational agent with no turn wiring.
1466
- const raw = unwrapSource(st[k]);
1467
- if (raw === undefined || raw === '' || raw === '=js:')
1468
- return undefined;
1469
- // Only a `=js:` value is an expression: `emitRef` lowers a single path to
1470
- // `out(...)` and anything composite to a `js` template. Any other string
1471
- // is literal text, as the product reads it (flow-schema
1472
- // `coerceToExpressionValue`), even when it looks like `$vars.…`.
1473
- return raw.startsWith('=js:') ? emitRef(raw.slice(4), imp, inputNames) : tsLiteral(raw);
1474
- };
1475
- // `'simple'` mode DERIVES the four runtime fields from `context`, so only
1476
- // the context binding is the author's; recovering the derived ones would
1477
- // re-emit them as hand-bound custom fields.
1478
- const mode = typeof st.mode === 'string' ? st.mode : 'simple';
1479
- const settingsSrc = mode === 'custom'
1480
- ? opts([
1481
- ['mode', tsLiteral('custom')],
1482
- ['conversationId', settingExpr('conversationId')],
1483
- ['exchangeId', settingExpr('exchangeId')],
1484
- ['messages', settingExpr('messages')],
1485
- ['userSettings', settingExpr('userSettings')],
1486
- ])
1487
- : opts([['context', settingExpr('context')]]);
1488
- return `${imp.use('conversationalAgent')}(${opts([
1489
- ['model', tsLiteral(plain(i.model) ?? '')],
1490
- ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1491
- // `settings` is required on ConversationalAgentInputs, so an empty one
1492
- // stays as `{}`, which check then reports as CONV_AGENT_NO_TURN.
1493
- ['settings', settingsSrc || '{}'],
1494
- ['temperature', num(i.temperature)],
1495
- ['maxTokenPerResponse', num(i.maxTokenPerResponse)],
1496
- ['maxIterations', num(i.maxIterations)],
1497
- ['guardrails', guardrails],
1498
- ['source', tsLiteral(plain(i.source) ?? '')],
1499
- ])})`;
1500
- }
1501
- return `${imp.use('inlineAgent')}(${opts([
1502
- ['model', tsLiteral(plain(i.model) ?? '')],
1503
- ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1504
- ['userPrompt', promptSrc(i.userPrompt) ?? tsLiteral('')],
1505
- ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1506
- ['returns', returns],
1507
- ['temperature', num(i.temperature)],
1508
- ['maxTokenPerResponse', num(i.maxTokenPerResponse)],
1509
- ['maxIterations', num(i.maxIterations)],
1510
- ['mode', plain(i.mode) !== undefined ? tsLiteral(plain(i.mode)) : undefined],
1511
- ['guardrails', guardrails],
1512
- ['context', list(contextSrc)],
1513
- ['tools', list(toolSrc)],
1514
- ['escalation', list(escalationSrc)],
1515
- ['memory', memorySrc],
1516
- // The agent's directory uuid is derived from the flow and step ids, but a
1517
- // decompiled flow must keep the ORIGINAL — the sidecar lives at
1518
- // `<source>/agent.json`, so a re-derived one would point at nothing.
1519
- ['source', tsLiteral(plain(i.source) ?? '')],
1520
- ])})`;
1521
- }
1522
800
  /**
1523
801
  * A connector EVENT subscription, shared by the trigger (`onEvent`) and the
1524
802
  * mid-flow wait (`waitForEvent`) — the same node body in both places, so the
@@ -1608,83 +886,6 @@ function emitEventSubscription(node, imp, inputNames) {
1608
886
  ['folder', folder !== undefined ? tsLiteral(folder) : undefined],
1609
887
  ]) + unboundMarker(node, { connection: conn, folder }, connector, event);
1610
888
  }
1611
- /**
1612
- * Read back the expression template older SDK builds emitted on an in-solution
1613
- * `agent()` input (#1013): `{ type: 'literal', expression: '{{ <js> }}' }`.
1614
- * serialize now emits the platform's `{ type: 'jsExpression', expression }`
1615
- * instead, but `.flow` files written between 2026-08-10 and the fix still carry
1616
- * the template, and the runtime evaluates it (the agent receives the value, not
1617
- * the braces). So on an in-solution agent node a literal that is exactly one
1618
- * `{{ … }}` is that expression, and it decompiles to the reference it was
1619
- * written from rather than to a string. Anything else is returned unchanged.
1620
- */
1621
- function legacyAgentTemplateInput(v) {
1622
- if (!v || typeof v !== 'object' || Array.isArray(v))
1623
- return v;
1624
- const o = v;
1625
- if (o.type !== 'literal' || typeof o.expression !== 'string')
1626
- return v;
1627
- const m = /^\{\{\s*([\s\S]+?)\s*\}\}$/.exec(o.expression);
1628
- if (!m || m[1].includes('}}') || m[1].includes('{{'))
1629
- return v;
1630
- return { ...o, type: 'jsExpression', expression: m[1] };
1631
- }
1632
- function emitPublishedResource(node, imp, inputNames) {
1633
- // The published AGENT resource — the same shape as the process family's
1634
- // node types (identity in the definition's binding pair, contract in its
1635
- // output schema), which lift themselves (nodes/process/flow/).
1636
- const FAMILIES = [
1637
- { prefix: 'uipath.core.agent.', factory: 'agent' },
1638
- ];
1639
- const family = FAMILIES.find((f) => node.type.startsWith(f.prefix));
1640
- if (!family)
1641
- return undefined;
1642
- const key = node.type.slice(family.prefix.length);
1643
- const def = runDefs.get(`${node.type}@${node.typeVersion}`) ?? runDefs.get(node.type);
1644
- const values = (def?.model?.bindings?.values ?? []);
1645
- const binding = (name) => {
1646
- const row = values.find((v) => v.name === name);
1647
- return typeof row?.default === 'string' ? row.default : undefined;
1648
- };
1649
- // The binding pair is the identity the runtime resolves by; without a
1650
- // definition to read there is nothing to recover it from.
1651
- const name = binding('name');
1652
- const folderPath = binding('folderPath');
1653
- // A sibling project of the same solution (#951): the model names its project
1654
- // instead of a folder. `isInSolution` is flow-core's marker; an in-solution
1655
- // agent emitted before #951 carries only `projectId`. Read back as
1656
- // `location: 'in-solution'` so the recompile takes the in-solution path
1657
- // instead of a published one with an empty folder.
1658
- const projectId = typeof def?.model?.projectId === 'string' ? def.model.projectId : undefined;
1659
- const inSolution = def?.model?.isInSolution === true || projectId !== undefined;
1660
- const inputs = { ...(node.inputs ?? {}) };
1661
- // The design-time fire-and-forget switch is an OPTION on the factory, not a
1662
- // process argument — serialize adds it to `inputs` after rendering the
1663
- // author's map, so it comes back off here.
1664
- const fireAndForget = unwrapPlain(inputs.fireAndForget) === true;
1665
- delete inputs.fireAndForget;
1666
- if (family.factory === 'agent' && (def?.model?.isInSolution === true || def?.model?.projectId !== undefined)) {
1667
- for (const [k, v] of Object.entries(inputs))
1668
- inputs[k] = legacyAgentTemplateInput(v);
1669
- }
1670
- const inputRows = Object.entries(inputs)
1671
- .map(([k, v]) => [k, emitVal(v, imp, inputNames)]);
1672
- const schema = def?.outputDefinition?.output?.schema?.properties;
1673
- const returns = schema && Object.keys(schema).length > 0
1674
- ? objLit(Object.entries(schema).map(([k, v]) => [k, tsLiteral(String(v?.type ?? 'string'))]))
1675
- : undefined;
1676
- return `${imp.use(family.factory)}(${opts([
1677
- ['key', tsLiteral(key)],
1678
- ['name', name !== undefined ? tsLiteral(name) : undefined],
1679
- ['location', inSolution ? tsLiteral('in-solution') : undefined],
1680
- ['folderPath', !inSolution && folderPath !== undefined ? tsLiteral(folderPath) : undefined],
1681
- ['projectId', inSolution && projectId !== undefined ? tsLiteral(projectId) : undefined],
1682
- ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1683
- ['returns', returns],
1684
- ['form', family.form !== undefined ? tsLiteral(family.form) : undefined],
1685
- ['completion', fireAndForget ? tsLiteral('fire-and-forget') : undefined],
1686
- ])})`;
1687
- }
1688
889
  function emitConnector(node, imp, inputNames, o) {
1689
890
  const split = splitConnector(node.type);
1690
891
  const detail = (node.inputs?.detail ?? {});
@@ -3084,13 +2285,24 @@ function layoutSeg(flow, nodes, edges) {
3084
2285
  // compile refuses the whole `.layout()`. So its entry, and any edge route that
3085
2286
  // ends at it, is left out and compile places the node itself.
3086
2287
  const renamed = (id) => runAbsorbed.has(id) && runAbsorbed.get(id) !== id;
2288
+ // A resource attached to a node whose spec cannot carry it (a tool on a voice
2289
+ // agent) is reached only over artifact edges, and no spec folded it in: the
2290
+ // source has no spelling for it, so the recompiled flow has no such node, and
2291
+ // a layout entry for it would make compile refuse the whole `.layout()`.
2292
+ const incoming = new Map();
2293
+ for (const e of edges)
2294
+ (incoming.get(e.targetNodeId) ?? incoming.set(e.targetNodeId, []).get(e.targetNodeId)).push(e);
2295
+ const unspelled = (id) => {
2296
+ const ins = incoming.get(id) ?? [];
2297
+ return ins.length > 0 && !runAbsorbed.has(id) && ins.every((e) => ARTIFACT_PORTS.has(e.sourcePort));
2298
+ };
3087
2299
  const nodeEntries = [];
3088
2300
  for (const n of nodes) {
3089
2301
  // A sticky note carries its own geometry in `.stickyNotes()`; a layout entry
3090
2302
  // for it would be a second, competing source for the same coordinates.
3091
2303
  if (n.type === STICKY_NOTE_TYPE)
3092
2304
  continue;
3093
- if (renamed(n.id))
2305
+ if (renamed(n.id) || unspelled(n.id))
3094
2306
  continue;
3095
2307
  const ui = { ...(n.ui ?? {}), ...(hoisted[n.id] ?? {}) };
3096
2308
  const pos = ui.position;
@@ -3128,7 +2340,7 @@ function layoutSeg(flow, nodes, edges) {
3128
2340
  }
3129
2341
  if (!parts.length)
3130
2342
  continue;
3131
- if (renamed(e.sourceNodeId) || renamed(e.targetNodeId))
2343
+ if (renamed(e.sourceNodeId) || renamed(e.targetNodeId) || unspelled(e.targetNodeId))
3132
2344
  continue;
3133
2345
  const pair = `${e.sourceNodeId}->${e.targetNodeId}`;
3134
2346
  if ((pairCount.get(pair) ?? 0) !== 1)