@uipath/maestro-builder-sdk 6.57.0 → 6.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/case/subscription.d.ts +4 -2
  2. package/dist/case/subscription.js +5 -2
  3. package/dist/core/actions.js +2 -2
  4. package/dist/core/binding-messages.d.ts +2 -1
  5. package/dist/core/binding-messages.js +3 -4
  6. package/dist/core/byoa.d.ts +3 -2
  7. package/dist/core/byoa.js +3 -3
  8. package/dist/core/carried-definitions.d.ts +0 -18
  9. package/dist/core/carried-definitions.js +3 -2
  10. package/dist/core/ceql.d.ts +2 -1
  11. package/dist/core/ceql.js +3 -2
  12. package/dist/core/compile-refusal.d.ts +21 -0
  13. package/dist/core/compile-refusal.js +26 -0
  14. package/dist/core/connector-inputs.js +2 -1
  15. package/dist/core/connector-resolve-error.d.ts +2 -1
  16. package/dist/core/connector-resolve-error.js +3 -4
  17. package/dist/core/connectors.js +2 -1
  18. package/dist/core/event-filters.js +2 -1
  19. package/dist/core/hitl-routing.d.ts +7 -82
  20. package/dist/core/hitl-routing.js +95 -6
  21. package/dist/core/library.js +4 -3
  22. package/dist/core/lookups.js +6 -5
  23. package/dist/core/nodes/registry.js +3 -2
  24. package/dist/core/nodes/versioned.js +3 -2
  25. package/dist/core/process-resource-sort-order.js +1 -1
  26. package/dist/core/read-text.d.ts +0 -28
  27. package/dist/core/read-text.js +3 -2
  28. package/dist/core/refusals.d.ts +465 -0
  29. package/dist/core/refusals.js +504 -0
  30. package/dist/core/script-function.js +2 -1
  31. package/dist/core/typescript-module.js +2 -1
  32. package/dist/edit-scope.d.ts +180 -0
  33. package/dist/edit-scope.js +251 -0
  34. package/dist/flow-expr-check.d.ts +2 -1
  35. package/dist/flow-expr-check.js +4 -2
  36. package/dist/flow-sdk.js +17 -16
  37. package/dist/flow-step-tree.js +1 -1
  38. package/dist/flow-variables.js +3 -2
  39. package/dist/load.js +7 -6
  40. package/dist/merge-cli.js +4 -2
  41. package/dist/merge-compile.d.ts +5 -88
  42. package/dist/merge-compile.js +8 -138
  43. package/dist/merge.d.ts +1 -61
  44. package/dist/merge.js +1 -112
  45. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  46. package/dist/nodes/connector/flow/node-type.js +2 -1
  47. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  48. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  49. package/dist/nodes/delay/flow/factories.js +2 -1
  50. package/dist/nodes/document/flow/ixp.js +5 -1
  51. package/dist/nodes/hitl/flow/check.js +4 -2
  52. package/dist/nodes/hitl/flow/node-type.js +6 -5
  53. package/dist/nodes/http/flow/factories.js +2 -1
  54. package/dist/nodes/http/flow/node-type.js +6 -5
  55. package/dist/nodes/process/flow/factories.js +2 -1
  56. package/dist/nodes/queue/flow/node-type.js +3 -2
  57. package/dist/nodes/transform/flow/node-type.js +2 -1
  58. package/dist/raw-node.js +3 -2
  59. package/dist/ref-resolve.d.ts +0 -36
  60. package/dist/ref-resolve.js +9 -8
  61. package/dist/schema-version.d.ts +0 -38
  62. package/dist/schema-version.js +3 -2
  63. package/dist/sdk-resolve.js +2 -1
  64. package/dist/serialize.d.ts +5 -2
  65. package/dist/serialize.js +115 -83
  66. package/package.json +2 -2
package/dist/load.js CHANGED
@@ -5,6 +5,7 @@ import { statSync } from 'node:fs';
5
5
  import { useThisSdkForAuthoredSource } from './sdk-resolve.js';
6
6
  import { scriptFunctionLowerings } from './core/script-function.js';
7
7
  import { scriptFunctionScopeProblem } from './script-function-scope.js';
8
+ import { CompileRefusal } from './core/compile-refusal.js';
8
9
  /** Resolve a user argument (a `.ts` path or a BaseName) to a flow source file. */
9
10
  export function resolveFlowFile(arg) {
10
11
  const candidates = [arg, `${arg}.flow.ts`, `${arg}.ts`];
@@ -19,7 +20,7 @@ export function resolveFlowFile(arg) {
19
20
  }
20
21
  return resolvedCandidate;
21
22
  }
22
- throw new Error(`flow source not found: tried ${candidates.join(', ')}`);
23
+ throw new CompileRefusal('FLOW_SOURCE_NOT_FOUND', `flow source not found: tried ${candidates.join(', ')}`);
23
24
  }
