@uipath/packager-tool-flow 1.201.0-preview.133 → 1.202.0-preview.134

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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Encoding-agnostic readers for the inline-agent input encoding (MST-12020).
3
+ *
4
+ * flow-workbench writes inline-agent input names in one of two schemes, gated by its
5
+ * `services.agent-storage.nested-agent-input-schema` feature flag:
6
+ * - FLAT (shipped default): one top-level `inputSchema` property per input, `__`-joined
7
+ * (`script1__output__query`), prompts read `{{input.script1__output__query}}`.
8
+ * - NESTED (flag on): a nested object tree whose auto-created container nodes carry
9
+ * `x-uipath-container: true`, prompts read `{{input.script1.output.query}}` (the runtime
10
+ * template engine treats `.` as navigation).
11
+ *
12
+ * The CLI must read BOTH — an agent.json is whatever the authoring canvas last wrote — so these
13
+ * helpers detect per file instead of consuming any flag. Detection is strictly the per-node
14
+ * container marker: an object property WITHOUT the marker is a user-declared whole-object input
15
+ * under either encoding and must be treated as one leaf, never unfolded.
16
+ *
17
+ * TODO: replace with flow-workbench's canonical helpers (`collectInlineAgentInputsFromAgent`,
18
+ * `decodeFlatName`, `getContainerChildProps`) once they are re-exported from
19
+ * `@uipath/flow-converter` — that package is the single source of truth for the encoding.
20
+ */
21
+ /** Marker flow-workbench stamps on auto-created container nodes of a NESTED inputSchema. */
22
+ export declare const SCHEMA_CONTAINER_MARKER = "x-uipath-container";
23
+ /** One bindable input leaf of an agent `inputSchema`, in both name forms. */
24
+ export interface AgentInputLeaf {
25
+ /** Flat `__`-joined name — what a flat prompt token / flat JobArgument uses. */
26
+ flatKey: string;
27
+ /** Canonical dotted path — what a nested prompt token navigates. */
28
+ path: string;
29
+ /** Declared `type` of the leaf node, when present. */
30
+ type?: string;
31
+ }
32
+ /** Canonical dotted form of an input name from either encoding (`a__b` and `a.b` → `a.b`). */
33
+ export declare function canonicalAgentInputPath(name: string): string;
34
+ /**
35
+ * Enumerate the bindable input leaves of an agent `inputSchema`, descending MARKED containers
36
+ * (nested encoding) and passing flat / declared-object top-level keys through as single leaves.
37
+ * Returns `[]` for an absent/empty/malformed schema.
38
+ */
39
+ export declare function collectAgentInputLeaves(inputSchema: unknown): AgentInputLeaf[];
40
+ /**
41
+ * Source binding expression for one input leaf — the inverse of flow-workbench's encode:
42
+ * a `metadata`-rooted path binds `$metadata.*`; everything else binds `$vars.*`.
43
+ */
44
+ export declare function leafToBinding(leaf: AgentInputLeaf): string;
package/dist/agents.d.ts CHANGED
@@ -8,7 +8,9 @@ export interface InlineAgentContract {
8
8
  }
9
9
  /**
10
10
  * Build the execution contract for one inline agent with the same migration,
11
- * validation, and binding pipeline used by normal solution packaging.
11
+ * validation, and binding pipeline used by normal solution packaging. The
12
+ * returned `agentJson` is healed to the flat input encoding (MST-12020) — see
13
+ * {@link healAgentInputEncoding}.
12
14
  */
