@uipath/packager-tool-flow 0.0.18 → 0.0.20

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,15 @@
1
+ import type { Workflow } from "@uipath/flow-core";
2
+ import type { IToolLogger } from "@uipath/solutionpackager-tool-core";
3
+ /**
4
+ * Safety net: ensure every process/agent node in the workflow has corresponding
5
+ * entries in the workflow `bindings` array and that `<bindings.*>` placeholders
6
+ * in model.context are resolved to `=bindings.<id>` references.
7
+ *
8
+ * This handles flows authored outside the CLI (e.g. direct file edits or
9
+ * skill-generated files) where `addNodeToFlow` was never called, so the
10
+ * binding creation / resolution step was skipped.
11
+ *
12
+ * Mutates the workflow in place. Safe to call multiple times — already-resolved
13
+ * nodes are skipped.
14
+ */
15
+ export declare function ensureProcessBindings(workflow: Workflow, logger: IToolLogger): number;
@@ -0,0 +1,27 @@
1
+ import { type IFileSystem } from "@uipath/filesystem";
2
+ import { type Workflow } from "@uipath/flow-schema";
3
+ export type FlowIoLogger = {
4
+ warn: (message: string) => void;
5
+ };
6
+ export interface ReadFlowWorkflowOptions {
7
+ /** Filesystem to read from. Defaults to `getFileSystem()`. */
8
+ fs?: IFileSystem;
9
+ /** Logger for non-fatal `$ref` resolution failures. */
10
+ logger?: FlowIoLogger;
11
+ }
12
+ /**
13
+ * Read a `.flow` file, resolve any `$ref` chunk references, and convert
14
+ * to an in-memory Workflow.
15
+ *
16
+ * Flow files may be split across sibling chunk files (layout.json,
17
+ * shared-vars.json, subflows/) via JSON `$ref` objects. These must be
18
+ * resolved before calling the schema validator, since unknown keys
19
+ * (including `$ref`) are stripped at parse time. Missing ref targets
20
+ * log a warning (fail-soft) and resolve to `null`; the resulting
21
+ * workflow may be partial.
22
+ *
23
+ * Pass `options.fs` when calling from an environment that injects its
24
+ * own filesystem (e.g., a packaging tool bundled for the browser);
25
+ * otherwise the process-wide `getFileSystem()` is used.
26
+ */
27
+ export declare function readFlowWorkflow(filePath: string, options?: ReadFlowWorkflowOptions): Promise<Workflow>;
@@ -11,6 +11,14 @@ export declare class FlowTool extends ProjectTool {
11
11
  buildAsync(options: IProjectBuildOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
12
12
  packAsync(options: IProjectPackOptions, cancellationToken?: AbortSignal): Promise<ToolResult>;
13
13
  dispose(): Promise<void>;
14
+ /**
15
+ * Read the .flow file from the content folder, resolve any unresolved
16
+ * process bindings, and write the updated workflow back.
17
+ *
18
+ * This is the solution pack safety net — flows authored outside the CLI
19
+ * may have `<bindings.*>` placeholders that need resolving before packaging.
20
+ */
21
+ private resolveProcessBindings;
14
22
  /**
15
23
  * Read the .flow file from the content folder, convert to BPMN, and
16
24
  * generate entry-points.json, bindings_v2.json, and the BPMN file.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export { type FlowIoLogger, readFlowWorkflow } from "./flow-io.js";
1
2
  export { FlowTool } from "./flow-tool.js";
2
3
  export { FlowToolFactory } from "./flow-tool-factory.js";
4
+ export { setPublishIntentOnInlineAgents } from "./inline-agent-utils.js";
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  generateOperateJson,
15
15
  getBindingResources,
16
16
  getEntryPoints,
17
+ inMemoryWorkflowToFileFormat,
17
18
  ProjectType
18
19
  } from "@uipath/flow-schema";
19
20
  import {
@@ -92,6 +93,162 @@ var processAgents = async (fileSystem, logger, projectPath, contentFolder) => {
92
93
  }
93
94
  };
94
95
 
96
+ // src/ensure-process-bindings.ts
97
+ import { addBinding, createBinding } from "@uipath/flow-core";
98
+ var PROCESS_NODE_PREFIXES = [
99
+ "uipath.core.rpa-workflow.",
100
+ "uipath.agent.resource.tool.process.",
101
+ "uipath.core.agent.",
102
+ "uipath.core.api-workflow."
103
+ ];
104
+ var UNRESOLVED_BINDING_PATTERN = /^<bindings\.\w+>$/;
105
+ function isProcessNode(nodeType) {
106
+ return PROCESS_NODE_PREFIXES.some((prefix) => nodeType?.startsWith(prefix));
107
+ }
108
+ function extractProcessGuid(nodeType) {
109
+ const match = nodeType.match(/^(?:uipath\.core\.rpa-workflow|uipath\.agent\.resource\.tool\.process|uipath\.core\.agent|uipath\.core\.api-workflow)\.([0-9a-f-]+)$/i);
110
+ if (!match) {
111
+ throw new Error(`Invalid process node type: "${nodeType}". ` + "Expected a known prefix followed by a process GUID.");
112
+ }
113
+ return match[1];
114
+ }
115
+ function createProcessBindings(manifest) {
116
+ const processGuid = extractProcessGuid(manifest.nodeType);
117
+ const model = manifest.model;
118
+ const processName = model?.bindings?.values?.name ?? "";
119
+ const folderPath = model?.bindings?.values?.folderPath;
120
+ if (folderPath == null) {
121
+ throw new Error("Manifest is missing model.bindings.values.folderPath. " + "Cannot create process bindings without a folder path.");
122
+ }
123
+ const subType = model?.bindings?.resourceSubType ?? "Process";
124
+ const resourceKey = model?.bindings?.resourceKey ?? processGuid;
125
+ const nameBinding = createBinding({
126
+ name: "name",
127
+ value: processName,
128
+ resource: "process",
129
+ resourceKey,
130
+ propertyAttribute: "name",
131
+ resourceSubType: subType
132
+ });
133
+ const folderBinding = createBinding({
134
+ name: "folderPath",
135
+ value: folderPath,
136
+ resource: "process",
137
+ resourceKey,
138
+ propertyAttribute: "folderPath",
139
+ resourceSubType: subType
140
+ });
141
+ if (folderPath === "" && folderBinding.default === undefined) {
142
+ folderBinding.default = "";
143
+ }
144
+ return { nameBinding, folderBinding };
145
+ }
146
+ function resolveContextPlaceholders(context, storedName, storedFolder) {
147
+ if (!context)
148
+ return;
149
+ for (const entry of context) {
150
+ if (entry.name === "name" && typeof entry.value === "string" && UNRESOLVED_BINDING_PATTERN.test(entry.value)) {
151
+ entry.default = storedName.default;
152
+ entry.value = `=bindings.${storedName.id}`;
153
+ } else if (entry.name === "folderPath" && typeof entry.value === "string" && UNRESOLVED_BINDING_PATTERN.test(entry.value)) {
154
+ entry.default = storedFolder.default;
155
+ entry.value = `=bindings.${storedFolder.id}`;
156
+ }
157
+ }
158
+ }
159
+ function ensureProcessBindings(workflow, logger) {
160
+ const nodes = workflow.nodes ?? [];
161
+ const definitions = workflow.definitions ?? [];
162
+ let bindingsCreated = 0;
163
+ for (const node of nodes) {
164
+ if (!isProcessNode(node.type))
165
+ continue;
166
+ const nodeModel = node.model;
167
+ const defModel = definitions.find((d) => d.nodeType === node.type)?.model;
168
+ const hasUnresolved = [nodeModel, defModel].some((m) => m?.context?.some((entry) => typeof entry.value === "string" && UNRESOLVED_BINDING_PATTERN.test(entry.value)));
169
+ if (!hasUnresolved)
170
+ continue;
171
+ const definition = definitions.find((d) => d.nodeType === node.type);
172
+ if (!definition)
173
+ continue;
174
+ let nameBinding;
175
+ let folderBinding;
176
+ try {
177
+ ({ nameBinding, folderBinding } = createProcessBindings(definition));
178
+ } catch (err) {
179
+ logger.warn(`Skipping binding resolution for node "${node.id}": ${err instanceof Error ? err.message : String(err)}`);
180
+ continue;
181
+ }
182
+ const beforeCount = workflow.bindings?.length ?? 0;
183
+ addBinding(workflow, nameBinding);
184
+ addBinding(workflow, folderBinding);
185
+ const storedBindings = workflow.bindings;
186
+ const storedName = storedBindings.find((b) => b.resourceKey === nameBinding.resourceKey && b.propertyAttribute === nameBinding.propertyAttribute) ?? nameBinding;
187
+ const storedFolder = storedBindings.find((b) => b.resourceKey === folderBinding.resourceKey && b.propertyAttribute === folderBinding.propertyAttribute) ?? folderBinding;
188
+ resolveContextPlaceholders(nodeModel?.context, storedName, storedFolder);
189
+ resolveContextPlaceholders(defModel?.context, storedName, storedFolder);
190
+ const added = (workflow.bindings?.length ?? 0) - beforeCount;
191
+ bindingsCreated += added;
192
+ logger.info(`Resolved process bindings for node "${node.id}" (${node.type})`);
193
+ }
194
+ if (bindingsCreated > 0) {
195
+ logger.info(`ensureProcessBindings: created ${bindingsCreated} binding(s) for directly-authored nodes`);
196
+ }
197
+ return bindingsCreated;
198
+ }
199
+
200
+ // src/flow-io.ts
201
+ import { getFileSystem } from "@uipath/filesystem";
202
+ import { resolveAndConvertWorkflow } from "@uipath/flow-schema";
203
+ async function readFlowWorkflow(filePath, options = {}) {
204
+ const fs = options.fs ?? getFileSystem();
205
+ const logger = options.logger;
206
+ const raw = JSON.parse(await readUtf8(fs, filePath));
207
+ const resolver = async (refPath, baseUri) => {
208
+ const resolvedPath = fs.path.resolve(fs.path.dirname(baseUri), refPath);
209
+ const refContent = await readUtf8OrNull(fs, resolvedPath);
210
+ if (refContent === null) {
211
+ logger?.warn(`Failed to resolve $ref "${refPath}": file not found at ${resolvedPath}`);
212
+ return null;
213
+ }
214
+ return JSON.parse(refContent);
215
+ };
216
+ const { workflow } = await resolveAndConvertWorkflow(raw, resolver, {
217
+ baseUri: filePath
218
+ });
219
+ return workflow;
220
+ }
221
+ async function readUtf8(fs, filePath) {
222
+ const content = await readUtf8OrNull(fs, filePath);
223
+ if (content === null) {
224
+ throw new Error(`Could not read flow file: ${filePath}`);
225
+ }
226
+ return content;
227
+ }
228
+ async function readUtf8OrNull(fs, filePath) {
229
+ const buffer = await fs.readFile(filePath);
230
+ if (buffer === null)
231
+ return null;
232
+ return typeof buffer === "string" ? buffer : new TextDecoder().decode(buffer);
233
+ }
234
+
235
+ // src/inline-agent-utils.ts
236
+ var INLINE_AGENT_DEF_TYPES = [
237
+ "uipath.agent.autonomous",
238
+ "uipath.agent.conversational"
239
+ ];
240
+ function setPublishIntentOnInlineAgents(fileFormat) {
241
+ const ff = fileFormat;
242
+ for (const def of ff.definitions ?? []) {
243
+ const d = def;
244
+ if (INLINE_AGENT_DEF_TYPES.includes(d.nodeType)) {
245
+ const model = d.model ?? {};
246
+ model.__packageIntent = "Publish";
247
+ d.model = model;
248
+ }
249
+ }
250
+ }
251
+
95
252
  // src/flow-tool.ts
96
253
  var FlowConstants = {
97
254
  EntryPointsFileName: "entry-points.json",
@@ -121,6 +278,8 @@ class FlowTool extends ProjectTool {
121
278
  await this.copyProjectFiles(options.projectPath, contentFolder);
122
279
  this.logger.progress("Processing agents...");
123
280
  await processAgents(this.fileSystem, this.logger, options.projectPath, contentFolder);
281
+ this.logger.progress("Resolving process bindings...");
282
+ await this.resolveProcessBindings(contentFolder);
124
283
  this.logger.progress("Generating packaging artifacts from .flow...");
125
284
  await this.generateFlowPackagingArtifacts(contentFolder, options.projectStorageId ?? "");
126
285
  this.logger.progress("Creating operate.json file...");
@@ -164,6 +323,27 @@ class FlowTool extends ProjectTool {
164
323
  await this._temporaryStorage.cleanup();
165
324
  } catch {}
166
325
  }
326
+ async resolveProcessBindings(contentFolder) {
327
+ const entries = await this.fileSystem.readdir(contentFolder);
328
+ const flowFile = entries.find((e) => e.endsWith(".flow"));
329
+ if (!flowFile)
330
+ return;
331
+ const flowPath = Path3.join(contentFolder, flowFile);
332
+ const flowBuffer = await this.fileSystem.readFile(flowPath);
333
+ if (!flowBuffer)
334
+ return;
335
+ const flowJson = typeof flowBuffer === "string" ? flowBuffer : new TextDecoder().decode(flowBuffer);
336
+ let workflow;
337
+ try {
338
+ workflow = JSON.parse(flowJson);
339
+ } catch {
340
+ return;
341
+ }
342
+ const count = ensureProcessBindings(workflow, this.logger);
343
+ if (count > 0) {
344
+ await this.fileSystem.writeFile(flowPath, JSON.stringify(workflow, null, 4));
345
+ }
346
+ }
167
347
  async generateFlowPackagingArtifacts(contentFolder, projectId) {
168
348
  const entries = await this.fileSystem.readdir(contentFolder);
169
349
  const flowFile = entries.find((e) => e.endsWith(".flow"));
@@ -171,50 +351,111 @@ class FlowTool extends ProjectTool {
171
351
  this.logger.warn("No .flow file found in content folder — skipping artifact generation");
172
352
  return;
173
353
  }
174
- const flowJsonBuffer = await this.fileSystem.readFile(Path3.join(contentFolder, flowFile));
175
- if (!flowJsonBuffer) {
176
- this.logger.warn(`Could not read ${flowFile} — skipping artifact generation`);
177
- return;
178
- }
179
- const flowJson = typeof flowJsonBuffer === "string" ? flowJsonBuffer : new TextDecoder().decode(flowJsonBuffer);
180
- let flowData;
354
+ const flowFilePath = Path3.join(contentFolder, flowFile);
355
+ let workflow;
181
356
  try {
182
- flowData = JSON.parse(flowJson);
183
- } catch {
184
- this.logger.warn(`Could not parse ${flowFile} as JSON — skipping artifact generation`);
357
+ workflow = await readFlowWorkflow(flowFilePath, {
358
+ fs: this.fileSystem,
359
+ logger: this.logger
360
+ });
361
+ } catch (err) {
362
+ const message = err instanceof Error ? err.message : String(err);
363
+ this.logger.warn(`Could not load ${flowFile} as a workflow — skipping artifact generation: ${message}`);
185
364
  return;
186
365
  }
187
366
  const bpmnFileName = flowFile.replace(/\.flow$/, ".bpmn");
188
367
  const bpmnPath = Path3.join(contentFolder, bpmnFileName);
189
- const { bpmn } = await convertFlowToBpmn(flowJson);
368
+ const fileFormat = inMemoryWorkflowToFileFormat(workflow);
369
+ setPublishIntentOnInlineAgents(fileFormat);
370
+ const resolvedFlowJson = JSON.stringify(fileFormat);
371
+ const { bpmn } = await convertFlowToBpmn(resolvedFlowJson);
190
372
  await this.fileSystem.writeFile(bpmnPath, bpmn);
191
- const packagingNodes = (flowData.nodes ?? []).map((node) => ({
373
+ const packagingNodes = (workflow.nodes ?? []).map((node) => ({
192
374
  id: node.id,
193
375
  type: node.type,
194
- data: {
195
- type: node.type,
196
- typeVersion: node.typeVersion ?? "1.0.0",
197
- display: node.display,
198
- inputs: node.inputs,
199
- outputs: node.outputs,
200
- model: node.model
201
- }
376
+ typeVersion: node.typeVersion ?? "1.0",
377
+ display: node.display,
378
+ inputs: node.inputs,
379
+ outputs: node.outputs
202
380
  }));
203
- const variables = flowData.variables ?? {};
204
- const bindings = flowData.bindings ?? [];
381
+ const variables = workflow.variables ?? {};
382
+ const bindings = workflow.bindings ?? [];
383
+ const definitions = workflow.definitions ?? [];
205
384
  const entryPointsPath = Path3.join(contentFolder, FlowConstants.EntryPointsFileName);
206
- const entryPoints = getEntryPoints(bpmnFileName, packagingNodes, variables, ProjectType.Flow);
207
- await this.fileSystem.writeFile(entryPointsPath, `${JSON.stringify(generateEntryPointsJson(entryPoints), null, 2)}
385
+ const entryPoints = getEntryPoints(bpmnFileName, packagingNodes, variables, definitions, ProjectType.Flow);
386
+ const INLINE_AGENT_TYPES = [
387
+ "uipath.agent.autonomous",
388
+ "uipath.agent.conversational"
389
+ ];
390
+ const readSource = (n) => {
391
+ const inputs = n.inputs;
392
+ const model = n.model;
393
+ const candidates = [
394
+ inputs?.source,
395
+ model?.source,
396
+ inputs?.agentProjectId,
397
+ model?.agentProjectId
398
+ ];
399
+ for (const c of candidates) {
400
+ if (typeof c === "string" && c.length > 0)
401
+ return c;
402
+ }
403
+ return;
404
+ };
405
+ const agentEntryPoints = [];
406
+ const seenSources = new Set;
407
+ for (const node of workflow.nodes ?? []) {
408
+ const n = node;
409
+ if (!INLINE_AGENT_TYPES.includes(n.type))
410
+ continue;
411
+ const source = readSource(n);
412
+ if (!source)
413
+ continue;
414
+ if (seenSources.has(source))
415
+ continue;
416
+ seenSources.add(source);
417
+ const agentJsonPath = Path3.join(contentFolder, source, "agent.json");
418
+ if (!await this.fileSystem.exists(agentJsonPath))
419
+ continue;
420
+ let agent = {};
421
+ try {
422
+ const raw = await this.fileSystem.readFile(agentJsonPath);
423
+ if (raw) {
424
+ const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
425
+ agent = JSON.parse(text);
426
+ }
427
+ } catch {
428
+ continue;
429
+ }
430
+ const inputSchema = agent.inputSchema ?? {
431
+ type: "object",
432
+ properties: {}
433
+ };
434
+ const outputSchema = agent.outputSchema ?? {
435
+ type: "object",
436
+ properties: {}
437
+ };
438
+ agentEntryPoints.push({
439
+ filePath: `content/${source}/agent.json`,
440
+ uniqueId: agent.projectId ?? source,
441
+ type: "agent",
442
+ input: inputSchema,
443
+ output: outputSchema,
444
+ displayName: agent.name ?? "Agent"
445
+ });
446
+ }
447
+ const allEntryPoints = [...entryPoints, ...agentEntryPoints];
448
+ await this.fileSystem.writeFile(entryPointsPath, `${JSON.stringify(generateEntryPointsJson(allEntryPoints), null, 2)}
208
449
  `);
209
450
  const bindingsPath = Path3.join(contentFolder, FlowConstants.BindingsV2FileName);
210
- const bindingResources = getBindingResources(packagingNodes, bindings);
451
+ const bindingResources = getBindingResources(packagingNodes, bindings, definitions);
211
452
  await this.fileSystem.writeFile(bindingsPath, `${JSON.stringify(generateBindingsJson(bindingResources), null, 2)}
212
453
  `);
213
454
  const operatePath = Path3.join(contentFolder, NugetConstants.OperateFileName);
214
455
  const startEventMatch = bpmn.match(/<bpmn:startEvent\s+id="([^"]+)"/);
215
456
  const startEventId = startEventMatch?.[1] ?? "start";
216
457
  const mainEntryPoint = `/${bpmnFileName}#${startEventId}`;
217
- await this.fileSystem.writeFile(operatePath, `${JSON.stringify(generateOperateJson(projectId || flowData.id || "", mainEntryPoint), null, 2)}
458
+ await this.fileSystem.writeFile(operatePath, `${JSON.stringify(generateOperateJson(projectId || workflow.id || "", mainEntryPoint), null, 2)}
218
459
  `);
219
460
  }
220
461
  async copyProjectFiles(projectPath, contentFolder) {
@@ -322,6 +563,8 @@ class FlowToolFactory {
322
563
  // src/index.ts
323
564
  toolsFactoryRepository.registerProjectToolFactory(new FlowToolFactory);
324
565
  export {
566
+ setPublishIntentOnInlineAgents,
567
+ readFlowWorkflow,
325
568
  FlowToolFactory,
326
569
  FlowTool
327
570
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set `__packageIntent = "Publish"` on inline agent definitions in a
3
+ * file-format workflow so the BPMN converter emits "content/" prefixed
4
+ * entry points.
5
+ *
6
+ * Mutates `fileFormat` in place.
7
+ */
8
+ export declare function setPublishIntentOnInlineAgents(fileFormat: unknown): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/packager-tool-flow",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "UiPath Flow tool implementation",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,23 +18,6 @@
18
18
  "registry": "https://registry.npmjs.org/"
19
19
  },
20
20
  "types": "./dist/index.d.ts",
21
- "scripts": {
22
- "build": "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-converter --external @uipath/flow-schema && tsc --emitDeclarationOnly --outDir dist",
23
- "dev": "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-converter --external @uipath/flow-schema --watch",
24
- "test": "vitest run",
25
- "test:browser": "vitest run --config=vitest.browser.config.ts",
26
- "test:coverage": "vitest run --coverage",
27
- "test:browser:coverage": "vitest run --config=vitest.browser.config.ts --coverage",
28
- "test:all": "bun run test && bun run test:browser",
29
- "test:all:coverage": "bun run test:coverage && bun run test:browser:coverage",
30
- "prepack": "bun run build",
31
- "publish:dry": "bun publish --dry-run",
32
- "publish:gh": "bun publish",
33
- "version:patch": "bun version patch --no-git-tag-version",
34
- "version:minor": "bun version minor --no-git-tag-version",
35
- "version:major": "bun version major --no-git-tag-version",
36
- "lint": "biome check ."
37
- },
38
21
  "files": [
39
22
  "dist",
40
23
  "!dist/**/*.map",
@@ -43,21 +26,25 @@
43
26
  "author": "",
44
27
  "license": "ISC",
45
28
  "peerDependencies": {
46
- "@uipath/flow-converter": "^0.1.6",
47
- "@uipath/flow-schema": "^0.2.141",
29
+ "@uipath/filesystem": "1.195.0",
30
+ "@uipath/flow-converter": "^0.1.273",
31
+ "@uipath/flow-core": "^0.2.376",
32
+ "@uipath/flow-schema": "^0.2.495",
48
33
  "@uipath/solutionpackager-tool-core": "0.0.33",
49
- "@uipath/tool-agent": "^1.0.1"
34
+ "@uipath/tool-agent": "^1.2.4"
50
35
  },
51
36
  "devDependencies": {
52
- "@types/node": "^25.5.0",
37
+ "@types/node": "^25.5.2",
38
+ "@uipath/filesystem": "1.195.0",
39
+ "@uipath/flow-core": "^0.2.376",
53
40
  "@uipath/solutionpackager-tool-core": "0.0.33",
54
- "@uipath/tool-agent": "^1.0.1",
55
- "@vitest/browser": "^4.0.14",
56
- "@vitest/browser-playwright": "^4.0.14",
57
- "@vitest/coverage-v8": "^4.0.14",
41
+ "@uipath/tool-agent": "^1.2.4",
42
+ "@vitest/browser": "^4.1.6",
43
+ "@vitest/browser-playwright": "^4.1.6",
44
+ "@vitest/coverage-v8": "^4.1.6",
58
45
  "playwright": "^1.57.0",
59
- "typescript": "^5.9.3",
60
- "vitest": "^4.0.14"
46
+ "typescript": "^6.0.2",
47
+ "vitest": "^4.1.6"
61
48
  },
62
- "gitHead": "3f1b4d8e9f910be81e4cab956537f21dbd5d63ac"
49
+ "gitHead": "ca8e2977762f231364386b40d976c61ca9ce87ca"
63
50
  }
@@ -0,0 +1,213 @@
1
+ import type { Binding, NodeInstance, Workflow } from "@uipath/flow-core";
2
+ import { addBinding, createBinding } from "@uipath/flow-core";
3
+ import type { IToolLogger } from "@uipath/solutionpackager-tool-core";
4
+
5
+ /**
6
+ * Node type prefixes that use process-style bindings (GUID as resourceKey).
7
+ * Covers RPA workflows, agent process tools, and agent nodes.
8
+ */
9
+ const PROCESS_NODE_PREFIXES = [
10
+ "uipath.core.rpa-workflow.",
11
+ "uipath.agent.resource.tool.process.",
12
+ "uipath.core.agent.",
13
+ "uipath.core.api-workflow.",
14
+ ];
15
+
16
+ const UNRESOLVED_BINDING_PATTERN = /^<bindings\.\w+>$/;
17
+
18
+ function isProcessNode(nodeType: string): boolean {
19
+ return PROCESS_NODE_PREFIXES.some((prefix) => nodeType?.startsWith(prefix));
20
+ }
21
+
22
+ function extractProcessGuid(nodeType: string): string {
23
+ const match = nodeType.match(
24
+ /^(?:uipath\.core\.rpa-workflow|uipath\.agent\.resource\.tool\.process|uipath\.core\.agent|uipath\.core\.api-workflow)\.([0-9a-f-]+)$/i,
25
+ );
26
+ if (!match) {
27
+ throw new Error(
28
+ `Invalid process node type: "${nodeType}". ` +
29
+ "Expected a known prefix followed by a process GUID.",
30
+ );
31
+ }
32
+ return match[1];
33
+ }
34
+
35
+ function createProcessBindings(manifest: {
36
+ nodeType: string;
37
+ model?: Record<string, unknown>;
38
+ }): { nameBinding: Binding; folderBinding: Binding } {
39
+ const processGuid = extractProcessGuid(manifest.nodeType);
40
+ const model = manifest.model as
41
+ | {
42
+ bindings?: {
43
+ resourceSubType?: string;
44
+ resourceKey?: string;
45
+ values?: { name?: string; folderPath?: string };
46
+ };
47
+ }
48
+ | undefined;
49
+ const processName = model?.bindings?.values?.name ?? "";
50
+ const folderPath = model?.bindings?.values?.folderPath;
51
+ if (folderPath == null) {
52
+ throw new Error(
53
+ "Manifest is missing model.bindings.values.folderPath. " +
54
+ "Cannot create process bindings without a folder path.",
55
+ );
56
+ }
57
+ const subType = model?.bindings?.resourceSubType ?? "Process";
58
+ const resourceKey = model?.bindings?.resourceKey ?? processGuid;
59
+
60
+ const nameBinding = createBinding({
61
+ name: "name",
62
+ value: processName,
63
+ resource: "process",
64
+ resourceKey,
65
+ propertyAttribute: "name",
66
+ resourceSubType: subType,
67
+ });
68
+ const folderBinding = createBinding({
69
+ name: "folderPath",
70
+ value: folderPath,
71
+ resource: "process",
72
+ resourceKey,
73
+ propertyAttribute: "folderPath",
74
+ resourceSubType: subType,
75
+ });
76
+ if (folderPath === "" && folderBinding.default === undefined) {
77
+ folderBinding.default = "";
78
+ }
79
+
80
+ return { nameBinding, folderBinding };
81
+ }
82
+
83
+ function resolveContextPlaceholders(
84
+ context: Array<Record<string, unknown>> | undefined,
85
+ storedName: Binding,
86
+ storedFolder: Binding,
87
+ ): void {
88
+ if (!context) return;
89
+ for (const entry of context) {
90
+ if (
91
+ entry.name === "name" &&
92
+ typeof entry.value === "string" &&
93
+ UNRESOLVED_BINDING_PATTERN.test(entry.value)
94
+ ) {
95
+ entry.default = storedName.default;
96
+ entry.value = `=bindings.${storedName.id}`;
97
+ } else if (
98
+ entry.name === "folderPath" &&
99
+ typeof entry.value === "string" &&
100
+ UNRESOLVED_BINDING_PATTERN.test(entry.value)
101
+ ) {
102
+ entry.default = storedFolder.default;
103
+ entry.value = `=bindings.${storedFolder.id}`;
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Safety net: ensure every process/agent node in the workflow has corresponding
110
+ * entries in the workflow `bindings` array and that `<bindings.*>` placeholders
111
+ * in model.context are resolved to `=bindings.<id>` references.
112
+ *
113
+ * This handles flows authored outside the CLI (e.g. direct file edits or
114
+ * skill-generated files) where `addNodeToFlow` was never called, so the
115
+ * binding creation / resolution step was skipped.
116
+ *
117
+ * Mutates the workflow in place. Safe to call multiple times — already-resolved
118
+ * nodes are skipped.
119
+ */
120
+ export function ensureProcessBindings(
121
+ workflow: Workflow,
122
+ logger: IToolLogger,
123
+ ): number {
124
+ const nodes = (workflow.nodes ?? []) as NodeInstance[];
125
+ const definitions = (workflow.definitions ?? []) as Array<{
126
+ nodeType: string;
127
+ model?: Record<string, unknown>;
128
+ }>;
129
+
130
+ let bindingsCreated = 0;
131
+
132
+ for (const node of nodes) {
133
+ if (!isProcessNode(node.type)) continue;
134
+
135
+ const nodeModel = node.model as
136
+ | { context?: Array<Record<string, unknown>> }
137
+ | undefined;
138
+ const defModel = definitions.find((d) => d.nodeType === node.type)
139
+ ?.model as { context?: Array<Record<string, unknown>> } | undefined;
140
+
141
+ // Check both node and definition contexts for unresolved placeholders
142
+ const hasUnresolved = [nodeModel, defModel].some((m) =>
143
+ m?.context?.some(
144
+ (entry) =>
145
+ typeof entry.value === "string" &&
146
+ UNRESOLVED_BINDING_PATTERN.test(entry.value),
147
+ ),
148
+ );
149
+ if (!hasUnresolved) continue;
150
+
151
+ const definition = definitions.find((d) => d.nodeType === node.type);
152
+ if (!definition) continue;
153
+
154
+ let nameBinding: Binding;
155
+ let folderBinding: Binding;
156
+ try {
157
+ ({ nameBinding, folderBinding } =
158
+ createProcessBindings(definition));
159
+ } catch (err: unknown) {
160
+ logger.warn(
161
+ `Skipping binding resolution for node "${node.id}": ${err instanceof Error ? err.message : String(err)}`,
162
+ );
163
+ continue;
164
+ }
165
+
166
+ const beforeCount =
167
+ (workflow.bindings as Binding[] | undefined)?.length ?? 0;
168
+ addBinding(workflow, nameBinding);
169
+ addBinding(workflow, folderBinding);
170
+
171
+ // Resolve against stored bindings (addBinding deduplicates)
172
+ const storedBindings = workflow.bindings as Binding[];
173
+ const storedName =
174
+ storedBindings.find(
175
+ (b) =>
176
+ b.resourceKey === nameBinding.resourceKey &&
177
+ b.propertyAttribute === nameBinding.propertyAttribute,
178
+ ) ?? nameBinding;
179
+ const storedFolder =
180
+ storedBindings.find(
181
+ (b) =>
182
+ b.resourceKey === folderBinding.resourceKey &&
183
+ b.propertyAttribute === folderBinding.propertyAttribute,
184
+ ) ?? folderBinding;
185
+
186
+ // Resolve placeholders in node model.context
187
+ resolveContextPlaceholders(
188
+ nodeModel?.context,
189
+ storedName,
190
+ storedFolder,
191
+ );
192
+
193
+ // Resolve in definition model.context for BPMN converter
194
+ resolveContextPlaceholders(defModel?.context, storedName, storedFolder);
195
+
196
+ const added =
197
+ ((workflow.bindings as Binding[] | undefined)?.length ?? 0) -
198
+ beforeCount;
199
+ bindingsCreated += added;
200
+
201
+ logger.info(
202
+ `Resolved process bindings for node "${node.id}" (${node.type})`,
203
+ );
204
+ }
205
+
206
+ if (bindingsCreated > 0) {
207
+ logger.info(
208
+ `ensureProcessBindings: created ${bindingsCreated} binding(s) for directly-authored nodes`,
209
+ );
210
+ }
211
+
212
+ return bindingsCreated;
213
+ }
package/src/flow-io.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { getFileSystem, type IFileSystem } from "@uipath/filesystem";
2
+ import { resolveAndConvertWorkflow, type Workflow } from "@uipath/flow-schema";
3
+
4
+ export type FlowIoLogger = {
5
+ warn: (message: string) => void;
6
+ };
7
+
8
+ export interface ReadFlowWorkflowOptions {
9
+ /** Filesystem to read from. Defaults to `getFileSystem()`. */
10
+ fs?: IFileSystem;
11
+ /** Logger for non-fatal `$ref` resolution failures. */
12
+ logger?: FlowIoLogger;
13
+ }
14
+
15
+ /**
16
+ * Read a `.flow` file, resolve any `$ref` chunk references, and convert
17
+ * to an in-memory Workflow.
18
+ *
19
+ * Flow files may be split across sibling chunk files (layout.json,
20
+ * shared-vars.json, subflows/) via JSON `$ref` objects. These must be
21
+ * resolved before calling the schema validator, since unknown keys
22
+ * (including `$ref`) are stripped at parse time. Missing ref targets
23
+ * log a warning (fail-soft) and resolve to `null`; the resulting
24
+ * workflow may be partial.
25
+ *
26
+ * Pass `options.fs` when calling from an environment that injects its
27
+ * own filesystem (e.g., a packaging tool bundled for the browser);
28
+ * otherwise the process-wide `getFileSystem()` is used.
29
+ */
30
+ export async function readFlowWorkflow(
31
+ filePath: string,
32
+ options: ReadFlowWorkflowOptions = {},
33
+ ): Promise<Workflow> {
34
+ const fs = options.fs ?? getFileSystem();
35
+ const logger = options.logger;
36
+ const raw = JSON.parse(await readUtf8(fs, filePath));
37
+
38
+ const resolver = async (refPath: string, baseUri: string) => {
39
+ const resolvedPath = fs.path.resolve(fs.path.dirname(baseUri), refPath);
40
+ const refContent = await readUtf8OrNull(fs, resolvedPath);
41
+ if (refContent === null) {
42
+ logger?.warn(
43
+ `Failed to resolve $ref "${refPath}": file not found at ${resolvedPath}`,
44
+ );
45
+ return null;
46
+ }
47
+ return JSON.parse(refContent);
48
+ };
49
+
50
+ const { workflow } = await resolveAndConvertWorkflow(raw, resolver, {
51
+ baseUri: filePath,
52
+ });
53
+ return workflow;
54
+ }
55
+
56
+ async function readUtf8(fs: IFileSystem, filePath: string): Promise<string> {
57
+ const content = await readUtf8OrNull(fs, filePath);
58
+ if (content === null) {
59
+ throw new Error(`Could not read flow file: ${filePath}`);
60
+ }
61
+ return content;
62
+ }
63
+
64
+ async function readUtf8OrNull(
65
+ fs: IFileSystem,
66
+ filePath: string,
67
+ ): Promise<string | null> {
68
+ const buffer = await fs.readFile(filePath);
69
+ if (buffer === null) return null;
70
+ return typeof buffer === "string"
71
+ ? buffer
72
+ : new TextDecoder().decode(buffer);
73
+ }
package/src/flow-tool.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  import { convertFlowToBpmn } from "@uipath/flow-converter";
2
2
  import {
3
+ type EntryPoint,
3
4
  generateBindingsJson,
4
5
  generateEntryPointsJson,
5
6
  generateOperateJson,
6
7
  getBindingResources,
7
8
  getEntryPoints,
9
+ inMemoryWorkflowToFileFormat,
8
10
  type PackagingBinding,
9
11
  type PackagingNode,
12
+ type PackagingNodeManifest,
10
13
  type PackagingWorkflowVariables,
11
14
  ProjectType,
15
+ type Workflow,
12
16
  } from "@uipath/flow-schema";
13
17
  import type {
14
18
  EntryPointsFileModel,
@@ -30,6 +34,9 @@ import {
30
34
  ToolResult,
31
35
  } from "@uipath/solutionpackager-tool-core";
32
36
  import { processAgents } from "./agents.js";
37
+ import { ensureProcessBindings } from "./ensure-process-bindings.js";
38
+ import { readFlowWorkflow } from "./flow-io.js";
39
+ import { setPublishIntentOnInlineAgents } from "./inline-agent-utils.js";
33
40
 
34
41
  const FlowConstants = {
35
42
  EntryPointsFileName: "entry-points.json",
@@ -91,6 +98,9 @@ export class FlowTool extends ProjectTool {
91
98
  contentFolder,
92
99
  );
93
100
 
101
+ this.logger.progress("Resolving process bindings...");
102
+ await this.resolveProcessBindings(contentFolder);
103
+
94
104
  this.logger.progress(
95
105
  "Generating packaging artifacts from .flow...",
96
106
  );
@@ -169,6 +179,43 @@ export class FlowTool extends ProjectTool {
169
179
  }
170
180
  }
171
181
 
182
+ /**
183
+ * Read the .flow file from the content folder, resolve any unresolved
184
+ * process bindings, and write the updated workflow back.
185
+ *
186
+ * This is the solution pack safety net — flows authored outside the CLI
187
+ * may have `<bindings.*>` placeholders that need resolving before packaging.
188
+ */
189
+ private async resolveProcessBindings(contentFolder: string): Promise<void> {
190
+ const entries = await this.fileSystem.readdir(contentFolder);
191
+ const flowFile = entries.find((e: string) => e.endsWith(".flow"));
192
+ if (!flowFile) return;
193
+
194
+ const flowPath = Path.join(contentFolder, flowFile);
195
+ const flowBuffer = await this.fileSystem.readFile(flowPath);
196
+ if (!flowBuffer) return;
197
+
198
+ const flowJson =
199
+ typeof flowBuffer === "string"
200
+ ? flowBuffer
201
+ : new TextDecoder().decode(flowBuffer);
202
+
203
+ let workflow: Workflow;
204
+ try {
205
+ workflow = JSON.parse(flowJson) as Workflow;
206
+ } catch {
207
+ return;
208
+ }
209
+
210
+ const count = ensureProcessBindings(workflow, this.logger);
211
+ if (count > 0) {
212
+ await this.fileSystem.writeFile(
213
+ flowPath,
214
+ JSON.stringify(workflow, null, 4),
215
+ );
216
+ }
217
+ }
218
+
172
219
  /**
173
220
  * Read the .flow file from the content folder, convert to BPMN, and
174
221
  * generate entry-points.json, bindings_v2.json, and the BPMN file.
@@ -191,70 +238,52 @@ export class FlowTool extends ProjectTool {
191
238
  return;
192
239
  }
193
240
 
194
- // Read the .flow JSON
195
- const flowJsonBuffer = await this.fileSystem.readFile(
196
- Path.join(contentFolder, flowFile),
197
- );
198
- if (!flowJsonBuffer) {
199
- this.logger.warn(
200
- `Could not read ${flowFile} — skipping artifact generation`,
201
- );
202
- return;
203
- }
204
- const flowJson =
205
- typeof flowJsonBuffer === "string"
206
- ? flowJsonBuffer
207
- : new TextDecoder().decode(flowJsonBuffer);
208
-
209
- let flowData: {
210
- id?: string;
211
- nodes?: Array<{
212
- id: string;
213
- type: string;
214
- typeVersion?: string;
215
- display?: Record<string, unknown>;
216
- inputs?: Record<string, unknown>;
217
- outputs?: Record<string, unknown>;
218
- model?: Record<string, unknown>;
219
- }>;
220
- bindings?: PackagingBinding[];
221
- variables?: PackagingWorkflowVariables;
222
- };
223
-
241
+ const flowFilePath = Path.join(contentFolder, flowFile);
242
+ let workflow: Workflow;
224
243
  try {
225
- flowData = JSON.parse(flowJson);
226
- } catch {
244
+ workflow = await readFlowWorkflow(flowFilePath, {
245
+ fs: this.fileSystem,
246
+ logger: this.logger,
247
+ });
248
+ } catch (err) {
249
+ const message = err instanceof Error ? err.message : String(err);
227
250
  this.logger.warn(
228
- `Could not parse ${flowFile} as JSON — skipping artifact generation`,
251
+ `Could not load ${flowFile} as a workflow — skipping artifact generation: ${message}`,
229
252
  );
230
253
  return;
231
254
  }
232
255
 
233
- // Convert .flow to BPMN (always regenerate from .flow source)
256
+ // Convert .flow to BPMN from the resolved file format, so any $ref
257
+ // content is inlined before the converter sees it.
258
+ // For pack/publish, set __packageIntent = "Publish" on inline agent
259
+ // definitions so the converter emits "content/" prefixed entry points.
234
260
  const bpmnFileName = flowFile.replace(/\.flow$/, ".bpmn");
235
261
  const bpmnPath = Path.join(contentFolder, bpmnFileName);
236
- const { bpmn } = await convertFlowToBpmn(flowJson);
262
+ const fileFormat = inMemoryWorkflowToFileFormat(workflow);
263
+ setPublishIntentOnInlineAgents(fileFormat);
264
+ const resolvedFlowJson = JSON.stringify(fileFormat);
265
+ const { bpmn } = await convertFlowToBpmn(resolvedFlowJson);
237
266
  await this.fileSystem.writeFile(bpmnPath, bpmn);
238
267
 
239
- // Build PackagingNode[] from the .flow nodes
240
- const packagingNodes: PackagingNode[] = (flowData.nodes ?? []).map(
241
- (node) => ({
242
- id: node.id,
243
- type: node.type,
244
- data: {
245
- type: node.type,
246
- typeVersion: node.typeVersion ?? "1.0.0",
247
- display: node.display,
248
- inputs: node.inputs,
249
- outputs: node.outputs,
250
- model: node.model,
251
- },
268
+ // Map file-format nodes to flat PackagingNode shape so
269
+ // getEntryPoints can read inputs.entryPointId and display.label.
270
+ const packagingNodes: PackagingNode[] = (workflow.nodes ?? []).map(
271
+ (node: Record<string, unknown>) => ({
272
+ id: node.id as string,
273
+ type: node.type as string,
274
+ typeVersion: (node.typeVersion as string) ?? "1.0",
275
+ display: node.display as PackagingNode["display"],
276
+ inputs: node.inputs as Record<string, unknown>,
277
+ outputs: node.outputs as Record<string, unknown>,
252
278
  }),
253
279
  );
254
280
 
255
281
  const variables =
256
- (flowData.variables as PackagingWorkflowVariables) ?? {};
257
- const bindings = (flowData.bindings ?? []) as PackagingBinding[];
282
+ (workflow.variables as PackagingWorkflowVariables) ?? {};
283
+ const bindings = (workflow.bindings ?? []) as PackagingBinding[];
284
+ // definitions required: without it getBindingResources silently drops process/queue/app bindings from bindings_v2.json
285
+ const definitions = (workflow.definitions ??
286
+ []) as PackagingNodeManifest[];
258
287
 
259
288
  // Generate entry-points.json (always regenerate from .flow source)
260
289
  const entryPointsPath = Path.join(
@@ -265,11 +294,94 @@ export class FlowTool extends ProjectTool {
265
294
  bpmnFileName,
266
295
  packagingNodes,
267
296
  variables,
297
+ definitions,
268
298
  ProjectType.Flow,
269
299
  );
300
+
301
+ // Scan for inline agent nodes and add their entry points.
302
+ // The source UUID lives at `inputs.source` (canonical post
303
+ // flow-core 0.2.50) or `model.source` (legacy). Validation
304
+ // accepts both, so packaging must too — otherwise legacy flows
305
+ // produce a .nupkg whose entry-points.json silently omits the
306
+ // agent, and runtime returns 404 for `StartInlineAgentJob`.
307
+ //
308
+ // Canonical copy of this resolution rule lives at
309
+ // `flow-tool/src/services/packaging-utils.ts:readInlineAgentSource`.
310
+ // It's duplicated here (rather than imported) because `flow-tool`
311
+ // already depends on this package, so the reverse import would
312
+ // be a cycle. Keep both in sync; longer-term the helper should
313
+ // move to `@uipath/flow-schema` (an external dep both can share).
314
+ const INLINE_AGENT_TYPES = [
315
+ "uipath.agent.autonomous",
316
+ "uipath.agent.conversational",
317
+ ];
318
+ const readSource = (n: Record<string, unknown>): string | undefined => {
319
+ const inputs = n.inputs as Record<string, unknown> | undefined;
320
+ const model = n.model as Record<string, unknown> | undefined;
321
+ const candidates: unknown[] = [
322
+ inputs?.source,
323
+ model?.source,
324
+ inputs?.agentProjectId,
325
+ model?.agentProjectId,
326
+ ];
327
+ for (const c of candidates) {
328
+ if (typeof c === "string" && c.length > 0) return c;
329
+ }
330
+ return undefined;
331
+ };
332
+ const agentEntryPoints: EntryPoint[] = [];
333
+ const seenSources = new Set<string>();
334
+ for (const node of workflow.nodes ?? []) {
335
+ const n = node as Record<string, unknown>;
336
+ if (!INLINE_AGENT_TYPES.includes(n.type as string)) continue;
337
+ const source = readSource(n);
338
+ if (!source) continue;
339
+ if (seenSources.has(source)) continue;
340
+ seenSources.add(source);
341
+
342
+ const agentJsonPath = Path.join(
343
+ contentFolder,
344
+ source,
345
+ "agent.json",
346
+ );
347
+ if (!(await this.fileSystem.exists(agentJsonPath))) continue;
348
+
349
+ let agent: Record<string, unknown> = {};
350
+ try {
351
+ const raw = await this.fileSystem.readFile(agentJsonPath);
352
+ if (raw) {
353
+ const text =
354
+ typeof raw === "string"
355
+ ? raw
356
+ : new TextDecoder().decode(raw);
357
+ agent = JSON.parse(text) as Record<string, unknown>;
358
+ }
359
+ } catch {
360
+ continue;
361
+ }
362
+
363
+ const inputSchema = (agent.inputSchema ?? {
364
+ type: "object",
365
+ properties: {},
366
+ }) as EntryPoint["input"];
367
+ const outputSchema = (agent.outputSchema ?? {
368
+ type: "object",
369
+ properties: {},
370
+ }) as EntryPoint["output"];
371
+ agentEntryPoints.push({
372
+ filePath: `content/${source}/agent.json`,
373
+ uniqueId: (agent.projectId as string) ?? source,
374
+ type: "agent" as EntryPoint["type"],
375
+ input: inputSchema,
376
+ output: outputSchema,
377
+ displayName: (agent.name as string) ?? "Agent",
378
+ });
379
+ }
380
+
381
+ const allEntryPoints = [...entryPoints, ...agentEntryPoints];
270
382
  await this.fileSystem.writeFile(
271
383
  entryPointsPath,
272
- `${JSON.stringify(generateEntryPointsJson(entryPoints), null, 2)}\n`,
384
+ `${JSON.stringify(generateEntryPointsJson(allEntryPoints), null, 2)}\n`,
273
385
  );
274
386
 
275
387
  // Generate bindings_v2.json (always regenerate from .flow source)
@@ -277,7 +389,11 @@ export class FlowTool extends ProjectTool {
277
389
  contentFolder,
278
390
  FlowConstants.BindingsV2FileName,
279
391
  );
280
- const bindingResources = getBindingResources(packagingNodes, bindings);
392
+ const bindingResources = getBindingResources(
393
+ packagingNodes,
394
+ bindings,
395
+ definitions,
396
+ );
281
397
  await this.fileSystem.writeFile(
282
398
  bindingsPath,
283
399
  `${JSON.stringify(generateBindingsJson(bindingResources), null, 2)}\n`,
@@ -293,7 +409,7 @@ export class FlowTool extends ProjectTool {
293
409
  const mainEntryPoint = `/${bpmnFileName}#${startEventId}`;
294
410
  await this.fileSystem.writeFile(
295
411
  operatePath,
296
- `${JSON.stringify(generateOperateJson(projectId || flowData.id || "", mainEntryPoint), null, 2)}\n`,
412
+ `${JSON.stringify(generateOperateJson(projectId || workflow.id || "", mainEntryPoint), null, 2)}\n`,
297
413
  );
298
414
  }
299
415
 
package/src/index.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  import { toolsFactoryRepository } from "@uipath/solutionpackager-tool-core";
3
3
  import { FlowToolFactory } from "./flow-tool-factory.js";
4
4
 
5
+ export { type FlowIoLogger, readFlowWorkflow } from "./flow-io.js";
5
6
  export { FlowTool } from "./flow-tool.js";
6
7
  export { FlowToolFactory } from "./flow-tool-factory.js";
8
+ export { setPublishIntentOnInlineAgents } from "./inline-agent-utils.js";
7
9
 
8
10
  toolsFactoryRepository.registerProjectToolFactory(new FlowToolFactory());
@@ -0,0 +1,23 @@
1
+ const INLINE_AGENT_DEF_TYPES = [
2
+ "uipath.agent.autonomous",
3
+ "uipath.agent.conversational",
4
+ ];
5
+
6
+ /**
7
+ * Set `__packageIntent = "Publish"` on inline agent definitions in a
8
+ * file-format workflow so the BPMN converter emits "content/" prefixed
9
+ * entry points.
10
+ *
11
+ * Mutates `fileFormat` in place.
12
+ */
13
+ export function setPublishIntentOnInlineAgents(fileFormat: unknown): void {
14
+ const ff = fileFormat as Record<string, unknown[]>;
15
+ for (const def of ff.definitions ?? []) {
16
+ const d = def as Record<string, unknown>;
17
+ if (INLINE_AGENT_DEF_TYPES.includes(d.nodeType as string)) {
18
+ const model = (d.model ?? {}) as Record<string, unknown>;
19
+ model.__packageIntent = "Publish";
20
+ d.model = model;
21
+ }
22
+ }
23
+ }