24
25
  export async function loadBuiltFlow(file) {
25
26
  useThisSdkForAuthoredSource();
@@ -33,10 +34,10 @@ export async function loadBuiltFlow(file) {
33
34
  }
34
35
  const exp = mod.default ?? mod.flow ?? mod.workflow;
35
36
  if (!exp)
36
- throw new Error(`${file} has no default export. Add: export default flow('id')...build();`);
37
+ throw new CompileRefusal('FLOW_NO_DEFAULT_EXPORT', `${file} has no default export. Add: export default flow('id')...build();`);
37
38
  const built = typeof exp.build === 'function' ? exp.build() : exp;
38
39
  if (!built || !Array.isArray(built.steps))
39
- throw new Error(`${file} default export is not a built flow. Did you call .build()?`);
40
+ throw new CompileRefusal('FLOW_EXPORT_NOT_BUILT', `${file} default export is not a built flow. Did you call .build()?`);
40
41
  // A `script(fn)` function was lowered from its own text, which cannot show a
41
42
  // name it takes from its module; this source can (script-function-scope.ts).
42
43
  // Only a flow that lowered one pays for the program this builds.
@@ -45,7 +46,7 @@ export async function loadBuiltFlow(file) {
45
46
  if (lowered.length > 0) {
46
47
  const problem = scriptFunctionScopeProblem(file, lowered);
47
48
  if (problem)
48
- throw new Error(problem);
49
+ throw new CompileRefusal('SCRIPT_FUNCTION_SCOPE', problem);
49
50
  }
50
51
  return built;
51
52
  }
@@ -96,7 +97,7 @@ export function connectorImportDiagnostic(error) {
96
97
  return undefined;
97
98
  }
98
99
  if (parent === 'connectors') {
99
- return new Error(`connector import "${unresolved}" does not name a file. The generated ` +
100
+ return new CompileRefusal('CONNECTOR_IMPORT_NOT_A_FILE', `connector import "${unresolved}" does not name a file. The generated ` +
100
101
  `descriptors are TypeScript, so the specifier keeps its real extension; ` +
101
102
  `import "${moduleFile}"`);
102
103
  }
@@ -104,7 +105,7 @@ export function connectorImportDiagnostic(error) {
104
105
  if (!statSync(descriptorData, { throwIfNoEntry: false })?.isDirectory()) {
105
106
  return undefined;
106
107
  }
107
- return new Error(`connector import "${unresolved}" does not name a file; module candidate ` +
108
+ return new CompileRefusal('CONNECTOR_IMPORT_NOT_A_FILE', `connector import "${unresolved}" does not name a file; module candidate ` +
108
109
  `"${moduleFile}" and descriptor data "${descriptorData}" are separate; ` +
109
110
  `import "${moduleFile}"`);
110
111
  }
package/dist/merge-cli.js CHANGED
@@ -9,7 +9,8 @@
9
9
  * `--baseline-from <original.flow>` makes merge build the baseline itself
10
10
  * (decompile + compile, nothing written), and an edited `.flow.ts` makes merge
11
11
  * compile it: both compiles tolerate DRAFT nodes, and merge refuses a draft
12
- * only where it would take the node from the edited flow. See merge-compile.ts.
12
+ * only where it would take the node from the edited flow. See merge-compile.ts
13
+ * and edit-scope.ts.
13
14
  *
14
15
  * `edited.flow` is compile(decompile(original) after your edits). `--baseline`
15
16
  * is compile(decompile(original)) BEFORE any edit — supplying it enables the
@@ -55,7 +56,8 @@ import { flowCheckOpts, openCliLibrary } from './cli-library.js';
55
56
  import { Bindings } from './bindings.js';
56
57
  import { defaultBindingsFile } from './workdir.js';
57
58
  import { loadNodePackages } from './node-packages.js';
58
- import { baselineFromOriginal, checkEdited, classifyDrafts, compileSourceDraftTolerant, findingCounts, splitUnplaced } from './merge-compile.js';
59
+ import { baselineFromOriginal, checkEdited, compileSourceDraftTolerant } from './merge-compile.js';
60
+ import { classifyDrafts, findingCounts, splitUnplaced } from './edit-scope.js';
59
61
  const USAGE = 'usage: flow-merge <original.flow> <edited.flow | Edited.flow.ts> [-o merged.flow] '
60
62
  + '[--baseline <baseline.flow> | --baseline-from <original.flow>] [--library <dir>] [--bindings <file>] [--strict]';
61
63
  /**
@@ -1,5 +1,6 @@
1
1
  import { type DraftFinding, type SerializeOpts } from './serialize.js';
2
- import { type CheckOpts, type Diagnostic } from './check.js';
2
+ import { type CheckOpts } from './check.js';
3
+ import { type PlacedDiagnostic } from './edit-scope.js';
3
4
  import type { BuiltFlow } from './flow-sdk.js';
4
5
  /**
5
6
  * A compile's flow, the draft refusals it reported instead of throwing, and the
@@ -30,94 +31,10 @@ export declare function compileSourceDraftTolerant(file: string, opts?: Omit<Ser
30
31
  export declare function baselineFromOriginal(originalPath: string, opts?: Omit<SerializeOpts, 'draft'>, warn?: (message: string) => void): Promise<DraftTolerantCompile & {
31
32
  built: BuiltFlow;
32
33
  }>;
33
- /**
34
- * Why a finding is an error: `edited`, its node is one the author changed or
35
- * added; `subflow`, it sits in a subflow the edit changed, which merge takes
36
- * whole; `unplaced`, it names no node and the original's own round trip does
37
- * not report it, so the edit introduced it.
38
- */
39
- export type TakenReason = 'edited' | 'subflow' | 'unplaced';
40
- /**
41
- * A finding on a node merge takes from the edited flow, with the reason it
42
- * takes it: `edited`, the author changed or added the node; `subflow`, the
43
- * node sits in a subflow the edit changed, which merge takes whole (`subflow`
44
- * names it).
45
- */
46
- export type Refused<T> = T & {
47
- reason: TakenReason;
48
- subflow?: string;
49
- };
50
- /** A finding on a node merge restores from the original; `subflow` names the untouched subflow it sits in. */
51
- export type Kept<T> = T & {
52
- subflow?: string;
53
- };
54
- /**
55
- * Sort each finding by what merge does with its node: `refused` when the node
56
- * comes from the edited flow (reason `edited`: the author changed or added it;
57
- * reason `subflow`: it sits in a subflow the edit changed, which merge takes
58
- * whole), `kept` when merge restores the original node — a root node the edit
59
- * did not touch, or any node of a subflow the edit did not touch (#1322) — and
60
- * `unplaced` when the finding names no node merge knows (an unnamed End's
61
- * `.return`, a flow-level finding). The caller sorts those again with
62
- * {@link splitUnplaced}: one the original's own round trip reports too is
63
- * printed, one the edit introduced is refused.
64
- *
65
- * For instance a Slack node with no connection that the author did not touch
66
- * is `kept`; the same node with a new message text is `refused` as `edited`.
67
- */
68
- export declare function classifyDrafts<T extends {
69
- nodeId: string;
70
- subflow?: string;
71
- }>(original: any, edited: any, baseline: any, findings: readonly T[]): {
72
- kept: Kept<T>[];
73
- refused: Refused<T>[];
74
- unplaced: T[];
75
- };
76
- /**
77
- * A `check()` diagnostic, placed on the node its step emitted (`''` when it
78
- * names no node); `subflow` is the calling step when the finding is inside a
79
- * subflow, which then decides how it is classified.
80
- */
81
- export type PlacedDiagnostic = Diagnostic & {
82
- nodeId: string;
83
- subflow?: string;
84
- };
85
34
  /**
86
35
  * Run `check()` on the edited source, as `compile` does, and place each
87
- * diagnostic on the node its step emitted in `editedFlow`, so
88
- * {@link classifyDrafts} can sort it: a root step on its node (by label, then
89
- * id); a step inside a subflow (`check` reports it as `<caller> › <step>`, one
90
- * segment per level) on the deepest step the path resolves to, with `subflow`
91
- * set to the deepest subflow on that path, which is what decides; a named End
92
- * on its End node (#1322). A diagnostic with no step, or one no node answers
93
- * to, gets nodeId `''`.
36
+ * diagnostic on its node ({@link placeDiagnostics}). The `check()` call stays
37
+ * here, not in `edit-scope.ts`, so `check.ts` can import that module without a
38
+ * load-time cycle.
94
39
  */
95
40
  export declare function checkEdited(built: BuiltFlow, editedFlow: any, opts: CheckOpts): PlacedDiagnostic[];
96
- /** The identity of a finding for the introduced-or-not comparison: its code and message. */
97
- export declare function findingKey(f: {
98
- code: string;
99
- message: string;
100
- }): string;
101
- /** How many times each {@link findingKey} occurs, for {@link splitUnplaced}'s baseline. */
102
- export declare function findingCounts(findings: ReadonlyArray<{
103
- code: string;
104
- message: string;
105
- }>): Map<string, number>;
106
- /**
107
- * Sort findings merge could not place on a node by whether the edit introduced
108
- * them: `known` when the ORIGINAL's own round trip (`baseline`, the decompiled
109
- * original compiled and checked, as {@link findingCounts}) reports the same
110
- * code and message — once per occurrence, so a second copy of a finding the
111
- * original has once is `introduced` — so the finding predates the edit and
112
- * only prints; `introduced` otherwise, which is refused like any error on an
113
- * edited node (#1322). With no baseline findings to compare against (a
114
- * precompiled `--baseline` file), every one is `introduced`, as merge treated
115
- * them before.
116
- */
117
- export declare function splitUnplaced<T extends {
118
- code: string;
119
- message: string;
120
- }>(unplaced: readonly T[], baseline: ReadonlyMap<string, number> | undefined): {
121
- known: T[];
122
- introduced: T[];
123
- };
@@ -4,7 +4,8 @@
4
4
  * Both compiles tolerate draft nodes (`SerializeOpts.draft`). A draft on a node
5
5
  * merge keeps from the original is a warning; a draft or `check()` error on a
6
6
  * node merge takes from the edited flow is an error. Nothing draft-tolerant is
7
- * written to disk.
7
+ * written to disk. Which nodes count as edited, and how each finding is sorted,
8
+ * is edit-scope.ts's.
8
9
  */
9
10
  import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
10
11
  import { tmpdir } from 'node:os';
@@ -12,9 +13,9 @@ import { dirname, join, resolve } from 'node:path';
12
13
  import { decompile } from './decompile.js';
13
14
  import { loadBuiltFlow } from './load.js';
14
15
  import { serialize } from './serialize.js';
15
- import { check } from './check.js';
16
- import { nodesTakenFromEdited, subflowsTakenFromEdited } from './merge.js';
17
16
  import { PACKAGE_NAME } from './package-name.js';
17
+ import { check } from './check.js';
18
+ import { placeDiagnostics } from './edit-scope.js';
18
19
  /** Compile `built` with draft tolerance. Every other refusal still throws. */
19
20
  export function compileDraftTolerant(built, opts = {}) {
20
21
  const drafts = [];
@@ -73,143 +74,12 @@ function baselineDirectory(originalPath) {
73
74
  return mkdtempSync(join(tmpdir(), 'flow-merge-baseline-'));
74
75
  }
75
76
  }
76
- /**
77
- * Sort each finding by what merge does with its node: `refused` when the node
78
- * comes from the edited flow (reason `edited`: the author changed or added it;
79
- * reason `subflow`: it sits in a subflow the edit changed, which merge takes
80
- * whole), `kept` when merge restores the original node — a root node the edit
81
- * did not touch, or any node of a subflow the edit did not touch (#1322) — and
82
- * `unplaced` when the finding names no node merge knows (an unnamed End's
83
- * `.return`, a flow-level finding). The caller sorts those again with
84
- * {@link splitUnplaced}: one the original's own round trip reports too is
85
- * printed, one the edit introduced is refused.
86
- *
87
- * For instance a Slack node with no connection that the author did not touch
88
- * is `kept`; the same node with a new message text is `refused` as `edited`.
89
- */
90
- export function classifyDrafts(original, edited, baseline, findings) {
91
- const fromEdited = new Set(nodesTakenFromEdited(original, edited, baseline));
92
- const takenSubflows = new Set(subflowsTakenFromEdited(original, edited, baseline));
93
- const rootIds = new Set((edited?.nodes ?? []).map((n) => n.id));
94
- /** A subflow's own node ids → the key (the calling step) of the subflow holding them. */
95
- const subflowOfNode = new Map();
96
- for (const [key, sf] of Object.entries(edited?.subflows ?? {})) {
97
- for (const n of sf?.nodes ?? [])
98
- subflowOfNode.set(n.id, key);
99
- }
100
- const kept = [];
101
- const refused = [];
102
- const unplaced = [];
103
- for (const f of findings) {
104
- // A finding inside a subflow — placed on the calling step by `checkEdited`
105
- // (`subflow` set), or a draft on one of the subflow's own nodes — is judged
106
- // by the SUBFLOW: taken whole when the edit changed it, else the original's.
107
- const subflow = f.subflow ?? subflowOfNode.get(f.nodeId);
108
- if (subflow !== undefined) {
109
- if (takenSubflows.has(subflow))
110
- refused.push({ ...f, reason: 'subflow', subflow });
111
- else
112
- kept.push({ ...f, subflow });
113
- }
114
- else if (rootIds.has(f.nodeId)) {
115
- if (fromEdited.has(f.nodeId))
116
- refused.push({ ...f, reason: 'edited' });
117
- else
118
- kept.push(f);
119
- }
120
- else {
121
- unplaced.push(f);
122
- }
123
- }
124
- return { kept, refused, unplaced };
125
- }
126
77
  /**
127
78
  * Run `check()` on the edited source, as `compile` does, and place each
128
- * diagnostic on the node its step emitted in `editedFlow`, so
129
- * {@link classifyDrafts} can sort it: a root step on its node (by label, then
130
- * id); a step inside a subflow (`check` reports it as `<caller> › <step>`, one
131
- * segment per level) on the deepest step the path resolves to, with `subflow`
132
- * set to the deepest subflow on that path, which is what decides; a named End
133
- * on its End node (#1322). A diagnostic with no step, or one no node answers
134
- * to, gets nodeId `''`.
79
+ * diagnostic on its node ({@link placeDiagnostics}). The `check()` call stays
80
+ * here, not in `edit-scope.ts`, so `check.ts` can import that module without a
81
+ * load-time cycle.
135
82
  */
136
83
  export function checkEdited(built, editedFlow, opts) {
137
- const byStep = new Map();
138
- for (const n of editedFlow?.nodes ?? []) {
139
- const label = n?.display?.label;
140
- if (typeof label === 'string' && !byStep.has(label))
141
- byStep.set(label, n.id);
142
- }
143
- const ids = new Set((editedFlow?.nodes ?? []).map((n) => n.id));
144
- /** `name` as a node id in `nodes`: by label, then by id; `''` when neither answers. */
145
- const resolveIn = (nodes, name) => {
146
- const byLabel = nodes.find((n) => n?.display?.label === name);
147
- if (byLabel)
148
- return byLabel.id;
149
- return nodes.some((n) => n?.id === name) ? name : '';
150
- };
151
- // `subflows` is flat, keyed by each caller's id at every depth, and check
152
- // prefixes a finding at every level (`outer › inner › x`): the subflow that
153
- // decides is the DEEPEST one on the path, since merge keeps or takes each
154
- // subflow on its own (an edit inside `inner` leaves `outer` kept).
155
- const place = (step) => {
156
- if (step === undefined)
157
- return { nodeId: '' };
158
- const segments = step.split(SUBFLOW_STEP_SEPARATOR);
159
- let scope = editedFlow?.nodes ?? [];
160
- let subflow;
161
- let nodeId = '';
162
- for (const [i, segment] of segments.entries()) {
163
- const id = i === 0 ? (byStep.get(segment) ?? (ids.has(segment) ? segment : '')) : resolveIn(scope, segment);
164
- if (id === '')
165
- break;
166
- nodeId = id;
167
- const child = i < segments.length - 1 ? editedFlow?.subflows?.[id] : undefined;
168
- if (child === undefined)
169
- break;
170
- subflow = id;
171
- scope = child.nodes ?? [];
172
- }
173
- return subflow !== undefined ? { nodeId: nodeId === '' ? subflow : nodeId, subflow } : { nodeId };
174
- };
175
- return check(built, opts).map((d) => ({ ...d, ...place(d.step) }));
176
- }
177
- /** The identity of a finding for the introduced-or-not comparison: its code and message. */
178
- export function findingKey(f) {
179
- return `${f.code}\n${f.message}`;
180
- }
181
- /** How many times each {@link findingKey} occurs, for {@link splitUnplaced}'s baseline. */
182
- export function findingCounts(findings) {
183
- const counts = new Map();
184
- for (const f of findings)
185
- counts.set(findingKey(f), (counts.get(findingKey(f)) ?? 0) + 1);
186
- return counts;
187
- }
188
- /**
189
- * Sort findings merge could not place on a node by whether the edit introduced
190
- * them: `known` when the ORIGINAL's own round trip (`baseline`, the decompiled
191
- * original compiled and checked, as {@link findingCounts}) reports the same
192
- * code and message — once per occurrence, so a second copy of a finding the
193
- * original has once is `introduced` — so the finding predates the edit and
194
- * only prints; `introduced` otherwise, which is refused like any error on an
195
- * edited node (#1322). With no baseline findings to compare against (a
196
- * precompiled `--baseline` file), every one is `introduced`, as merge treated
197
- * them before.
198
- */
199
- export function splitUnplaced(unplaced, baseline) {
200
- const known = [];
201
- const introduced = [];
202
- const left = new Map(baseline ?? []);
203
- for (const f of unplaced) {
204
- const n = left.get(findingKey(f)) ?? 0;
205
- if (n > 0) {
206
- left.set(findingKey(f), n - 1);
207
- known.push(f);
208
- }
209
- else
210
- introduced.push(f);
211
- }
212
- return { known, introduced };
84
+ return placeDiagnostics(check(built, opts), editedFlow);
213
85
  }
214
- /** How `check` joins a subflow's calling step and the step inside it in a diagnostic's `step`. */
215
- const SUBFLOW_STEP_SEPARATOR = ' › ';
package/dist/merge.d.ts CHANGED
@@ -87,50 +87,9 @@
87
87
  * original plus the author's change to the node. See merge-embedded-agent.ts.
88
88
  */
89
89
  import { type MergeSidecarSources, type SidecarWrite } from './merge-sidecars.js';
90
+ import { type AnyFlow } from './edit-scope.js';
90
91
  export { MergeRefusedError, type MergeFinding } from './merge-roundtrip.js';
91
92
  export type { MergeSidecarSources, SidecarSet, SidecarWrite } from './merge-sidecars.js';
92
- interface AnyNode {
93
- id: string;
94
- type: string;
95
- typeVersion?: string;
96
- parentId?: string;
97
- ui?: unknown;
98
- display?: unknown;
99
- inputs?: Record<string, unknown>;
100
- outputs?: Record<string, unknown>;
101
- [k: string]: unknown;
102
- }
103
- interface AnyEdge {
104
- id?: string;
105
- sourceNodeId: string;
106
- sourcePort: string;
107
- targetNodeId: string;
108
- targetPort: string;
109
- [k: string]: unknown;
110
- }
111
- interface AnyFlow {
112
- id?: string;
113
- name?: string;
114
- version?: string;
115
- nodes?: AnyNode[];
116
- edges?: AnyEdge[];
117
- definitions?: Array<{
118
- nodeType?: string;
119
- [k: string]: unknown;
120
- }>;
121
- variables?: unknown;
122
- subflows?: Record<string, unknown>;
123
- bindings?: Array<{
124
- id?: string;
125
- [k: string]: unknown;
126
- }>;
127
- metadata?: unknown;
128
- solutionId?: unknown;
129
- projectId?: unknown;
130
- layout?: unknown;
131
- runtime?: unknown;
132
- [k: string]: unknown;
133
- }
134
93
  export interface MergeOptions {
135
94
  /**
136
95
  * compile(decompile(original)) captured BEFORE the user's edits. Enables the
@@ -172,25 +131,6 @@ export interface MergeOptions {
172
131
  */
173
132
  strict?: boolean;
174
133
  }
175
- /**
176
- * The ids of the root nodes {@link mergeFlow} takes from `edited` rather than
177
- * restoring from `original`: every node that is new, or that differs from its
178
- * `baseline` counterpart. With no baseline, that is every node.
179
- *
180
- * For instance, with a baseline equal to `edited` and every node present in
181
- * `original`, the answer is `[]` — a no-edit merge keeps them all.
182
- */
183
- export declare function nodesTakenFromEdited(original: AnyFlow, edited: AnyFlow, baseline?: AnyFlow): string[];
184
- /**
185
- * The keys of the subflows {@link mergeFlow} takes from `edited` rather than
186
- * restoring from `original`: every subflow that is new, that the original does
187
- * not have, or that differs from its `baseline` counterpart in any way the
188
- * author can edit — a node's inputs, output sources or label, an edge, the
189
- * scope's `variables` or `layout` (`subflowUnchanged`, #1322). With no
190
- * baseline, that is every subflow. A kept subflow is the original's whole: its
191
- * nodes, edges, variables and layout.
192
- */
193
- export declare function subflowsTakenFromEdited(original: AnyFlow, edited: AnyFlow, baseline?: AnyFlow): string[];
194
134
  export declare function mergeFlow(original: AnyFlow, edited: AnyFlow, options?: MergeOptions): AnyFlow;
195
135
  /**
196
136
  * `mergeFlow`, plus the agent sidecar files the merged flow needs written beside
package/dist/merge.js CHANGED
@@ -95,119 +95,8 @@ import { placeAddedNodes } from './node-placement.js';
95
95
  import { mergeAgentSidecars } from './merge-sidecars.js';
96
96
  import { embeddedAgentFindings, isEmbeddedAgent, mergeEmbeddedAgentNode } from './merge-embedded-agent.js';
97
97
  import { mergeObject, mergeVariables, stableJson } from './merge-variables.js';
98
+ import { byId, isKept, nodeUnchanged, subflowsTakenFromEdited } from './edit-scope.js';
98
99
  export { MergeRefusedError } from './merge-roundtrip.js';
99
- function byId(nodes) {
100
- return new Map((nodes ?? []).map((n) => [n.id, n]));
101
- }
102
- /**
103
- * Whether two nodes are the SAME as far as the author is concerned — same type,
104
- * same inputs and same output mappings. Layout (`ui`), `display`, `typeVersion`,
105
- * and the derived parts of `outputs` (type, schema, description) are ignored,
106
- * because those are exactly the parts decompile does not carry through. An
107
- * output's `source` is authored, not derived: it is what an End returns, so an
108
- * edit to `.return({ ... })` changes nothing else on the node. Used only to
109
- * decide "did the user edit this node?" against the baseline.
110
- */
111
- function nodeUnchanged(a, b) {
112
- if (!a || !b)
113
- return false;
114
- if (a.type !== b.type)
115
- return false;
116
- return JSON.stringify(a.inputs ?? {}) === JSON.stringify(b.inputs ?? {})
117
- && JSON.stringify(outputSources(a)) === JSON.stringify(outputSources(b));
118
- }
119
- /** Each output's `source` by output name; outputs without one are left out. */
120
- function outputSources(n) {
121
- const sources = {};
122
- for (const [name, output] of Object.entries(n.outputs ?? {})) {
123
- if (output && typeof output === 'object' && 'source' in output)
124
- sources[name] = output.source;
125
- }
126
- return sources;
127
- }
128
- /**
129
- * The ids of the root nodes {@link mergeFlow} takes from `edited` rather than
130
- * restoring from `original`: every node that is new, or that differs from its
131
- * `baseline` counterpart. With no baseline, that is every node.
132
- *
133
- * For instance, with a baseline equal to `edited` and every node present in
134
- * `original`, the answer is `[]` — a no-edit merge keeps them all.
135
- */
136
- export function nodesTakenFromEdited(original, edited, baseline) {
137
- const origNodes = byId(original.nodes);
138
- const baseNodes = byId(baseline?.nodes);
139
- return (edited.nodes ?? []).filter((e) => !isKept(e, origNodes, baseNodes, baseline)).map((e) => e.id);
140
- }
141
- /**
142
- * The keys of the subflows {@link mergeFlow} takes from `edited` rather than
143
- * restoring from `original`: every subflow that is new, that the original does
144
- * not have, or that differs from its `baseline` counterpart in any way the
145
- * author can edit — a node's inputs, output sources or label, an edge, the
146
- * scope's `variables` or `layout` (`subflowUnchanged`, #1322). With no
147
- * baseline, that is every subflow. A kept subflow is the original's whole: its
148
- * nodes, edges, variables and layout.
149
- */
150
- export function subflowsTakenFromEdited(original, edited, baseline) {
151
- return Object.keys(edited.subflows ?? {}).filter((key) => {
152
- const orig = original.subflows?.[key];
153
- const base = baseline?.subflows?.[key];
154
- return !baseline || orig === undefined || base === undefined
155
- || !subflowUnchanged(edited.subflows[key], base);
156
- });
157
- }
158
- /** A node's canvas label, when it has one. */
159
- const labelOf = (n) => n?.display?.label;
160
- /**
161
- * Whether an edited subflow equals its baseline: the same node ids, each node
162
- * unchanged (`nodeUnchanged`) and under the same label, the same edges by
163
- * their endpoints and handles (ids are compile's own), and the same
164
- * `variables` and `layout` byte for byte. The scope's variables carry what the
165
- * nodes do not — a step's `{ updates }` row, a child `.input()` type, a `.var()`
166
- * default — and a kept subflow is the original's whole, so any of them changing
167
- * has to make the subflow a taken one.
168
- */
169
- function subflowUnchanged(e, b) {
170
- const eNodes = byId(e.nodes);
171
- const bNodes = byId(b.nodes);
172
- if (eNodes.size !== bNodes.size)
173
- return false;
174
- for (const [id, n] of eNodes) {
175
- const base = bNodes.get(id);
176
- if (!nodeUnchanged(n, base) || labelOf(n) !== labelOf(base))
177
- return false;
178
- }
179
- if (JSON.stringify(e.variables ?? null) !== JSON.stringify(b.variables ?? null))
180
- return false;
181
- // `layout.edges` is keyed by edge id, and ids come from one file-wide
182
- // counter: a step added before the call shifts every id in the subflow
183
- // without touching it. So routes are compared by the endpoints the id names
184
- // (as the edges themselves are), and `layout.nodes` as it is.
185
- if (JSON.stringify(layoutByEndpoints(e)) !== JSON.stringify(layoutByEndpoints(b)))
186
- return false;
187
- const eEdges = (e.edges ?? []).map(edgeKey).sort();
188
- const bEdges = (b.edges ?? []).map(edgeKey).sort();
189
- return eEdges.length === bEdges.length && eEdges.every((k, i) => k === bEdges[i]);
190
- }
191
- /** `<source>@<port>-><target>@<port>`: an edge's identity apart from its id. */
192
- const edgeKey = (x) => `${x.sourceNodeId}@${x.sourcePort}->${x.targetNodeId}@${x.targetPort}`;
193
- /** A scope's layout with its `edges` re-keyed by endpoints, so an id shift does not read as a change. */
194
- function layoutByEndpoints(sf) {
195
- const layout = sf.layout;
196
- if (!layout || typeof layout !== 'object')
197
- return layout ?? null;
198
- const byId = new Map((sf.edges ?? []).map((x) => [x.id, edgeKey(x)]));
199
- const { edges, ...rest } = layout;
200
- const routes = Object.entries(edges ?? {}).map(([id, route]) => [byId.get(id) ?? id, route]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
201
- return { ...rest, edges: Object.fromEntries(routes) };
202
- }
203
- /**
204
- * Whether merge restores `e`'s ORIGINAL node instead of taking `e`: there is a
205
- * baseline, the original has the node, and `e` equals its baseline counterpart.
206
- * {@link mergeFlow}'s node loop and {@link nodesTakenFromEdited} both ask this.
207
- */
208
- function isKept(e, origNodes, baseNodes, baseline) {
209
- return !!baseline && origNodes.has(e.id) && nodeUnchanged(e, baseNodes.get(e.id));
210
- }
211
100
  /**
212
101
  * Give an edited node the original's position, by id.
213
102
  *
@@ -10,6 +10,7 @@ import { processResourceBindings } from '../../../core/process-resource.js';
10
10
  import { fnv1aX4Uuid, schemaId } from '../../../core/stable-id.js';
11
11
  import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
12
12
  import { BUILTIN_TOOL_DEFS, INLINE_AGENT, PROCESS_TOOL_META, a2aToolDef, builtinToolResourceJson, connectorToolDef, connectorToolDescription, connectorToolDetail, connectorToolResourceJson, contextIndexDef, contextResourceJson, declaresMemoryHandle, escalationAppFolder, escalationDef, escalationResourceJson, fieldSchema, httpRequestToolResourceJson, inlineAgentDef, ixpToolDef, mcpToolDef, memoryResourceDef, processToolDef, renderContextQuery, toolResourceJson, } from './resources.js';
13
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
13
14
  /** Every bundled version of the inline agent, oldest first; the newest is the default (#956). */
14
15
  export const INLINE_AGENT_VERSIONS = ['1.2', '1.3', '1.4'];
15
16
  export const INLINE_AGENT_DEFAULT_VERSION = INLINE_AGENT_VERSIONS[INLINE_AGENT_VERSIONS.length - 1];
@@ -39,7 +40,7 @@ export function serializeInlineAgent(s, ctx) {
39
40
  if (!def)
40
41
  throw new Error(`internal SDK error: step "${step}": no definition resolves for ${INLINE_AGENT}@${version}.`);
41
42
  if (s.memory !== undefined && !declaresMemoryHandle(def)) {
42
- throw new Error(`"${step}" attaches \`memory\`, but ${INLINE_AGENT}@${version} declares no \`memory\` handle. `
43
+ throw new CompileRefusal('AGENT_MEMORY_VERSION_NO_HANDLE', `"${step}" attaches \`memory\`, but ${INLINE_AGENT}@${version} declares no \`memory\` handle. `
43
44
  + (version === INLINE_AGENT_DEFAULT_VERSION
44
45
  ? `Pin a version that has it with inlineAgent({ ..., version: '<x.y>' }) `
45
46
  + `(bundled with memory: ${INLINE_AGENT_VERSIONS.filter((v) => declaresMemoryHandle(ctx.definition(INLINE_AGENT, v))).join(', ') || 'none'}), or drop \`memory\`.`
@@ -134,8 +135,8 @@ export function serializeInlineAgent(s, ctx) {
134
135
  // the type `…context.index.<slug>.`, which no tenant serves, and nothing
135
136
  // after this point would say so.
136
137
  if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
137
- throw Object.assign(new Error(`inline agent "${step}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
138
- + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
138
+ throw new CompileRefusal('AGENT_CONTEXT_NO_ID', `inline agent "${step}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
139
+ + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`);
139
140
  }
140
141
  const cdef = contextIndexDef({ name: cx.name, id: cx.id });
141
142
  // `<step>Context`, deduped for a second index on the same agent, unless the
@@ -207,7 +208,7 @@ export function serializeInlineAgent(s, ctx) {
207
208
  const bdef = ownEntry(BUILTIN_TOOL_DEFS, tr.tool);
208
209
  tdef = bdef === undefined ? undefined : jsonClone(bdef);
209
210
  if (!tdef)
210
- throw new Error(`inline agent "${step}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
211
+ throw new CompileRefusal('INLINE_AGENT_TOOL_UNKNOWN_BUILTIN', `inline agent "${step}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
211
212
  label = tdef.display.label;
212
213
  // The designer-placed node carries the def's `inputDefaults` with the
213
214
  // author's values over them — the context rule, kept per builtin (each def
@@ -242,7 +243,7 @@ export function serializeInlineAgent(s, ctx) {
242
243
  }
243
244
  else if (tr.kind === 'connector') {
244
245
  if (!ctx.library)
245
- throw new Error(`inline agent "${step}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
246
+ throw new CompileRefusal('LIBRARY_REQUIRED', `inline agent "${step}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
246
247
  const rc = ctx.library.resolve(tr.connector, tr.operation, tr.version, tr.object);
247
248
  // An entry `prepare` synthesized from Integration Service compiles in BPMN
248
249
  // only — for a tool as for a step: `connectorToolDef` reads the same
@@ -337,7 +338,7 @@ export function serializeInlineAgent(s, ctx) {
337
338
  else {
338
339
  const meta = ownEntry(PROCESS_TOOL_META, tr.kind);
339
340
  if (!meta)
340
- throw new Error(`inline agent "${step}": unknown tool kind "${tr.kind}".`);
341
+ throw new CompileRefusal('INLINE_AGENT_TOOL_UNKNOWN_KIND', `inline agent "${step}": unknown tool kind "${tr.kind}".`);
341
342
  tdef = processToolDef(tr.kind, tr);
342
343
  label = tr.name;
343
344
  // The definition's `model.context` names `<bindings.name>` /
@@ -9,6 +9,7 @@ import { jsonClone } from '../../../core/json-clone.js';
9
9
  import { subNodeId } from '../../../core/node-id-placeholder.js';
10
10
  import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
11
11
  import { tsLiteral, tsObject } from '../../../core/ts-literal.js';
12
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
12
13
  const CONNECTOR_PREFIX = 'uipath.connector.';
13
14
  /** `<key>.<action>` after the prefix, split on the last dot; undefined for an event, a trigger, or a name with no action. */
14
15
  function splitOperation(typeName) {
@@ -65,7 +66,7 @@ function listOutputs(rc, nodeId) {
65
66
  function serializeConnector(spec, ctx) {
66
67
  const step = ctx.step;
67
68
  if (!ctx.library)
68
- throw new Error(`connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
69
+ throw new CompileRefusal('LIBRARY_REQUIRED', `connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
69
70
  let rc;
70
71
  try {
71
72
  rc = ctx.library.resolve(spec.key, spec.action, spec.version, spec.object);
@@ -5,6 +5,7 @@
5
5
  * them, so authors import them exactly as before.
6
6
  */
7
7
  import { builtinActionSpec } from '../../../core/actions.js';
8
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
8
9
  /**
9
10
  * Query entity records from Data Fabric (`core.datafabric.read`). The result
10
11
  * publishes to the step's `output`; downstream references re-fetch fresh data
@@ -129,6 +130,6 @@ function assertOneRecordRef(record, factory) {
129
130
  const byId = record?.byId !== undefined;
130
131
  const fromRead = typeof record?.fromRead === 'string';
131
132
  if (byId === fromRead) {
132
- throw new Error(`${factory}() targets exactly ONE of record: { byId } or record: { fromRead: '<step>' }.`);
133
+ throw new CompileRefusal('DATAFABRIC_RECORD_REF', `${factory}() targets exactly ONE of record: { byId } or record: { fromRead: '<step>' }.`);
133
134
  }
134
135
  }
@@ -1,6 +1,7 @@
1
1
  import { applyScope, baseConfig, entityConfigOf, fieldRows, filterRows, liftFields, liftRecord, liftScope, liftValue, printFields, printRecord, scopeOptions, targetRecord, } from './shared.js';
2
2
  import { checkCreate, checkRead, checkUpdate, checkWrite } from './check.js';
3
3
  import { rawNodeAdvice } from './raw.js';
4
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
4
5
  /**
5
6
  * Every Data Fabric node carries no node-level `model`, whichever path emits it
6
7
  * (a verb, or a rawNode of its definition). Studio Web never writes one
@@ -36,7 +37,7 @@ function readAt(version) {
36
37
  nodeModel: NODE_MODEL, rawNode: READ_ADVICE,
37
38
  serialize: ({ inputs }, ctx) => {
38
39
  if (version === '1.0' && inputs.resultMode !== undefined) {
39
- throw new Error("dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
40
+ throw new CompileRefusal('DATAFABRIC_RESULT_MODE_VERSION', "dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
40
41
  + 'requested. Drop the version, request 1.4, or drop resultMode.');
41
42
  }
42
43
  const cfg = baseConfig(inputs);