13
15
  export declare function buildInlineAgentContract(config: {
14
16
  fileSystem: IFileSystem;
@@ -37,6 +37,15 @@ export declare class FlowTool extends ProjectTool {
37
37
  * `buildVoiceAgentDefinitions` reports it.
38
38
  */
39
39
  private resolveVoiceAgents;
40
+ /**
41
+ * Reconcile each inline-agent node's `agentInputVariables` from its
42
+ * agent's `inputSchema` before conversion. Reads the schema from the
43
+ * already-built agent (`builtAgents`) first, falling back to an
44
+ * `agent.json` copied under `content/<source>/`. Mutates `workflow.nodes`
45
+ * in place; best-effort, so a missing/unreadable agent leaves the node
46
+ * as-is.
47
+ */
48
+ private hydrateInlineAgentInputs;
40
49
  /** Read an `agent.json` copied in without going through `buildAgent`. */
41
50
  private readCopiedAgentJson;
42
51
  /**
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ export { type AgentInputLeaf, canonicalAgentInputPath, collectAgentInputLeaves, leafToBinding, SCHEMA_CONTAINER_MARKER, } from "./agent-input-encoding-utils.js";
1
2
  export { buildInlineAgentContract, type InlineAgentContract, } from "./agents.js";
2
3
  export { replaceExporterVersion } from "./exporter-version.js";
3
4
  export { createFlowRefResolver, type FlowIoLogger, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveWorkflowRefs, } from "./flow-io.js";
4
5
  export { FlowTool } from "./flow-tool.js";
5
6
  export { FlowToolFactory } from "./flow-tool-factory.js";
6
- export { isInlineAgentNodeType, readInlineAgentSource, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
7
+ export { type AgentInputVariable, hydrateInlineAgentInputVariables, isInlineAgentNodeType, readInlineAgentSource, reconcileInlineAgentInputVariables, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
7
8
  export { operateRuntimeOptions } from "./operate-runtime-options.js";
8
9
  export { assertVoiceAgentDefinitionsEmbedded, type VoiceAgentFlowNode, voiceConvertOptions, } from "./voice-agent-definitions.js";
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  // package.json
33
33
  var package_default = {
34
34
  name: "@uipath/packager-tool-flow",
35
- version: "1.201.0-preview.133",
35
+ version: "1.202.0-preview.134",
36
36
  description: "UiPath Flow tool implementation",
37
37
  type: "module",
38
38
  exports: {
@@ -75,17 +75,18 @@ var package_default = {
75
75
  license: "ISC",
76
76
  peerDependencies: {
77
77
  "@uipath/filesystem": "workspace:*",
78
- "@uipath/flow-converter": "^0.53.6",
79
- "@uipath/flow-core": "^0.92.5",
80
- "@uipath/flow-migrations": "^0.33.0",
81
- "@uipath/flow-schema": "^0.57.6",
78
+ "@uipath/flow-converter": "0.55.0-develop",
79
+ "@uipath/flow-core": "0.96.0-develop",
80
+ "@uipath/flow-migrations": "0.45.0-develop",
81
+ "@uipath/flow-schema": "0.57.4-develop",
82
82
  "@uipath/solutionpackager-tool-core": "workspace:*",
83
83
  "@uipath/tool-agent": "^2.0.0"
84
84
  },
85
85
  devDependencies: {
86
86
  "@types/node": "^25.5.2",
87
87
  "@uipath/filesystem": "workspace:*",
88
- "@uipath/flow-core": "^0.92.5",
88
+ "@uipath/flow-converter": "0.55.0-develop",
89
+ "@uipath/flow-core": "0.96.0-develop",
89
90
  "@uipath/solutionpackager-tool-core": "workspace:*",
90
91
  "@uipath/tool-agent": "^2.0.0",
91
92
  "@vitest/browser": "^4.1.6",
@@ -98,6 +99,10 @@ var package_default = {
98
99
  };
99
100
 
100
101
  // src/agents.ts
102
+ import {
103
+ healResourceInputEncoding,
104
+ normalizeAgentInputEncoding
105
+ } from "@uipath/flow-converter";
101
106
  import { Path as Path2 } from "@uipath/solutionpackager-tool-core";
102
107
  import { buildAgent } from "@uipath/tool-agent/build";
103
108
 
@@ -139,13 +144,19 @@ async function buildInlineAgentContract(config) {
139
144
  options: { writeBindings: false }
140
145
  });
141
146
  return {
142
- agentJson: built.agent,
147
+ agentJson: healAgentInputEncoding(built.agent),
143
148
  bindingsJson: {
144
149
  version: built.bindings.version,
145
150
  resources: built.bindings.resources
146
151
  }
147
152
  };
148
153
  }
154
+ function healAgentInputEncoding(agent) {
155
+ const normalized = normalizeAgentInputEncoding(agent);
156
+ const rawResources = normalized.resources;
157
+ const resources = Array.isArray(rawResources) ? rawResources.map((resource) => resource && typeof resource === "object" && !Array.isArray(resource) ? healResourceInputEncoding(resource) : resource) : rawResources;
158
+ return { ...normalized, resources };
159
+ }
149
160
  var processAgents = async (fileSystem, logger, projectPath, contentFolder) => {
150
161
  const agentResults = [];
151
162
  const builtAgents = new Map;
@@ -168,8 +179,13 @@ var processAgents = async (fileSystem, logger, projectPath, contentFolder) => {
168
179
  outputPath: outputDir,
169
180
  logger
170
181
  });
182
+ let agentJson = result.agent;
183
+ if (agentJson) {
184
+ agentJson = healAgentInputEncoding(agentJson);
185
+ await fileSystem.writeFile(Path2.join(outputDir, "agent.json"), JSON.stringify(agentJson, null, 2));
186
+ }
171
187
  agentResults.push(result);
172
- builtAgents.set(entry, result.agent);
188
+ builtAgents.set(entry, agentJson);
173
189
  }
174
190
  const agentBindingResources = agentResults.flatMap(toBindingsResources);
175
191
  if (agentBindingResources.length > 0) {
@@ -434,7 +450,57 @@ async function readUtf8OrNull(fs, filePath) {
434
450
  }
435
451
 
436
452
  // src/inline-agent-utils.ts
437
- import { isAgentNodeType } from "@uipath/flow-schema";
453
+ import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
454
+
455
+ // src/agent-input-encoding-utils.ts
456
+ var SCHEMA_CONTAINER_MARKER = "x-uipath-container";
457
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
458
+ function containerChildren(prop) {
459
+ if (!isRecord(prop))
460
+ return;
461
+ if (prop[SCHEMA_CONTAINER_MARKER] !== true)
462
+ return;
463
+ const children = prop.properties;
464
+ return isRecord(children) ? children : undefined;
465
+ }
466
+ function canonicalAgentInputPath(name) {
467
+ return name.split("__").join(".");
468
+ }
469
+ function collectAgentInputLeaves(inputSchema) {
470
+ const properties = isRecord(inputSchema) ? inputSchema.properties : undefined;
471
+ if (!isRecord(properties))
472
+ return [];
473
+ const leaves = [];
474
+ const walk = (prop, segments) => {
475
+ const children = containerChildren(prop);
476
+ if (!children) {
477
+ const flatKey = segments.join("__");
478
+ leaves.push({
479
+ flatKey,
480
+ path: canonicalAgentInputPath(segments.join(".")),
481
+ type: isRecord(prop) && typeof prop.type === "string" ? prop.type : undefined
482
+ });
483
+ return;
484
+ }
485
+ for (const [key, child] of Object.entries(children)) {
486
+ walk(child, [...segments, key]);
487
+ }
488
+ };
489
+ for (const [key, prop] of Object.entries(properties)) {
490
+ walk(prop, [key]);
491
+ }
492
+ return leaves;
493
+ }
494
+ function leafToBinding(leaf) {
495
+ const segments = leaf.path.split(".");
496
+ if (segments[0] === "metadata") {
497
+ const rest = segments.slice(1).join(".");
498
+ return rest ? `=$metadata.${rest}` : "=$metadata";
499
+ }
500
+ return `=$vars.${leaf.path}`;
501
+ }
502
+
503
+ // src/inline-agent-utils.ts
438
504
  function readInlineAgentSource(node) {
439
505
  const candidates = [
440
506
  node.inputs?.source,
@@ -452,6 +518,55 @@ function readInlineAgentSource(node) {
452
518
  function isInlineAgentNodeType(type) {
453
519
  return isAgentNodeType(type);
454
520
  }
521
+ function reconcileInlineAgentInputVariables(existing, inputSchema) {
522
+ const leaves = collectAgentInputLeaves(inputSchema);
523
+ if (leaves.length === 0) {
524
+ return { variables: existing, added: 0 };
525
+ }
526
+ const existingPaths = new Set(existing.map((e) => e.id).filter((id) => typeof id === "string").map(canonicalAgentInputPath));
527
+ const additions = leaves.filter((leaf) => !existingPaths.has(leaf.path)).map((leaf) => {
528
+ const binding = leafToBinding(leaf);
529
+ return {
530
+ id: leaf.flatKey,
531
+ type: leaf.type ?? "string",
532
+ binding,
533
+ description: `Bound from ${binding.slice(1)}`
534
+ };
535
+ });
536
+ if (additions.length === 0) {
537
+ return { variables: existing, added: 0 };
538
+ }
539
+ return { variables: [...existing, ...additions], added: additions.length };
540
+ }
541
+ async function hydrateInlineAgentInputVariables(nodes, resolveInputSchema, onReconcile, onUnresolved) {
542
+ for (const node of nodes) {
543
+ if (!isInlineAgentNodeType(node.type))
544
+ continue;
545
+ const source = readInlineAgentSource(node);
546
+ if (!source)
547
+ continue;
548
+ const reportable = !isVoiceAgentNodeType(node.type);
549
+ if (/[/\\.]/.test(source)) {
550
+ if (reportable)
551
+ onUnresolved?.(source, "invalid-source");
552
+ continue;
553
+ }
554
+ const inputSchema = await resolveInputSchema(source);
555
+ if (inputSchema === undefined) {
556
+ if (reportable)
557
+ onUnresolved?.(source, "schema-unavailable");
558
+ continue;
559
+ }
560
+ const existing = Array.isArray(node.inputs?.agentInputVariables) ? node.inputs.agentInputVariables : [];
561
+ const { variables, added } = reconcileInlineAgentInputVariables(existing, inputSchema);
562
+ if (added === 0)
563
+ continue;
564
+ if (!node.inputs)
565
+ node.inputs = {};
566
+ node.inputs.agentInputVariables = variables;
567
+ onReconcile?.(source, added);
568
+ }
569
+ }
455
570
  function setPublishIntentOnInlineAgents(fileFormat) {
456
571
  const ff = fileFormat;
457
572
  for (const def of ff.definitions ?? []) {
@@ -471,7 +586,7 @@ function operateRuntimeOptions(nodes) {
471
586
  }
472
587
 
473
588
  // src/voice-agent-definitions.ts
474
- import { isVoiceAgentNodeType } from "@uipath/flow-schema";
589
+ import { isVoiceAgentNodeType as isVoiceAgentNodeType2 } from "@uipath/flow-schema";
475
590
  function nodeLabel(node) {
476
591
  const label = node.display?.label;
477
592
  return typeof label === "string" && label.length > 0 ? label : node.id;
@@ -479,7 +594,7 @@ function nodeLabel(node) {
479
594
  function collectVoiceAgentNodes(nodes) {
480
595
  const result = [];
481
596
  for (const node of nodes) {
482
- if (!isVoiceAgentNodeType(node.type))
597
+ if (!isVoiceAgentNodeType2(node.type))
483
598
  continue;
484
599
  const source = readInlineAgentSource(node);
485
600
  if (!source) {
@@ -510,7 +625,7 @@ function assertNoSubflowVoiceAgents(flowJson) {
510
625
  const workflow = JSON.parse(flowJson);
511
626
  for (const [subflowId, entry] of Object.entries(workflow.subflows ?? {})) {
512
627
  for (const node of entry.nodes ?? []) {
513
- if (!isVoiceAgentNodeType(node.type))
628
+ if (!isVoiceAgentNodeType2(node.type))
514
629
  continue;
515
630
  throw new Error(`Voice agent node "${nodeLabel(node)}" is inside subflow "${subflowId}". ` + "Voice agent nodes are not supported inside subflows — move it to the top-level flow.");
516
631
  }
@@ -653,6 +768,7 @@ class FlowTool extends ProjectTool {
653
768
  this.logger.warn(`Could not load ${flowFile} as a workflow — skipping artifact generation: ${message}`);
654
769
  return;
655
770
  }
771
+ await this.hydrateInlineAgentInputs(workflow, builtAgents, contentFolder);
656
772
  const bpmnFileName = flowFile.replace(/\.flow$/, ".bpmn");
657
773
  const bpmnPath = Path3.join(contentFolder, bpmnFileName);
658
774
  const fileFormat = inMemoryWorkflowToFileFormat(workflow);
@@ -745,6 +861,13 @@ class FlowTool extends ProjectTool {
745
861
  }
746
862
  return voiceAgents;
747
863
  }
864
+ async hydrateInlineAgentInputs(workflow, builtAgents, contentFolder) {
865
+ const nodes = workflow.nodes ?? [];
866
+ await hydrateInlineAgentInputVariables(nodes, async (source) => {
867
+ const agent = builtAgents.get(source) ?? await this.readCopiedAgentJson(contentFolder, source);
868
+ return agent?.inputSchema;
869
+ }, (source, added) => this.logger.info(`Reconciled ${added} inline-agent input(s) for "${source}" from agent.json inputSchema`), (source, reason) => this.logger.warn(reason === "invalid-source" ? `Inline agent "${source}" is not a bare project-id folder, so its inputs were not reconciled and packaging skips it — the agent would ship with no bound inputs.` : `Could not read an inputSchema for inline agent "${source}", so its inputs were not reconciled — the agent would ship with no bound inputs. Check that ${source}/agent.json exists and parses.`));
870
+ }
748
871
  async readCopiedAgentJson(contentFolder, source) {
749
872
  const agentJsonPath = Path3.join(contentFolder, source, "agent.json");
750
873
  if (!await this.fileSystem.exists(agentJsonPath)) {
@@ -838,21 +961,27 @@ class FlowToolFactory {
838
961
  // src/index.ts
839
962
  toolsFactoryRepository.registerProjectToolFactory(new FlowToolFactory);
840
963
  export {
841
- voiceConvertOptions,
842
- setPublishIntentOnInlineAgents,
843
- resolveWorkflowRefs,
844
- resolveAndMigrateWorkflow,
845
- replaceExporterVersion,
846
- readInlineAgentSource,
847
- readFlowWorkflow,
848
- operateRuntimeOptions,
849
- migrateRawWorkflow,
850
- isInlineAgentNodeType,
851
- createFlowRefResolver,
852
- buildInlineAgentContract,
853
- assertVoiceAgentDefinitionsEmbedded,
964
+ FlowTool,
854
965
  FlowToolFactory,
855
- FlowTool
966
+ SCHEMA_CONTAINER_MARKER,
967
+ assertVoiceAgentDefinitionsEmbedded,
968
+ buildInlineAgentContract,
969
+ canonicalAgentInputPath,
970
+ collectAgentInputLeaves,
971
+ createFlowRefResolver,
972
+ hydrateInlineAgentInputVariables,
973
+ isInlineAgentNodeType,
974
+ leafToBinding,
975
+ migrateRawWorkflow,
976
+ operateRuntimeOptions,
977
+ readFlowWorkflow,
978
+ readInlineAgentSource,
979
+ reconcileInlineAgentInputVariables,
980
+ replaceExporterVersion,
981
+ resolveAndMigrateWorkflow,
982
+ resolveWorkflowRefs,
983
+ setPublishIntentOnInlineAgents,
984
+ voiceConvertOptions
856
985
  };
857
986
 
858
- //# debugId=7E6DF946A11A786164756E2164756E21
987
+ //# debugId=A6C01CA5DD3E27EB64756E2164756E21
@@ -10,6 +10,73 @@ export declare function readInlineAgentSource(node: {
10
10
  }): string | undefined;
11
11
  /** True for a known inline-agent node type. */
12
12
  export declare function isInlineAgentNodeType(type: string | undefined): boolean;
13
+ /** One flow-node `agentInputVariables[]` entry. */
14
+ export interface AgentInputVariable {
15
+ id?: string;
16
+ type?: string;
17
+ binding?: string;
18
+ description?: string;
19
+ }
20
+ /**
21
+ * Reconcile a node's existing `agentInputVariables` against an agent's
22
+ * `inputSchema` — the input contract the runtime actually binds.
23
+ *
24
+ * Adds a derived entry for every input leaf the node is missing and preserves
25
+ * every entry the node already declares, so a custom or divergent binding is
26
+ * never clobbered. Leaves come from {@link collectAgentInputLeaves}, so a
27
+ * nested schema descends its marked containers rather than being read as one
28
+ * opaque top-level key.
29
+ *
30
+ * Comparison is on the canonical dotted path, so a node that declares the
31
+ * nested spelling (`a.b`) is not re-added under the flat one (`a__b`). The
32
+ * derived id is always the FLAT spelling: the packager heals the shipped
33
+ * agent.json prompts and schema to the flat encoding, and the converter emits
34
+ * each id verbatim as a JobArgument name, so flat ids are what match the
35
+ * healed `{{input.<flat>}}` tokens for both schema encodings.
36
+ *
37
+ * Returns the reconciled list plus how many entries were added; when nothing
38
+ * is missing (or the schema is empty) returns the original array unchanged so
39
+ * callers can skip a spurious mutation.
40
+ */
41
+ export declare function reconcileInlineAgentInputVariables(existing: AgentInputVariable[], inputSchema: unknown): {
42
+ variables: AgentInputVariable[];
43
+ added: number;
44
+ };
45
+ /** Minimal shape of a flow node needed to hydrate inline-agent inputs. */
46
+ interface InlineAgentNodeLike {
47
+ type?: string;
48
+ inputs?: Record<string, unknown>;
49
+ model?: Record<string, unknown>;
50
+ }
51
+ /**
52
+ * Reconcile every inline-agent node's `agentInputVariables` against its
53
+ * agent's `inputSchema` before BPMN conversion. Mutates `nodes` in place.
54
+ *
55
+ * Studio Web keeps the input binding in the agent's `inputSchema` and rebuilds
56
+ * the node's `agentInputVariables` from it in memory on load; the `.flow` it
57
+ * writes carries an empty `agentInputVariables`. The converter derives the
58
+ * agent's `JobArguments` from `agentInputVariables` and never reads
59
+ * `agent.json`, so an SW-authored flow would otherwise convert to an agent with
60
+ * `JobArguments = {"input":""}` — works in SW, drops every bound input on
61
+ * `flow pack`, `solution pack` and `debug`. Mirroring SW here fills the gap.
62
+ *
63
+ * `resolveInputSchema` returns the agent's `inputSchema` for a given `source`,
64
+ * or `undefined` when it cannot be resolved. Best-effort by design: an
65
+ * unresolvable agent leaves its node as-is rather than failing the build.
66
+ *
67
+ * `onUnresolved` fires for a node this function could NOT hydrate, so callers
68
+ * can say so. Without it the empty-`JobArguments` bug this exists to fix comes
69
+ * back silently: the node keeps `agentInputVariables: []`, the converter emits
70
+ * the `{"input":""}` floor, and nothing in the output explains why. It does not
71
+ * fire for a node that needed nothing — an agent declaring no inputs, or one
72
+ * whose bindings the node already covers, is not a problem.
73
+ *
74
+ * Voice agents are hydrated but never reported: their inputs are authored on
75
+ * the node (`callContext`, explicit `agentInputVariables`) rather than bound
76
+ * from a schema, so an unresolved schema is the normal case and packaging
77
+ * already warns separately about a voice agent's embedded definition.
78
+ */
79
+ export declare function hydrateInlineAgentInputVariables(nodes: InlineAgentNodeLike[], resolveInputSchema: (source: string) => Promise<unknown>, onReconcile?: (source: string, added: number) => void, onUnresolved?: (source: string, reason: "invalid-source" | "schema-unavailable") => void): Promise<void>;
13
80
  /**
14
81
  * Set `__packageIntent = "Publish"` on inline agent definitions in a
15
82
  * file-format workflow so the BPMN converter emits "content/" prefixed
@@ -18,3 +85,4 @@ export declare function isInlineAgentNodeType(type: string | undefined): boolean
18
85
  * Mutates `fileFormat` in place.
19
86
  */
20
87
  export declare function setPublishIntentOnInlineAgents(fileFormat: unknown): void;
88
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/packager-tool-flow",
3
- "version": "1.201.0-preview.133",
3
+ "version": "1.202.0-preview.134",
4
4
  "description": "UiPath Flow tool implementation",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,13 +25,13 @@
25
25
  "author": "",
26
26
  "license": "ISC",
27
27
  "peerDependencies": {
28
- "@uipath/filesystem": "1.201.0",
29
- "@uipath/flow-converter": "^0.53.6",
30
- "@uipath/flow-core": "^0.92.5",
31
- "@uipath/flow-migrations": "^0.33.0",
32
- "@uipath/flow-schema": "^0.57.6",
33
- "@uipath/solutionpackager-tool-core": "1.201.0",
28
+ "@uipath/filesystem": "1.202.0",
29
+ "@uipath/flow-converter": "0.55.0-develop",
30
+ "@uipath/flow-core": "0.96.0-develop",
31
+ "@uipath/flow-migrations": "0.45.0-develop",
32
+ "@uipath/flow-schema": "0.57.4-develop",
33
+ "@uipath/solutionpackager-tool-core": "1.202.0",
34
34
  "@uipath/tool-agent": "^2.0.0"
35
35
  },
36
- "gitHead": "7933378ad5276ac369900293a1447474dcec6826"
36
+ "gitHead": "a335728adbdb02f28308e4f55d8936d0b150444b"
37
37
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Encoding-agnostic readers for the inline-agent input encoding (MST-12020).
3
+ *
4
+ * flow-workbench writes inline-agent input names in one of two schemes, gated by its
5
+ * `services.agent-storage.nested-agent-input-schema` feature flag:
6
+ * - FLAT (shipped default): one top-level `inputSchema` property per input, `__`-joined
7
+ * (`script1__output__query`), prompts read `{{input.script1__output__query}}`.
8
+ * - NESTED (flag on): a nested object tree whose auto-created container nodes carry
9
+ * `x-uipath-container: true`, prompts read `{{input.script1.output.query}}` (the runtime
10
+ * template engine treats `.` as navigation).
11
+ *
12
+ * The CLI must read BOTH — an agent.json is whatever the authoring canvas last wrote — so these
13
+ * helpers detect per file instead of consuming any flag. Detection is strictly the per-node
14
+ * container marker: an object property WITHOUT the marker is a user-declared whole-object input
15
+ * under either encoding and must be treated as one leaf, never unfolded.
16
+ *
17
+ * TODO: replace with flow-workbench's canonical helpers (`collectInlineAgentInputsFromAgent`,
18
+ * `decodeFlatName`, `getContainerChildProps`) once they are re-exported from
19
+ * `@uipath/flow-converter` — that package is the single source of truth for the encoding.
20
+ */
21
+
22
+ /** Marker flow-workbench stamps on auto-created container nodes of a NESTED inputSchema. */
23
+ export const SCHEMA_CONTAINER_MARKER = "x-uipath-container";
24
+
25
+ /** One bindable input leaf of an agent `inputSchema`, in both name forms. */
26
+ export interface AgentInputLeaf {
27
+ /** Flat `__`-joined name — what a flat prompt token / flat JobArgument uses. */
28
+ flatKey: string;
29
+ /** Canonical dotted path — what a nested prompt token navigates. */
30
+ path: string;
31
+ /** Declared `type` of the leaf node, when present. */
32
+ type?: string;
33
+ }
34
+
35
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
36
+ typeof value === "object" && value !== null && !Array.isArray(value);
37
+
38
+ /** Children of a MARKED container node; `undefined` for leaves and declared whole-object inputs. */
39
+ function containerChildren(prop: unknown): Record<string, unknown> | undefined {
40
+ if (!isRecord(prop)) return undefined;
41
+ if (prop[SCHEMA_CONTAINER_MARKER] !== true) return undefined;
42
+ const children = prop.properties;
43
+ return isRecord(children) ? children : undefined;
44
+ }
45
+
46
+ /** Canonical dotted form of an input name from either encoding (`a__b` and `a.b` → `a.b`). */
47
+ export function canonicalAgentInputPath(name: string): string {
48
+ return name.split("__").join(".");
49
+ }
50
+
51
+ /**
52
+ * Enumerate the bindable input leaves of an agent `inputSchema`, descending MARKED containers
53
+ * (nested encoding) and passing flat / declared-object top-level keys through as single leaves.
54
+ * Returns `[]` for an absent/empty/malformed schema.
55
+ */
56
+ export function collectAgentInputLeaves(
57
+ inputSchema: unknown,
58
+ ): AgentInputLeaf[] {
59
+ const properties = isRecord(inputSchema)
60
+ ? inputSchema.properties
61
+ : undefined;
62
+ if (!isRecord(properties)) return [];
63
+
64
+ const leaves: AgentInputLeaf[] = [];
65
+ const walk = (prop: unknown, segments: string[]): void => {
66
+ const children = containerChildren(prop);
67
+ if (!children) {
68
+ // Flat keys already carry their whole path in one segment; keep the
69
+ // stored spelling for flatKey and canonicalize for the dotted path.
70
+ const flatKey = segments.join("__");
71
+ leaves.push({
72
+ flatKey,
73
+ path: canonicalAgentInputPath(segments.join(".")),
74
+ type:
75
+ isRecord(prop) && typeof prop.type === "string"
76
+ ? prop.type
77
+ : undefined,
78
+ });
79
+ return;
80
+ }
81
+ for (const [key, child] of Object.entries(children)) {
82
+ walk(child, [...segments, key]);
83
+ }
84
+ };
85
+ for (const [key, prop] of Object.entries(properties)) {
86
+ walk(prop, [key]);
87
+ }
88
+ return leaves;
89
+ }
90
+
91
+ /**
92
+ * Source binding expression for one input leaf — the inverse of flow-workbench's encode:
93
+ * a `metadata`-rooted path binds `$metadata.*`; everything else binds `$vars.*`.
94
+ */
95
+ export function leafToBinding(leaf: AgentInputLeaf): string {
96
+ const segments = leaf.path.split(".");
97
+ if (segments[0] === "metadata") {
98
+ // A whole-object `metadata` leaf (unmarked object) has no trailing
99
+ // segments — bind the root `$metadata`, not an invalid `$metadata.`.
100
+ const rest = segments.slice(1).join(".");
101
+ return rest ? `=$metadata.${rest}` : "=$metadata";
102
+ }
103
+ return `=$vars.${leaf.path}`;
104
+ }
package/src/agents.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ healResourceInputEncoding,
3
+ normalizeAgentInputEncoding,
4
+ } from "@uipath/flow-converter";
1
5
  import type {
2
6
  IFileSystem,
3
7
  IToolLogger,
@@ -16,7 +20,9 @@ export interface InlineAgentContract {
16
20
 
17
21
  /**
18
22
  * Build the execution contract for one inline agent with the same migration,
19
- * validation, and binding pipeline used by normal solution packaging.
23
+ * validation, and binding pipeline used by normal solution packaging. The
24
+ * returned `agentJson` is healed to the flat input encoding (MST-12020) — see
25
+ * {@link healAgentInputEncoding}.
20
26
  */
21
27
  export async function buildInlineAgentContract(config: {
22
28
  fileSystem: IFileSystem;
@@ -30,7 +36,9 @@ export async function buildInlineAgentContract(config: {
30
36
  });
31
37
 
32
38
  return {
33
- agentJson: built.agent as Record<string, unknown>,
39
+ agentJson: healAgentInputEncoding(
40
+ built.agent as Record<string, unknown>,
41
+ ),
34
42
  bindingsJson: {
35
43
  version: built.bindings.version,
36
44
  resources: built.bindings.resources as unknown as Record<
@@ -41,6 +49,37 @@ export async function buildInlineAgentContract(config: {
41
49
  };
42
50
  }
43
51
 
52
+ /**
53
+ * Normalize a built inline-agent contract to the FLAT input encoding (MST-12020) —
54
+ * the one heal every packaging path shares.
55
+ *
56
+ * The sidecar `agent.json` is whatever the authoring canvas last wrote: flat `__`
57
+ * names, or — behind flow-workbench's `nested-agent-input-schema` flag — a nested
58
+ * `x-uipath-container` tree with dotted prompt tokens. Flattens both halves: prompts
59
+ * + `inputSchema` via the agent heal, and each embedded resource's `argumentPath`
60
+ * via the resource heal. Applied by `buildInlineAgentContract` (flow-tool's
61
+ * `flow pack`/`publish`) and by `processAgents` (solution packaging). Defaults to
62
+ * 'flat', idempotent.
63
+ */
64
+ function healAgentInputEncoding(
65
+ agent: Record<string, unknown>,
66
+ ): Record<string, unknown> {
67
+ const normalized = normalizeAgentInputEncoding(agent);
68
+ const rawResources = normalized.resources;
69
+ const resources = Array.isArray(rawResources)
70
+ ? rawResources.map((resource) =>
71
+ resource &&
72
+ typeof resource === "object" &&
73
+ !Array.isArray(resource)
74
+ ? healResourceInputEncoding(
75
+ resource as Record<string, unknown>,
76
+ )
77
+ : resource,
78
+ )
79
+ : rawResources;
80
+ return { ...normalized, resources };
81
+ }
82
+
44
83
  /**
45
84
  * Build every root-level agent directory into the content folder and merge
46
85
  * their binding resources. Returns each built agent.json keyed by its
@@ -77,8 +116,18 @@ export const processAgents = async (
77
116
  outputPath: outputDir,
78
117
  logger,
79
118
  });
119
+ // buildAgent wrote the sidecar's encoding verbatim; normalize the deploy
120
+ // artifact to flat (MST-12020), matching `flow pack` / `publish`.
121
+ let agentJson = result.agent as Record<string, unknown>;
122
+ if (agentJson) {
123
+ agentJson = healAgentInputEncoding(agentJson);
124
+ await fileSystem.writeFile(
125
+ Path.join(outputDir, "agent.json"),
126
+ JSON.stringify(agentJson, null, 2),
127
+ );
128
+ }
80
129
  agentResults.push(result);
81
- builtAgents.set(entry, result.agent as Record<string, unknown>);
130
+ builtAgents.set(entry, agentJson);
82
131
  }
83
132
 
84
133
  const agentBindingResources = agentResults.flatMap(toBindingsResources);
package/src/flow-tool.ts CHANGED
@@ -40,6 +40,7 @@ import { ensureProcessBindings } from "./ensure-process-bindings.js";
40
40
  import { replaceExporterVersion } from "./exporter-version.js";
41
41
  import { readFlowWorkflow } from "./flow-io.js";
42
42
  import {
43
+ hydrateInlineAgentInputVariables,
43
44
  isInlineAgentNodeType,
44
45
  readInlineAgentSource,
45
46
  setPublishIntentOnInlineAgents,
@@ -273,6 +274,19 @@ export class FlowTool extends ProjectTool {
273
274
  return;
274
275
  }
275
276
 
277
+ // Reconcile each inline agent's `agentInputVariables` from its
278
+ // agent.json `inputSchema` before conversion. Studio-Web-authored
279
+ // flows write the node with `agentInputVariables: []` (SW assembles
280
+ // them in memory), and the converter derives `JobArguments` from that
281
+ // node array — so without this the agent packs with
282
+ // `JobArguments = {"input":""}` and every bound input is dropped
283
+ // (UV-15890 / UV-15979). Mirrors the debug path.
284
+ await this.hydrateInlineAgentInputs(
285
+ workflow,
286
+ builtAgents,
287
+ contentFolder,
288
+ );
289
+
276
290
  // Convert .flow to BPMN from the resolved file format, so any $ref
277
291
  // content is inlined before the converter sees it.
278
292
  // For pack/publish, set __packageIntent = "Publish" on inline agent
@@ -459,6 +473,45 @@ export class FlowTool extends ProjectTool {
459
473
  return voiceAgents;
460
474
  }
461
475
 
476
+ /**
477
+ * Reconcile each inline-agent node's `agentInputVariables` from its
478
+ * agent's `inputSchema` before conversion. Reads the schema from the
479
+ * already-built agent (`builtAgents`) first, falling back to an
480
+ * `agent.json` copied under `content/<source>/`. Mutates `workflow.nodes`
481
+ * in place; best-effort, so a missing/unreadable agent leaves the node
482
+ * as-is.
483
+ */
484
+ private async hydrateInlineAgentInputs(
485
+ workflow: Workflow,
486
+ builtAgents: ReadonlyMap<string, Record<string, unknown>>,
487
+ contentFolder: string,
488
+ ): Promise<void> {
489
+ const nodes = (workflow.nodes ?? []) as Array<{
490
+ type?: string;
491
+ inputs?: Record<string, unknown>;
492
+ model?: Record<string, unknown>;
493
+ }>;
494
+ await hydrateInlineAgentInputVariables(
495
+ nodes,
496
+ async (source) => {
497
+ const agent =
498
+ builtAgents.get(source) ??
499
+ (await this.readCopiedAgentJson(contentFolder, source));
500
+ return agent?.inputSchema;
501
+ },
502
+ (source, added) =>
503
+ this.logger.info(
504
+ `Reconciled ${added} inline-agent input(s) for "${source}" from agent.json inputSchema`,
505
+ ),
506
+ (source, reason) =>
507
+ this.logger.warn(
508
+ reason === "invalid-source"
509
+ ? `Inline agent "${source}" is not a bare project-id folder, so its inputs were not reconciled and packaging skips it — the agent would ship with no bound inputs.`
510
+ : `Could not read an inputSchema for inline agent "${source}", so its inputs were not reconciled — the agent would ship with no bound inputs. Check that ${source}/agent.json exists and parses.`,
511
+ ),
512
+ );
513
+ }
514
+
462
515
  /** Read an `agent.json` copied in without going through `buildAgent`. */
463
516
  private async readCopiedAgentJson(
464
517
  contentFolder: string,
package/src/index.ts CHANGED
@@ -8,6 +8,13 @@
8
8
  import { toolsFactoryRepository } from "@uipath/solutionpackager-tool-core";
9
9
  import { FlowToolFactory } from "./flow-tool-factory.js";
10
10
 
11
+ export {
12
+ type AgentInputLeaf,
13
+ canonicalAgentInputPath,
14
+ collectAgentInputLeaves,
15
+ leafToBinding,
16
+ SCHEMA_CONTAINER_MARKER,
17
+ } from "./agent-input-encoding-utils.js";
11
18
  export {
12
19
  buildInlineAgentContract,
13
20
  type InlineAgentContract,
@@ -24,8 +31,11 @@ export {
24
31
  export { FlowTool } from "./flow-tool.js";
25
32
  export { FlowToolFactory } from "./flow-tool-factory.js";
26
33
  export {
34
+ type AgentInputVariable,
35
+ hydrateInlineAgentInputVariables,
27
36
  isInlineAgentNodeType,
28
37
  readInlineAgentSource,
38
+ reconcileInlineAgentInputVariables,
29
39
  setPublishIntentOnInlineAgents,
30
40
  } from "./inline-agent-utils.js";
31
41
  export { operateRuntimeOptions } from "./operate-runtime-options.js";
@@ -1,4 +1,9 @@
1
- import { isAgentNodeType } from "@uipath/flow-schema";
1
+ import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
2
+ import {
3
+ canonicalAgentInputPath,
4
+ collectAgentInputLeaves,
5
+ leafToBinding,
6
+ } from "./agent-input-encoding-utils.js";
2
7
 
3
8
  /**
4
9
  * Resolve the inline-agent's projectId UUID from a flow node. Falls back to
@@ -29,6 +34,147 @@ export function isInlineAgentNodeType(type: string | undefined): boolean {
29
34
  return isAgentNodeType(type);
30
35
  }
31
36
 
37
+ /** One flow-node `agentInputVariables[]` entry. */
38
+ export interface AgentInputVariable {
39
+ id?: string;
40
+ type?: string;
41
+ binding?: string;
42
+ description?: string;
43
+ }
44
+
45
+ /**
46
+ * Reconcile a node's existing `agentInputVariables` against an agent's
47
+ * `inputSchema` — the input contract the runtime actually binds.
48
+ *
49
+ * Adds a derived entry for every input leaf the node is missing and preserves
50
+ * every entry the node already declares, so a custom or divergent binding is
51
+ * never clobbered. Leaves come from {@link collectAgentInputLeaves}, so a
52
+ * nested schema descends its marked containers rather than being read as one
53
+ * opaque top-level key.
54
+ *
55
+ * Comparison is on the canonical dotted path, so a node that declares the
56
+ * nested spelling (`a.b`) is not re-added under the flat one (`a__b`). The
57
+ * derived id is always the FLAT spelling: the packager heals the shipped
58
+ * agent.json prompts and schema to the flat encoding, and the converter emits
59
+ * each id verbatim as a JobArgument name, so flat ids are what match the
60
+ * healed `{{input.<flat>}}` tokens for both schema encodings.
61
+ *
62
+ * Returns the reconciled list plus how many entries were added; when nothing
63
+ * is missing (or the schema is empty) returns the original array unchanged so
64
+ * callers can skip a spurious mutation.
65
+ */
66
+ export function reconcileInlineAgentInputVariables(
67
+ existing: AgentInputVariable[],
68
+ inputSchema: unknown,
69
+ ): { variables: AgentInputVariable[]; added: number } {
70
+ const leaves = collectAgentInputLeaves(inputSchema);
71
+ if (leaves.length === 0) {
72
+ return { variables: existing, added: 0 };
73
+ }
74
+ const existingPaths = new Set(
75
+ existing
76
+ .map((e) => e.id)
77
+ .filter((id): id is string => typeof id === "string")
78
+ .map(canonicalAgentInputPath),
79
+ );
80
+ const additions = leaves
81
+ .filter((leaf) => !existingPaths.has(leaf.path))
82
+ .map((leaf) => {
83
+ const binding = leafToBinding(leaf);
84
+ return {
85
+ id: leaf.flatKey,
86
+ type: leaf.type ?? "string",
87
+ binding,
88
+ // binding is "=$vars.X" / "=$metadata.X"; drop the "="
89
+ description: `Bound from ${binding.slice(1)}`,
90
+ };
91
+ });
92
+ if (additions.length === 0) {
93
+ return { variables: existing, added: 0 };
94
+ }
95
+ return { variables: [...existing, ...additions], added: additions.length };
96
+ }
97
+
98
+ /** Minimal shape of a flow node needed to hydrate inline-agent inputs. */
99
+ interface InlineAgentNodeLike {
100
+ type?: string;
101
+ inputs?: Record<string, unknown>;
102
+ model?: Record<string, unknown>;
103
+ }
104
+
105
+ /**
106
+ * Reconcile every inline-agent node's `agentInputVariables` against its
107
+ * agent's `inputSchema` before BPMN conversion. Mutates `nodes` in place.
108
+ *
109
+ * Studio Web keeps the input binding in the agent's `inputSchema` and rebuilds
110
+ * the node's `agentInputVariables` from it in memory on load; the `.flow` it
111
+ * writes carries an empty `agentInputVariables`. The converter derives the
112
+ * agent's `JobArguments` from `agentInputVariables` and never reads
113
+ * `agent.json`, so an SW-authored flow would otherwise convert to an agent with
114
+ * `JobArguments = {"input":""}` — works in SW, drops every bound input on
115
+ * `flow pack`, `solution pack` and `debug`. Mirroring SW here fills the gap.
116
+ *
117
+ * `resolveInputSchema` returns the agent's `inputSchema` for a given `source`,
118
+ * or `undefined` when it cannot be resolved. Best-effort by design: an
119
+ * unresolvable agent leaves its node as-is rather than failing the build.
120
+ *
121
+ * `onUnresolved` fires for a node this function could NOT hydrate, so callers
122
+ * can say so. Without it the empty-`JobArguments` bug this exists to fix comes
123
+ * back silently: the node keeps `agentInputVariables: []`, the converter emits
124
+ * the `{"input":""}` floor, and nothing in the output explains why. It does not
125
+ * fire for a node that needed nothing — an agent declaring no inputs, or one
126
+ * whose bindings the node already covers, is not a problem.
127
+ *
128
+ * Voice agents are hydrated but never reported: their inputs are authored on
129
+ * the node (`callContext`, explicit `agentInputVariables`) rather than bound
130
+ * from a schema, so an unresolved schema is the normal case and packaging
131
+ * already warns separately about a voice agent's embedded definition.
132
+ */
133
+ export async function hydrateInlineAgentInputVariables(
134
+ nodes: InlineAgentNodeLike[],
135
+ resolveInputSchema: (source: string) => Promise<unknown>,
136
+ onReconcile?: (source: string, added: number) => void,
137
+ onUnresolved?: (
138
+ source: string,
139
+ reason: "invalid-source" | "schema-unavailable",
140
+ ) => void,
141
+ ): Promise<void> {
142
+ for (const node of nodes) {
143
+ if (!isInlineAgentNodeType(node.type)) continue;
144
+ const source = readInlineAgentSource(node);
145
+ if (!source) continue;
146
+ // Same guard as inline-agent packaging: a `source` is a bare UUID
147
+ // folder name. Skip anything with `/`, `\`, or `.` so a traversing or
148
+ // divergent source cannot read an agent.json outside the packageable
149
+ // folder — packaging skips these too, so reconciling them would mutate
150
+ // inputs for an agent that will not ship.
151
+ const reportable = !isVoiceAgentNodeType(node.type);
152
+ if (/[/\\.]/.test(source)) {
153
+ if (reportable) onUnresolved?.(source, "invalid-source");
154
+ continue;
155
+ }
156
+
157
+ const inputSchema = await resolveInputSchema(source);
158
+ if (inputSchema === undefined) {
159
+ if (reportable) onUnresolved?.(source, "schema-unavailable");
160
+ continue;
161
+ }
162
+
163
+ const existing = Array.isArray(node.inputs?.agentInputVariables)
164
+ ? (node.inputs.agentInputVariables as AgentInputVariable[])
165
+ : [];
166
+ const { variables, added } = reconcileInlineAgentInputVariables(
167
+ existing,
168
+ inputSchema,
169
+ );
170
+ if (added === 0) continue;
171
+
172
+ if (!node.inputs) node.inputs = {};
173
+ node.inputs.agentInputVariables = variables;
174
+ onReconcile?.(source, added);
175
+ }
176
+ }
177
+
32
178
  /**
33
179
  * Set `__packageIntent = "Publish"` on inline agent definitions in a
34
180
  * file-format workflow so the BPMN converter emits "content/" prefixed