@uipath/packager-tool-flow 1.203.0-preview.212 → 1.204.0-preview.211

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.
package/dist/flow-io.d.ts CHANGED
@@ -42,6 +42,15 @@ export declare function resolveWorkflowRefs(parsed: unknown, resolver: ResolveRe
42
42
  resolved: unknown;
43
43
  refMap: RefMap;
44
44
  }>;
45
+ /**
46
+ * A `.flow`'s text with its `$ref` chunks inlined — the document the build
47
+ * packs — for a validator that parses text. The schema parser strips an
48
+ * unresolved `$ref`, so a chunked subflow fails the parse and a chunked
49
+ * variable set silently disappears. Text with no `$ref` comes back as it
50
+ * was, and so does text that is not JSON, for the validator to report.
51
+ * Throws as {@link resolveWorkflowRefs} does.
52
+ */
53
+ export declare function inlineFlowRefs(fs: IFileSystem, content: string, filePath: string, logger?: FlowIoLogger): Promise<string>;
45
54
  /**
46
55
  * The full read pipeline for a parsed `.flow` document: resolve `$ref`
47
56
  * chunks, project to the fleet write floor, convert to the in-memory
@@ -75,3 +84,43 @@ export interface ReadFlowWorkflowOptions {
75
84
  * otherwise the process-wide `getFileSystem()` is used.
76
85
  */
77
86
  export declare function readFlowWorkflow(filePath: string, options?: ReadFlowWorkflowOptions): Promise<Workflow>;
87
+ /**
88
+ * Resolve the `.flow` a project runs: `MainFile` first, then `<Name>.flow`,
89
+ * then any `.flow` in the project. One resolver so the pack gate, the debug
90
+ * run, the conversational pre-flight and local eval preparation all read the
91
+ * same file. An unreadable `project.uiproj` degrades to the plain search
92
+ * rather than failing.
93
+ *
94
+ * Takes the file system as a parameter: the packager hands in the one it was
95
+ * constructed with, which in a browser host is not the Node one.
96
+ */
97
+ export declare function resolveProjectFlowFile(fs: IFileSystem, projectPath: string): Promise<string>;
98
+ /**
99
+ * {@link resolveProjectFlowFile}, answering `undefined` for a project that
100
+ * has no `.flow` instead of throwing. See {@link tryFindProjectFlowFile} for
101
+ * why the two answers stay apart.
102
+ */
103
+ export declare function tryResolveProjectFlowFile(fs: IFileSystem, projectPath: string): Promise<string | undefined>;
104
+ /**
105
+ * Locate a flow project's `.flow` source file. Prefers the `project.uiproj`
106
+ * `MainFile` when it names a `.flow` inside the project, then the first
107
+ * `.flow` found in the project root, `content/`, or `flow_files/`.
108
+ *
109
+ * Studio Web-exported solutions ship the flow as `new.flow` (not
110
+ * `<ProjectName>.flow`) with `MainFile: null`, so callers must NOT assume the
111
+ * file is named after the project. When a directory holds multiple `.flow`s,
112
+ * pass `preferredBaseName` (the project name) to disambiguate to
113
+ * `<preferredBaseName>.flow` deterministically. Throws when no `.flow` is found.
114
+ */
115
+ export declare function findProjectFlowFile(fs: IFileSystem, projectPath: string, mainFile?: string | null, preferredBaseName?: string): Promise<string>;
116
+ /**
117
+ * {@link findProjectFlowFile}, except that a project with no `.flow` answers
118
+ * `undefined` rather than throwing.
119
+ *
120
+ * For a caller to whom "this project has none" is an ordinary outcome — a
121
+ * Lite or Maestro Automate project packs fine without one. Reading the
122
+ * directory can still throw (EACCES, EIO); that is not the same answer and
123
+ * must not be flattened into it, or an unreadable project reads as an empty
124
+ * one.
125
+ */
126
+ export declare function tryFindProjectFlowFile(fs: IFileSystem, projectPath: string, mainFile?: string | null, preferredBaseName?: string): Promise<string | undefined>;
@@ -7,7 +7,36 @@ export declare class FlowTool extends ProjectTool {
7
7
  private readonly _temporaryStorage;
8
8
  constructor(fileSystem: IFileSystem, logger: IToolLogger);
9
9
  restoreAsync(_options: IProjectRestoreOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
10
- validateAsync(_options: IProjectValidateOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
10
+ validateAsync(options: IProjectValidateOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
11
+ /**
12
+ * Validate the project's `.flow` with the designer's rules, and turn what
13
+ * they found into a result the solution packager can put on the wire.
14
+ *
15
+ * Every issue becomes a finding, warnings included — Studio renders the
16
+ * severity, and a warning row next to the error that blocked the publish
17
+ * is worth more than the error alone. Only errors decide the verdict.
18
+ *
19
+ * The rules are the designer's own (`validateFlowSource`), so a flow that
20
+ * packs here is one the designer draws clean — in every host, Studio Web
21
+ * included, because nothing has to be supplied from outside. The CLI's
22
+ * extra authoring checks stay in `uip maestro flow validate`.
23
+ *
24
+ * Two ways this ends without a verdict, both building as before: no
25
+ * `.flow` in the project (a Lite or Maestro Automate project, which is
26
+ * ordinary, hence info not warn); and a `.flow` that cannot be read,
27
+ * `$ref` chunks that cannot be resolved (a cycle), or a rule that
28
+ * throws — an infrastructure failure rather than a verdict on
29
+ * the flow, logged and continued, because refusing to pack over it would
30
+ * break packs that work today.
31
+ */
32
+ private runValidation;
33
+ /**
34
+ * `flowFilePath` as the finding contract wants it — relative to the
35
+ * project, forward slashes. Studio refuses to navigate a path that
36
+ * escapes the project directory, so anything that would is reduced to the
37
+ * bare file name, which still names the file in the Error List.
38
+ */
39
+ private toProjectRelative;
11
40
  buildAsync(options: IProjectBuildOptions, _cancellationToken?: AbortSignal): Promise<ToolResult>;
12
41
  packAsync(options: IProjectPackOptions, cancellationToken?: AbortSignal): Promise<ToolResult>;
13
42
  dispose(): Promise<void>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The shape of a Flow validation issue, and the two helpers that turn one
3
+ * into a finding Studio can navigate: `splitIssueMessage` peels the rule code
4
+ * off the text, `nodeIdFromIssuePath` finds the node the issue is about.
5
+ *
6
+ * Issues come from `validateFlowSource` (the pack gate) and from flow-tool's
7
+ * `FlowValidateService`, whose `ValidationIssue` has the same shape.
8
+ */
9
+ export type FlowValidationSeverity = "error" | "warning";
10
+ /** One problem found in a `.flow`. */
11
+ export interface FlowValidationIssue {
12
+ /**
13
+ * Dot path into the flow — `nodes[<node-id>].inputs.<field>`, or
14
+ * `subflows[<subflow-id>].nodes[<node-id>]…` inside a subflow.
15
+ */
16
+ path: string;
17
+ /**
18
+ * Human-readable text. Rules that carry a code prefix it as `[CODE] `;
19
+ * {@link splitIssueMessage} peels that back off for the finding contract.
20
+ */
21
+ message: string;
22
+ severity: FlowValidationSeverity;
23
+ }
24
+ /**
25
+ * Split `[CODE] text` into its parts, so the finding contract can carry the
26
+ * code in `ErrorCode` and leave `Description` readable. Rules that emit no
27
+ * code (the per-node validators) come back with `code: undefined` and the
28
+ * message untouched.
29
+ */
30
+ export declare function splitIssueMessage(message: string): {
31
+ code?: string;
32
+ description: string;
33
+ };
34
+ /**
35
+ * The node a dot path points at, or `undefined` for a workflow-scoped issue.
36
+ * The last `nodes[…]` segment wins, so a subflow-prefixed path yields the node
37
+ * inside the subflow rather than the subflow id.
38
+ *
39
+ * A purely numeric segment is an array index, not an id: a schema-parse issue
40
+ * carries a Zod path, and flow-schema's `formatZodPath` renders those as
41
+ * `nodes[0].type`. Treating the `0` as a node id would have Studio claim the
42
+ * row, fail to focus a node by that name, and — because the id read as
43
+ * present — drop the path from the description, leaving nothing to locate the
44
+ * problem by.
45
+ */
46
+ export declare function nodeIdFromIssuePath(path: string): string | undefined;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The designer's validation rules, run over a `.flow` without the designer.
3
+ *
4
+ * This is what decides whether a Flow project packs: parse the file against
5
+ * the flow schema, load each inline agent's prompts and settings from its
6
+ * `agent.json` the way the designer does, then run `@uipath/flow-schema`'s
7
+ * rule set — the same rules the canvas runs — over the root graph and every
8
+ * subflow. No network or login, and file access only through the file system
9
+ * the caller hands in, so it runs wherever the packager does: the CLI,
10
+ * desktop Studio, and Studio Web.
11
+ *
12
+ * `uip maestro flow validate` runs the same two steps (via
13
+ * {@link parseFlowSource} and {@link runDesignerRules}) and adds its own
14
+ * authoring checks around them. Those extra checks are for flows written by
15
+ * agents or by hand, which the designer never shows, so they stay out of the
16
+ * pack verdict.
17
+ */
18
+ import { type ParsedWorkflow, type ValidationRule, type WorkflowValidationContext, type WorkflowVariables } from "@uipath/flow-schema";
19
+ import type { FlowValidationIssue } from "./flow-validation-hook.js";
20
+ import { type ReadAgentJson } from "./inline-agent-utils.js";
21
+ /** Graph shape shared by root workflow and each subflow entry; avoids `as ParsedWorkflow` over `unknown[]`. */
22
+ export interface FlowGraph {
23
+ nodes: Array<{
24
+ id: string;
25
+ type: string;
26
+ typeVersion?: string;
27
+ display?: {
28
+ label?: string;
29
+ } & Record<string, unknown>;
30
+ inputs?: Record<string, unknown>;
31
+ outputs?: Record<string, unknown>;
32
+ model?: Record<string, unknown>;
33
+ }>;
34
+ edges: Array<{
35
+ id: string;
36
+ sourceNodeId: string;
37
+ sourcePort: string;
38
+ targetNodeId: string;
39
+ targetPort: string;
40
+ }>;
41
+ definitions: Array<{
42
+ nodeType: string;
43
+ version: string;
44
+ category?: string;
45
+ display?: {
46
+ label: string;
47
+ };
48
+ handleConfiguration: unknown[];
49
+ inputDefinition?: Record<string, unknown>;
50
+ outputDefinition?: Record<string, unknown>;
51
+ /** BPMN element the node lowers to at runtime, e.g. `bpmn:ParallelGateway`. */
52
+ model?: {
53
+ type?: string;
54
+ } & Record<string, unknown>;
55
+ tags?: string[];
56
+ }>;
57
+ variables?: WorkflowVariables;
58
+ /** Top-level binding registry that `<bindings.{name}>` placeholders resolve against. */
59
+ bindings?: Array<Record<string, unknown>>;
60
+ }
61
+ /** A `.flow` that parsed, or the issues explaining why it did not. */
62
+ export type ParsedFlowSource = {
63
+ ok: true;
64
+ workflow: ParsedWorkflow;
65
+ /**
66
+ * `supportsErrorHandling` per `nodeType:version`, recovered from the
67
+ * raw JSON — the schema parser strips it, and the edge-handle check
68
+ * needs it to honour the implicit `error` source port.
69
+ */
70
+ rawDefErrorHandling: Map<string, boolean>;
71
+ } | {
72
+ ok: false;
73
+ issues: FlowValidationIssue[];
74
+ };
75
+ export interface DesignerRuleOptions {
76
+ /**
77
+ * The graph is a subflow body (a loop body, a nested call activity), so
78
+ * rules scoped to the whole workflow — `trigger-required` — do not apply.
79
+ */
80
+ isSubflow?: boolean;
81
+ governanceEnforcements?: WorkflowValidationContext["governanceEnforcements"];
82
+ availableModelNames?: WorkflowValidationContext["availableModelNames"];
83
+ /** Appended after the designer's rules. */
84
+ extraRules?: readonly ValidationRule[];
85
+ }
86
+ /**
87
+ * Copy onto an inline agent's node what the designer shows on it, which the
88
+ * `.flow` keeps in the agent's own `agent.json`: the system and user prompts
89
+ * from its messages, and model / temperature / token limit from its settings
90
+ * (voice settings too, for a voice agent). Without this every stored inline
91
+ * agent reads as missing its prompts and model. A value already on the node
92
+ * is never overwritten, and the first system and first user message win.
93
+ */
94
+ export declare function hydrateAgentNodeInputs(node: {
95
+ type: string;
96
+ inputs?: Record<string, unknown>;
97
+ }, agent: Record<string, unknown>): void;
98
+ /**
99
+ * Parse and validate against flow schema. `parseWorkflowJson` returns a
100
+ * structured `parseIssues` array (one entry per Zod issue) on flow-schema
101
+ * versions ≥ the one shipping the side-channel; on older versions only the
102
+ * joined `parseError` string is populated. The cast below keeps us
103
+ * forward-compatible without forcing a dep bump: when the field arrives we
104
+ * emit one issue per Zod issue with its real path; otherwise we split the
105
+ * formatted multi-line `parseError` string ourselves so each issue keeps its
106
+ * Zod path (UiPath/cli#2029). Only when neither shape is available do we
107
+ * collapse to a single `(root)` issue (legacy / non-schema errors like JSON
108
+ * parse failures). flow-workbench PR #1479.
109
+ */
110
+ export declare function parseFlowSource(content: string): ParsedFlowSource;
111
+ /**
112
+ * The designer's rules over one graph. Paths come back relative to the graph
113
+ * — a caller validating a subflow prefixes them.
114
+ *
115
+ * Semantic validation using built-in rules from flow-schema.
116
+ * conditionExpressionRule is deliberately excluded from cliRules on the
117
+ * canvas (it runs as a main-thread fallback there); with no competing
118
+ * validator here we opt it in, to catch malformed HTTP/Decision/Switch
119
+ * condition expressions. triggerRequiredRule is scoped to the root workflow
120
+ * only — subflows (loop bodies, nested call activities) are not expected to
121
+ * carry their own trigger. expression-ref is dropped everywhere: it treats
122
+ * the `any` that script, transform and loop `currentItem` outputs declare as
123
+ * a closed primitive, so it refuses deep reads of them, and reads the
124
+ * manifest rather than the `.flow`, so no author can suppress it. The canvas
125
+ * dropped it over the same false positives (MST-9593); flow-workbench#3638
126
+ * removes it from `cliRules`, after which this filter matches nothing.
127
+ */
128
+ export declare function runDesignerRules(graph: FlowGraph, options?: DesignerRuleOptions): FlowValidationIssue[];
129
+ /**
130
+ * {@link parseFlowSource}, then {@link runDesignerRules} over the root graph
131
+ * and every subflow — with each inline agent first hydrated from its
132
+ * `agent.json` through `readAgentJson`, as the designer shows it. This is the
133
+ * pack verdict: any `error` fails the pack.
134
+ */
135
+ export declare function validateFlowSource(content: string, readAgentJson?: ReadAgentJson): Promise<FlowValidationIssue[]>;
package/dist/index.d.ts CHANGED
@@ -2,10 +2,12 @@ export { type AgentInputLeaf, canonicalAgentInputPath, collectAgentInputLeaves,
2
2
  export { buildInlineAgentContract, type InlineAgentContract, } from "./agents.js";
3
3
  export { createProcessBindings, ensureProcessBindings, extractProcessGuid, } from "./ensure-process-bindings.js";
4
4
  export { replaceExporterVersion } from "./exporter-version.js";
5
- export { createFlowRefResolver, type FlowIoLogger, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveWorkflowRefs, } from "./flow-io.js";
5
+ export { createFlowRefResolver, type FlowIoLogger, findProjectFlowFile, inlineFlowRefs, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveProjectFlowFile, resolveWorkflowRefs, tryFindProjectFlowFile, tryResolveProjectFlowFile, } from "./flow-io.js";
6
6
  export { FlowTool } from "./flow-tool.js";
7
7
  export { FlowToolFactory } from "./flow-tool-factory.js";
8
- export { type AgentInputVariable, hydrateInlineAgentInputVariables, isInlineAgentNodeType, readInlineAgentSource, reconcileInlineAgentInputVariables, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
8
+ export { type DesignerRuleOptions, type FlowGraph, hydrateAgentNodeInputs, type ParsedFlowSource, parseFlowSource, runDesignerRules, validateFlowSource, } from "./flow-validation.js";
9
+ export type { FlowValidationIssue, FlowValidationSeverity, } from "./flow-validation-hook.js";
10
+ export { type AgentInputVariable, agentJsonReader, hydrateInlineAgentInputVariables, isInlineAgentNodeType, type ReadAgentJson, readInlineAgentSource, reconcileInlineAgentInputVariables, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
9
11
  export { applyRuntimeProfile, isMaestroAutomateSentinel, RuntimeProfile, resolveRuntimeProfile, resolveRuntimeProfileForFlowFile, withRuntimeProfile, } from "./maestro-automate.js";
10
12
  export { operateRuntimeOptions } from "./operate-runtime-options.js";
11
13
  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.203.0-preview.212",
35
+ version: "1.204.0-preview.211",
36
36
  description: "UiPath Flow tool implementation",
37
37
  type: "module",
38
38
  exports: {
@@ -49,8 +49,9 @@ var package_default = {
49
49
  },
50
50
  types: "./dist/index.d.ts",
51
51
  scripts: {
52
- build: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --sourcemap=linked && bun build ./src/maestro-automate-entry.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --sourcemap=linked && tsc --emitDeclarationOnly --outDir dist",
53
- dev: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --watch",
52
+ build: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --external i18next --sourcemap=linked && bun build ./src/maestro-automate-entry.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --sourcemap=linked && tsc -p tsconfig.build.json --emitDeclarationOnly --noCheck --outDir dist",
53
+ typecheck: "tsc --noEmit",
54
+ dev: "bun build ./src/index.ts --outdir dist --format esm --target browser --external @uipath/filesystem --external @uipath/solutionpackager-tool-core --external @uipath/tool-agent --external @uipath/flow-core --external @uipath/flow-converter --external @uipath/flow-migrations --external @uipath/flow-schema --external i18next --watch",
54
55
  test: "vitest run",
55
56
  "test:browser": "vitest run --config=vitest.browser.config.ts",
56
57
  "test:coverage": "vitest run --coverage",
@@ -77,7 +78,8 @@ var package_default = {
77
78
  "@uipath/flow-migrations": "0.56.2",
78
79
  "@uipath/flow-schema": "0.68.0",
79
80
  "@uipath/solutionpackager-tool-core": "workspace:*",
80
- "@uipath/tool-agent": "^2.0.0"
81
+ "@uipath/tool-agent": "^2.0.0",
82
+ i18next: "^23.16.8"
81
83
  },
82
84
  devDependencies: {
83
85
  "@types/node": "^25.5.2",
@@ -89,6 +91,7 @@ var package_default = {
89
91
  "@vitest/browser": "^4.1.11",
90
92
  "@vitest/browser-playwright": "^4.1.11",
91
93
  "@vitest/coverage-v8": "^4.1.11",
94
+ i18next: "^23.16.8",
92
95
  playwright: "^1.57.0",
93
96
  typescript: "^7.0.2",
94
97
  vitest: "^4.1.11"
@@ -428,6 +431,18 @@ async function resolveWorkflowRefs(parsed, resolver, baseUri) {
428
431
  }
429
432
  return { resolved, refMap };
430
433
  }
434
+ async function inlineFlowRefs(fs, content, filePath, logger) {
435
+ if (!content.includes('"$ref"'))
436
+ return content;
437
+ let parsed;
438
+ try {
439
+ parsed = JSON.parse(content);
440
+ } catch {
441
+ return content;
442
+ }
443
+ const { resolved } = await resolveWorkflowRefs(parsed, createFlowRefResolver(fs, logger), filePath);
444
+ return JSON.stringify(resolved);
445
+ }
431
446
  async function resolveAndMigrateWorkflow(parsed, resolver, baseUri, logger) {
432
447
  const { resolved, refMap } = await resolveWorkflowRefs(parsed, resolver, baseUri);
433
448
  const migrated = migrateRawWorkflow(resolved, logger);
@@ -456,6 +471,79 @@ async function readUtf8OrNull(fs, filePath) {
456
471
  return null;
457
472
  return typeof buffer === "string" ? buffer : new TextDecoder().decode(buffer);
458
473
  }
474
+ async function resolveProjectFlowFile(fs, projectPath) {
475
+ const found = await tryResolveProjectFlowFile(fs, projectPath);
476
+ if (!found) {
477
+ throw new Error(`Missing .flow file in ${projectPath}`);
478
+ }
479
+ return found;
480
+ }
481
+ async function tryResolveProjectFlowFile(fs, projectPath) {
482
+ let uiproj;
483
+ try {
484
+ const raw = await fs.readFile(fs.path.join(projectPath, "project.uiproj"), "utf-8");
485
+ uiproj = JSON.parse(raw);
486
+ } catch {
487
+ uiproj = undefined;
488
+ }
489
+ const safeName = uiproj?.Name?.replace(/[^a-zA-Z0-9_-]/g, "_");
490
+ return tryFindProjectFlowFile(fs, projectPath, uiproj?.MainFile, safeName);
491
+ }
492
+ async function findProjectFlowFile(fs, projectPath, mainFile, preferredBaseName) {
493
+ const found = await tryFindProjectFlowFile(fs, projectPath, mainFile, preferredBaseName);
494
+ if (!found) {
495
+ throw new Error(`Missing .flow file in ${projectPath}`);
496
+ }
497
+ return found;
498
+ }
499
+ async function tryFindProjectFlowFile(fs, projectPath, mainFile, preferredBaseName) {
500
+ if (mainFile?.endsWith(".flow")) {
501
+ const mainFilePath = fs.path.join(projectPath, mainFile);
502
+ if (isInsideDirectory(fs, projectPath, mainFilePath) && await fs.exists(mainFilePath)) {
503
+ return mainFilePath;
504
+ }
505
+ }
506
+ const preferred = preferredBaseName ? `${preferredBaseName}.flow`.toLowerCase() : undefined;
507
+ const candidateDirs = [
508
+ projectPath,
509
+ fs.path.join(projectPath, "content"),
510
+ fs.path.join(projectPath, "flow_files")
511
+ ];
512
+ for (const dir of candidateDirs) {
513
+ if (!await fs.exists(dir)) {
514
+ continue;
515
+ }
516
+ const stat = await fs.stat(dir);
517
+ if (!stat?.isDirectory()) {
518
+ continue;
519
+ }
520
+ const flowFiles = (await fs.readdir(dir)).filter((entry) => entry.endsWith(".flow"));
521
+ if (flowFiles.length === 0) {
522
+ continue;
523
+ }
524
+ const chosen = (preferred && flowFiles.find((f) => f.toLowerCase() === preferred)) ?? flowFiles[0];
525
+ return fs.path.join(dir, chosen);
526
+ }
527
+ return;
528
+ }
529
+ function isInsideDirectory(fs, directory, candidate) {
530
+ const relative = fs.path.relative(directory, candidate);
531
+ return relative !== "" && !/^\.\.(?:[\\/]|$)/.test(relative) && !fs.path.isAbsolute(relative);
532
+ }
533
+
534
+ // src/flow-validation.ts
535
+ import {
536
+ AGENT_NODE_TYPES,
537
+ cliRules,
538
+ conditionExpressionRule,
539
+ isBlockingSeverity,
540
+ isVoiceAgentNodeType as isVoiceAgentNodeType2,
541
+ parseWorkflowJson,
542
+ registerFlowSchemaI18n,
543
+ runRules,
544
+ toSerializedNodes
545
+ } from "@uipath/flow-schema";
546
+ import i18next from "i18next";
459
547
 
460
548
  // src/inline-agent-utils.ts
461
549
  import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
@@ -523,6 +611,19 @@ function readInlineAgentSource(node) {
523
611
  }
524
612
  return;
525
613
  }
614
+ function agentJsonReader(fs, flowDir) {
615
+ return async (sourceId) => {
616
+ try {
617
+ const raw = await fs.readFile(fs.path.join(flowDir, sourceId, "agent.json"), "utf-8");
618
+ if (!raw)
619
+ return;
620
+ const agent = JSON.parse(raw);
621
+ return typeof agent === "object" && agent !== null && !Array.isArray(agent) ? agent : undefined;
622
+ } catch {
623
+ return;
624
+ }
625
+ };
626
+ }
526
627
  function isInlineAgentNodeType(type) {
527
628
  return isAgentNodeType(type);
528
629
  }
@@ -587,6 +688,260 @@ function setPublishIntentOnInlineAgents(fileFormat) {
587
688
  }
588
689
  }
589
690
 
691
+ // src/flow-validation.ts
692
+ var validationMessagesRegistered = false;
693
+ function ensureValidationMessages() {
694
+ if (validationMessagesRegistered)
695
+ return;
696
+ if (!i18next.isInitialized) {
697
+ i18next.init({
698
+ lng: "en",
699
+ fallbackLng: "en",
700
+ defaultNS: "validation",
701
+ resources: {},
702
+ interpolation: { escapeValue: false }
703
+ });
704
+ }
705
+ registerFlowSchemaI18n(i18next);
706
+ validationMessagesRegistered = true;
707
+ }
708
+ function hydrateAgentNodeInputs(node, agent) {
709
+ if (!node.inputs)
710
+ node.inputs = {};
711
+ const inputs = node.inputs;
712
+ const settings = agent.settings;
713
+ if (isVoiceAgentNodeType2(node.type)) {
714
+ const voiceSettings = isRecord2(settings) ? settings.voice : undefined;
715
+ if (isRecord2(voiceSettings) && inputs.voice === undefined) {
716
+ inputs.voice = voiceSettings;
717
+ }
718
+ }
719
+ const messages = agent.messages;
720
+ if (!Array.isArray(messages))
721
+ return;
722
+ for (const msg of messages) {
723
+ if (!msg || typeof msg !== "object")
724
+ continue;
725
+ const m = msg;
726
+ if (m.role === "system" && typeof m.content === "string") {
727
+ inputs.systemPrompt ??= m.content;
728
+ }
729
+ if (m.role === "user" && typeof m.content === "string") {
730
+ inputs.userPrompt ??= m.content;
731
+ }
732
+ }
733
+ if (settings && typeof settings === "object") {
734
+ const s = settings;
735
+ if (typeof s.temperature === "number" && inputs.temperature === undefined) {
736
+ inputs.temperature = s.temperature;
737
+ }
738
+ if (typeof s.maxTokens === "number" && inputs.maxTokenPerResponse === undefined) {
739
+ inputs.maxTokenPerResponse = s.maxTokens;
740
+ }
741
+ if (typeof s.model === "string" && inputs.model === undefined) {
742
+ inputs.model = s.model;
743
+ }
744
+ }
745
+ }
746
+ async function hydrateInlineAgents(nodes, readAgentJson) {
747
+ for (const node of nodes) {
748
+ if (!AGENT_NODE_TYPES.includes(node.type)) {
749
+ continue;
750
+ }
751
+ const source = readInlineAgentSource(node);
752
+ if (source === undefined || /[/\\.]/.test(source))
753
+ continue;
754
+ const agent = await readAgentJson(source);
755
+ if (agent)
756
+ hydrateAgentNodeInputs(node, agent);
757
+ }
758
+ }
759
+ function isRecord2(value) {
760
+ return typeof value === "object" && value !== null && !Array.isArray(value);
761
+ }
762
+ function parseFlowSource(content) {
763
+ ensureValidationMessages();
764
+ const parsed = parseWorkflowJson(content);
765
+ const { workflow, parseError, parseIssues } = parsed;
766
+ if (parseError || !workflow) {
767
+ const recoveredIssues = parseIssues && parseIssues.length > 0 ? parseIssues : parseError ? splitSchemaParseError(parseError) : null;
768
+ const fallback = [
769
+ {
770
+ path: "(root)",
771
+ message: parseError ?? "File is not valid JSON",
772
+ severity: "error"
773
+ }
774
+ ];
775
+ return {
776
+ ok: false,
777
+ issues: recoveredIssues && recoveredIssues.length > 0 ? recoveredIssues.map((pi) => ({
778
+ path: pi.path,
779
+ message: pi.message,
780
+ severity: "error"
781
+ })) : fallback
782
+ };
783
+ }
784
+ return {
785
+ ok: true,
786
+ workflow,
787
+ rawDefErrorHandling: extractSupportsErrorHandling(content)
788
+ };
789
+ }
790
+ function runDesignerRules(graph, options = {}) {
791
+ ensureValidationMessages();
792
+ const ctx = buildValidationContext(graph);
793
+ if (options.governanceEnforcements) {
794
+ ctx.governanceEnforcements = options.governanceEnforcements;
795
+ }
796
+ if (options.availableModelNames) {
797
+ ctx.availableModelNames = options.availableModelNames;
798
+ }
799
+ const dropped = new Set(["expression-ref"]);
800
+ if (options.isSubflow)
801
+ dropped.add("trigger-required");
802
+ const rules = cliRules.filter((r) => !dropped.has(r.id));
803
+ const ruleErrors = runRules([...rules, conditionExpressionRule, ...options.extraRules ?? []], ctx);
804
+ return ruleErrors.map(validationErrorToIssue);
805
+ }
806
+ async function validateFlowSource(content, readAgentJson) {
807
+ const parsed = parseFlowSource(content);
808
+ if (!parsed.ok) {
809
+ return parsed.issues;
810
+ }
811
+ const { workflow } = parsed;
812
+ const subflows = workflow.subflows ?? {};
813
+ if (readAgentJson) {
814
+ await hydrateInlineAgents(workflow.nodes, readAgentJson);
815
+ for (const entry of Object.values(subflows)) {
816
+ await hydrateInlineAgents(entry.nodes, readAgentJson);
817
+ }
818
+ }
819
+ const issues = runDesignerRules(workflow);
820
+ for (const [subflowId, entry] of Object.entries(subflows)) {
821
+ const subGraph = {
822
+ nodes: entry.nodes,
823
+ edges: entry.edges,
824
+ definitions: workflow.definitions,
825
+ variables: entry.variables
826
+ };
827
+ const prefix = `subflows[${subflowId}].`;
828
+ issues.push(...runDesignerRules(subGraph, { isSubflow: true }).map((issue) => ({
829
+ ...issue,
830
+ path: `${prefix}${issue.path}`
831
+ })));
832
+ }
833
+ return issues;
834
+ }
835
+ function extractSupportsErrorHandling(content) {
836
+ const map = new Map;
837
+ let raw;
838
+ try {
839
+ raw = JSON.parse(content);
840
+ } catch {
841
+ return map;
842
+ }
843
+ if (!raw || typeof raw !== "object")
844
+ return map;
845
+ const defs = raw.definitions;
846
+ if (!Array.isArray(defs))
847
+ return map;
848
+ for (const def of defs) {
849
+ if (!def || typeof def !== "object")
850
+ continue;
851
+ const d = def;
852
+ if (typeof d.nodeType === "string" && typeof d.version === "string" && d.supportsErrorHandling === true) {
853
+ map.set(`${d.nodeType}:${d.version}`, true);
854
+ }
855
+ }
856
+ return map;
857
+ }
858
+ function validationErrorToIssue(error) {
859
+ let path;
860
+ if (error.nodeId) {
861
+ path = error.fieldPath ? `nodes[${error.nodeId}].${error.fieldPath}` : `nodes[${error.nodeId}]`;
862
+ } else {
863
+ path = error.fieldPath ?? "(workflow)";
864
+ }
865
+ return {
866
+ path,
867
+ message: `[${error.code}] ${error.message}`,
868
+ severity: isBlockingSeverity(error.severity) ? "error" : "warning"
869
+ };
870
+ }
871
+ function buildValidationContext(flow) {
872
+ const nodes = toSerializedNodes(flow.nodes);
873
+ const edges = flow.edges.map((e) => ({
874
+ id: e.id,
875
+ source: e.sourceNodeId,
876
+ sourceHandle: e.sourcePort ?? null,
877
+ target: e.targetNodeId,
878
+ targetHandle: e.targetPort ?? null
879
+ }));
880
+ const manifests = new Map;
881
+ for (const def of flow.definitions) {
882
+ manifests.set(`${def.nodeType}:${def.version}`, {
883
+ nodeType: def.nodeType,
884
+ version: def.version,
885
+ category: def.category,
886
+ display: def.display,
887
+ handleConfiguration: def.handleConfiguration,
888
+ inputDefinition: def.inputDefinition,
889
+ outputDefinition: def.outputDefinition,
890
+ tags: def.tags
891
+ });
892
+ }
893
+ const outputVariableIds = [];
894
+ const variables = flow.variables;
895
+ if (variables?.globals) {
896
+ for (const v of variables.globals) {
897
+ if (v.direction === "out" || v.direction === "inout") {
898
+ outputVariableIds.push(v.id);
899
+ }
900
+ }
901
+ }
902
+ return {
903
+ nodes,
904
+ edges,
905
+ manifests,
906
+ outputVariableIds
907
+ };
908
+ }
909
+ function splitSchemaParseError(parseError) {
910
+ const header = "Schema validation failed:";
911
+ if (!parseError.startsWith(header)) {
912
+ return null;
913
+ }
914
+ const body = parseError.slice(header.length);
915
+ const lines = body.split(`
916
+ `);
917
+ const issues = [];
918
+ for (const rawLine of lines) {
919
+ const match = /^\s*-\s+(.+?):\s(.*)$/.exec(rawLine);
920
+ if (!match)
921
+ continue;
922
+ const [, path, message] = match;
923
+ if (!path || !message)
924
+ continue;
925
+ issues.push({ path, message });
926
+ }
927
+ return issues.length > 0 ? issues : null;
928
+ }
929
+
930
+ // src/flow-validation-hook.ts
931
+ function splitIssueMessage(message) {
932
+ const match = /^\[([A-Z][A-Z0-9_]*)\]\s+(.*)$/s.exec(message);
933
+ return match?.[2] ? { code: match[1], description: match[2] } : { description: message };
934
+ }
935
+ function nodeIdFromIssuePath(path) {
936
+ let nodeId;
937
+ for (const match of path.matchAll(/nodes\[([^\]]+)\]/g)) {
938
+ if (match[1] !== undefined && !/^\d+$/.test(match[1])) {
939
+ nodeId = match[1];
940
+ }
941
+ }
942
+ return nodeId;
943
+ }
944
+
590
945
  // src/maestro-automate.ts
591
946
  import { Path as Path3 } from "@uipath/solutionpackager-tool-core";
592
947
  var SENTINEL_FILE_NAME = ".maestro_automate";
@@ -641,7 +996,7 @@ function operateRuntimeOptions(nodes) {
641
996
  }
642
997
 
643
998
  // src/voice-agent-definitions.ts
644
- import { isVoiceAgentNodeType as isVoiceAgentNodeType2 } from "@uipath/flow-schema";
999
+ import { isVoiceAgentNodeType as isVoiceAgentNodeType3 } from "@uipath/flow-schema";
645
1000
  function nodeLabel(node) {
646
1001
  const label = node.display?.label;
647
1002
  return typeof label === "string" && label.length > 0 ? label : node.id;
@@ -649,7 +1004,7 @@ function nodeLabel(node) {
649
1004
  function collectVoiceAgentNodes(nodes) {
650
1005
  const result = [];
651
1006
  for (const node of nodes) {
652
- if (!isVoiceAgentNodeType2(node.type))
1007
+ if (!isVoiceAgentNodeType3(node.type))
653
1008
  continue;
654
1009
  const source = readInlineAgentSource(node);
655
1010
  if (!source) {
@@ -680,7 +1035,7 @@ function assertNoSubflowVoiceAgents(flowJson) {
680
1035
  const workflow = JSON.parse(flowJson);
681
1036
  for (const [subflowId, entry] of Object.entries(workflow.subflows ?? {})) {
682
1037
  for (const node of entry.nodes ?? []) {
683
- if (!isVoiceAgentNodeType2(node.type))
1038
+ if (!isVoiceAgentNodeType3(node.type))
684
1039
  continue;
685
1040
  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.");
686
1041
  }
@@ -713,6 +1068,9 @@ var FlowConstants = {
713
1068
  EntryPointsFileName: "entry-points.json",
714
1069
  BindingsV2FileName: "bindings_v2.json"
715
1070
  };
1071
+ var FLOW_VALIDATION_FAILED = "FLOW_VALIDATION_FAILED";
1072
+ var FLOW_VALIDATION_SOURCE = "Validator";
1073
+ var SUBFLOW_PATH_SEGMENT = "subflows[";
716
1074
 
717
1075
  class FlowTool extends ProjectTool {
718
1076
  _temporaryStorage;
@@ -724,11 +1082,59 @@ class FlowTool extends ProjectTool {
724
1082
  this.logger.info("Restore operation is not required for Flow projects");
725
1083
  return ToolResult.success();
726
1084
  }
727
- async validateAsync(_options, _cancellationToken) {
728
- this.logger.info("Validate operation is not required for Flow projects");
729
- return ToolResult.success();
1085
+ async validateAsync(options, _cancellationToken) {
1086
+ return this.runValidation(options.projectPath);
1087
+ }
1088
+ async runValidation(projectPath) {
1089
+ let flowFile;
1090
+ let issues;
1091
+ try {
1092
+ flowFile = await tryResolveProjectFlowFile(this.fileSystem, projectPath);
1093
+ if (flowFile === undefined) {
1094
+ this.logger.info("Flow project has no .flow file; skipping Flow validation");
1095
+ return ToolResult.success();
1096
+ }
1097
+ const content = await this.fileSystem.readFile(flowFile, "utf-8");
1098
+ if (content === null) {
1099
+ throw new Error(`Could not read ${flowFile}`);
1100
+ }
1101
+ this.logger.progress("Validating Flow project...");
1102
+ const source = await inlineFlowRefs(this.fileSystem, content, flowFile, this.logger);
1103
+ issues = await validateFlowSource(source, agentJsonReader(this.fileSystem, projectPath));
1104
+ } catch (error) {
1105
+ const message = error instanceof Error ? error.message : String(error);
1106
+ this.logger.warn(`Could not validate the Flow project, continuing without validation: ${message}`);
1107
+ return ToolResult.success();
1108
+ }
1109
+ const flowFilePath = this.toProjectRelative(projectPath, flowFile);
1110
+ const findings = issues.map((issue) => toFlowFinding(issue, flowFilePath));
1111
+ const errors = issues.filter((issue) => issue.severity === "error");
1112
+ for (const issue of issues) {
1113
+ const line = `[${issue.path}] ${issue.message}`;
1114
+ if (issue.severity === "error") {
1115
+ this.logger.error(line);
1116
+ } else {
1117
+ this.logger.warn(line);
1118
+ }
1119
+ }
1120
+ const result = errors.length === 0 ? ToolResult.success() : ToolResult.error(FLOW_VALIDATION_FAILED, `Flow validation failed for ${flowFilePath}: ${errors.length} error(s).`, "Open the flow in the designer and fix the reported errors, " + "or run 'uip maestro flow validate <project-path>' for the full list.");
1121
+ if (findings.length > 0) {
1122
+ result.details = { ...result.details, findings };
1123
+ }
1124
+ return result;
1125
+ }
1126
+ toProjectRelative(projectPath, filePath) {
1127
+ const root = `${Path4.normalize(projectPath).replace(/\/$/, "")}/`;
1128
+ const file = Path4.normalize(filePath);
1129
+ return file.toLowerCase().startsWith(root.toLowerCase()) ? file.slice(root.length) : Path4.basename(file);
730
1130
  }
731
1131
  async buildAsync(options, _cancellationToken) {
1132
+ if (!options.skipValidate) {
1133
+ const validationResult = await this.runValidation(options.projectPath);
1134
+ if (!validationResult.isSuccess) {
1135
+ return validationResult;
1136
+ }
1137
+ }
732
1138
  const tempFolder = await this._temporaryStorage.getTempFolderPath();
733
1139
  const localBuildFolder = Path4.join(tempFolder, NugetConstants.OutputFolderName);
734
1140
  const contentFolder = Path4.join(localBuildFolder, NugetConstants.ContentFolderName);
@@ -1022,6 +1428,19 @@ class FlowTool extends ProjectTool {
1022
1428
  await this.fileSystem.writeFile(packageDescriptorPath, packageDescriptorJson);
1023
1429
  }
1024
1430
  }
1431
+ function toFlowFinding(issue, flowFilePath) {
1432
+ const { code, description } = splitIssueMessage(issue.message);
1433
+ const nodeId = nodeIdFromIssuePath(issue.path);
1434
+ const pathAddsLocation = nodeId === undefined || issue.path.includes(SUBFLOW_PATH_SEGMENT);
1435
+ return {
1436
+ Level: issue.severity === "warning" ? "Warning" : "Error",
1437
+ Source: FLOW_VALIDATION_SOURCE,
1438
+ ...code !== undefined && { ErrorCode: code },
1439
+ Description: pathAddsLocation ? `${issue.path}: ${description}` : description,
1440
+ FilePath: flowFilePath,
1441
+ ...nodeId !== undefined && { ActivityIdRef: nodeId }
1442
+ };
1443
+ }
1025
1444
 
1026
1445
  // src/flow-tool-factory.ts
1027
1446
  class FlowToolFactory {
@@ -1038,6 +1457,7 @@ export {
1038
1457
  FlowToolFactory,
1039
1458
  RuntimeProfile,
1040
1459
  SCHEMA_CONTAINER_MARKER,
1460
+ agentJsonReader,
1041
1461
  applyRuntimeProfile,
1042
1462
  assertVoiceAgentDefinitionsEmbedded,
1043
1463
  buildInlineAgentContract,
@@ -1047,23 +1467,32 @@ export {
1047
1467
  createProcessBindings,
1048
1468
  ensureProcessBindings,
1049
1469
  extractProcessGuid,
1470
+ findProjectFlowFile,
1471
+ hydrateAgentNodeInputs,
1050
1472
  hydrateInlineAgentInputVariables,
1473
+ inlineFlowRefs,
1051
1474
  isInlineAgentNodeType,
1052
1475
  isMaestroAutomateSentinel,
1053
1476
  leafToBinding,
1054
1477
  migrateRawWorkflow,
1055
1478
  operateRuntimeOptions,
1479
+ parseFlowSource,
1056
1480
  readFlowWorkflow,
1057
1481
  readInlineAgentSource,
1058
1482
  reconcileInlineAgentInputVariables,
1059
1483
  replaceExporterVersion,
1060
1484
  resolveAndMigrateWorkflow,
1485
+ resolveProjectFlowFile,
1061
1486
  resolveRuntimeProfile,
1062
1487
  resolveRuntimeProfileForFlowFile,
1063
1488
  resolveWorkflowRefs,
1489
+ runDesignerRules,
1064
1490
  setPublishIntentOnInlineAgents,
1491
+ tryFindProjectFlowFile,
1492
+ tryResolveProjectFlowFile,
1493
+ validateFlowSource,
1065
1494
  voiceConvertOptions,
1066
1495
  withRuntimeProfile
1067
1496
  };
1068
1497
 
1069
- //# debugId=57F20301CD791DCF64756E2164756E21
1498
+ //# debugId=00A47C9E92F8453364756E2164756E21
@@ -1,3 +1,4 @@
1
+ import type { IFileSystem } from "@uipath/filesystem";
1
2
  /**
2
3
  * Resolve the inline-agent's projectId UUID from a flow node. Falls back to
3
4
  * the legacy `model.source` / `agentProjectId` aliases so packaging tolerates
@@ -8,6 +9,14 @@ export declare function readInlineAgentSource(node: {
8
9
  inputs?: Record<string, unknown>;
9
10
  model?: Record<string, unknown>;
10
11
  }): string | undefined;
12
+ /**
13
+ * An inline agent's `agent.json`, looked up by the agent's source id.
14
+ * `undefined` when there is none, or it cannot be read or parsed — the agent
15
+ * then stays as stored and the rules report what it is missing.
16
+ */
17
+ export type ReadAgentJson = (sourceId: string) => Promise<Record<string, unknown> | undefined>;
18
+ /** Reads `<flowDir>/<sourceId>/agent.json` through the caller's file system. */
19
+ export declare function agentJsonReader(fs: IFileSystem, flowDir: string): ReadAgentJson;
11
20
  /** True for a known inline-agent node type. */
12
21
  export declare function isInlineAgentNodeType(type: string | undefined): boolean;
13
22
  /** One flow-node `agentInputVariables[]` entry. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/packager-tool-flow",
3
- "version": "1.203.0-preview.212",
3
+ "version": "1.204.0-preview.211",
4
4
  "description": "UiPath Flow tool implementation",
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,13 +22,14 @@
22
22
  "author": "UiPath",
23
23
  "license": "SEE LICENSE IN LICENSE.txt",
24
24
  "peerDependencies": {
25
- "@uipath/filesystem": "1.203.0",
25
+ "@uipath/filesystem": "1.204.0",
26
26
  "@uipath/flow-converter": "0.69.3",
27
27
  "@uipath/flow-core": "0.115.4-develop",
28
28
  "@uipath/flow-migrations": "0.56.2",
29
29
  "@uipath/flow-schema": "0.68.0",
30
- "@uipath/solutionpackager-tool-core": "1.203.0",
31
- "@uipath/tool-agent": "^2.0.0"
30
+ "@uipath/solutionpackager-tool-core": "1.204.0",
31
+ "@uipath/tool-agent": "^2.0.0",
32
+ "i18next": "^23.16.8"
32
33
  },
33
- "gitHead": "15094ce85cf76c59a54affbed3304febc271f41a"
34
+ "gitHead": "275e01f1f6098d4c3bc9673f5225f33372822534"
34
35
  }