@uipath/maestro-builder-sdk 6.43.0 → 6.44.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.
- package/dist/api-index.md +66 -65
- package/dist/api-members.md +228 -226
- package/dist/check.js +101 -7
- package/dist/core/actions.d.ts +9 -0
- package/dist/core/form-field-type.d.ts +7 -0
- package/dist/core/form-field-type.js +12 -2
- package/dist/decompile.js +204 -34
- package/dist/flow-expr-check.js +5 -0
- package/dist/flow-node-type.d.ts +9 -0
- package/dist/flow-sdk.d.ts +40 -5
- package/dist/flow-sdk.js +14 -1
- package/dist/flow-step-variables.d.ts +4 -0
- package/dist/flow-step-variables.js +22 -0
- package/dist/loop-version.d.ts +23 -6
- package/dist/loop-version.js +37 -15
- package/dist/merge.d.ts +5 -3
- package/dist/merge.js +20 -10
- package/dist/node-placement.d.ts +6 -5
- package/dist/node-placement.js +25 -10
- package/dist/nodes/hitl/flow/check.js +15 -0
- package/dist/nodes/hitl/flow/node-type.js +18 -3
- package/dist/nodes/process/flow/node-type.js +17 -2
- package/dist/serialize.js +56 -15
- package/package.json +2 -2
package/dist/flow-sdk.d.ts
CHANGED
|
@@ -444,6 +444,39 @@ export interface NodeOptions {
|
|
|
444
444
|
*/
|
|
445
445
|
label?: string;
|
|
446
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* The options of `.parallel()`: the join's {@link NodeOptions}, plus the fork
|
|
449
|
+
* node when the flow has one.
|
|
450
|
+
*/
|
|
451
|
+
export interface ParallelOptions extends NodeOptions {
|
|
452
|
+
/**
|
|
453
|
+
* A FORK node the arms leave from: a `core.logic.merge` with one incoming
|
|
454
|
+
* edge and one edge per arm, which is how Studio Web draws a parallel split
|
|
455
|
+
* (its "Parallel" gateway). Without it the arms leave from the previous
|
|
456
|
+
* step's own port, one edge per arm, which the platform runs the same way.
|
|
457
|
+
* Name it to round-trip a flow the canvas drew with a fork node; `label` is
|
|
458
|
+
* the canvas text, defaulting to the name, and `version` the fork node's
|
|
459
|
+
* exact `core.logic.merge` version when it differs from the join's.
|
|
460
|
+
*
|
|
461
|
+
* ```ts
|
|
462
|
+
* .parallel('join', [(a) => a.step('t1', …), (b) => b.step('t2', …)], { fork: 'split' })
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
fork?: string | {
|
|
466
|
+
name: string;
|
|
467
|
+
label?: string;
|
|
468
|
+
version?: string;
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* The fork node a `.parallel()` asks for, normalized, or `undefined`.
|
|
473
|
+
* @internal
|
|
474
|
+
*/
|
|
475
|
+
export declare function parallelFork(options: ParallelOptions | undefined): {
|
|
476
|
+
name: string;
|
|
477
|
+
label?: string;
|
|
478
|
+
version?: string;
|
|
479
|
+
} | undefined;
|
|
447
480
|
/**
|
|
448
481
|
* `.return(...)`-specific options.
|
|
449
482
|
*
|
|
@@ -588,15 +621,16 @@ export type Step = {
|
|
|
588
621
|
/**
|
|
589
622
|
* A fan-out and its join. `arms` is one step list per arm — an ARRAY, because
|
|
590
623
|
* arms are positional and carry no author-supplied keys — and `name` names the
|
|
591
|
-
* Merge node the arms converge on (the only node this step creates
|
|
592
|
-
*
|
|
624
|
+
* Merge node the arms converge on (the only node this step creates unless
|
|
625
|
+
* `options.fork` names a fork node too; otherwise the fork is just N edges
|
|
626
|
+
* out of one port).
|
|
593
627
|
*/
|
|
594
628
|
| {
|
|
595
629
|
kind: 'parallel';
|
|
596
630
|
name: string;
|
|
597
631
|
label?: string;
|
|
598
632
|
arms: Step[][];
|
|
599
|
-
options?:
|
|
633
|
+
options?: ParallelOptions;
|
|
600
634
|
}
|
|
601
635
|
/**
|
|
602
636
|
* A hard stop: `core.logic.terminate`, a BPMN End event carrying a
|
|
@@ -1137,11 +1171,12 @@ declare class StepList {
|
|
|
1137
1171
|
* @param arms - One callback per parallel arm, each receiving its own
|
|
1138
1172
|
* sub-builder. Two or more.
|
|
1139
1173
|
* @param options - Node options; `version` selects the exact definition
|
|
1140
|
-
* version (see {@link NodeOptions})
|
|
1174
|
+
* version (see {@link NodeOptions}), and `fork` names a fork node the arms
|
|
1175
|
+
* leave from (see {@link ParallelOptions}).
|
|
1141
1176
|
* @returns This builder, positioned after the Merge — so what you chain next
|
|
1142
1177
|
* runs once, when every arm has finished.
|
|
1143
1178
|
*/
|
|
1144
|
-
parallel(name: string, arms: ((b: StepList) => void)[], options?:
|
|
1179
|
+
parallel(name: string, arms: ((b: StepList) => void)[], options?: ParallelOptions): this;
|
|
1145
1180
|
/**
|
|
1146
1181
|
* Iterate `collection`; `bodyFn` receives a sub-builder for the loop body.
|
|
1147
1182
|
*
|
package/dist/flow-sdk.js
CHANGED
|
@@ -264,6 +264,18 @@ export function conversationTrigger() {
|
|
|
264
264
|
export function voiceTrigger() {
|
|
265
265
|
return builtinTriggerSpec({ kind: 'voice' });
|
|
266
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* The fork node a `.parallel()` asks for, normalized, or `undefined`.
|
|
269
|
+
* @internal
|
|
270
|
+
*/
|
|
271
|
+
export function parallelFork(options) {
|
|
272
|
+
const fork = options?.fork;
|
|
273
|
+
if (fork === undefined)
|
|
274
|
+
return undefined;
|
|
275
|
+
return typeof fork === 'string'
|
|
276
|
+
? { name: fork }
|
|
277
|
+
: { name: fork.name, ...(fork.label !== undefined ? { label: fork.label } : {}), ...(fork.version !== undefined ? { version: fork.version } : {}) };
|
|
278
|
+
}
|
|
267
279
|
/**
|
|
268
280
|
* A node's layout at a canvas position — the short form of a {@link NodeLayout}
|
|
269
281
|
* for the `nodes` map of {@link FlowBuilder.layout}.
|
|
@@ -764,7 +776,8 @@ class StepList {
|
|
|
764
776
|
* @param arms - One callback per parallel arm, each receiving its own
|
|
765
777
|
* sub-builder. Two or more.
|
|
766
778
|
* @param options - Node options; `version` selects the exact definition
|
|
767
|
-
* version (see {@link NodeOptions})
|
|
779
|
+
* version (see {@link NodeOptions}), and `fork` names a fork node the arms
|
|
780
|
+
* leave from (see {@link ParallelOptions}).
|
|
768
781
|
* @returns This builder, positioned after the Merge — so what you chain next
|
|
769
782
|
* runs once, when every arm has finished.
|
|
770
783
|
*/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flow variables a step DECLARES by running (`FlowNodeType.declares`): a
|
|
3
|
+
* quick form's asked fields land in their `variable` (`vars.<Step>_<field>` as
|
|
4
|
+
* the designer names it), and later steps read `$vars.<name>`. Resolved at the
|
|
5
|
+
* version the step resolves to, the way `stepExits` does, so `check` and
|
|
6
|
+
* `flow-expr-check` agree on what a step publishes (#1309).
|
|
7
|
+
*/
|
|
8
|
+
import { resolveVersion } from './core/nodes/versioned.js';
|
|
9
|
+
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
10
|
+
export function stepDeclaredVariables(spec, pinnedVersion) {
|
|
11
|
+
const entry = typeof spec?.kind === 'string' ? FLOW_NODE_TYPES.byKind(spec.kind, spec) : undefined;
|
|
12
|
+
if (!entry)
|
|
13
|
+
return [];
|
|
14
|
+
let impl;
|
|
15
|
+
try {
|
|
16
|
+
impl = resolveVersion(entry.type, spec, pinnedVersion, 'declares');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
impl = resolveVersion(entry.type, spec, undefined, 'declares');
|
|
20
|
+
}
|
|
21
|
+
return impl.declares?.(spec, pinnedVersion) ?? [];
|
|
22
|
+
}
|
package/dist/loop-version.d.ts
CHANGED
|
@@ -3,28 +3,45 @@ import type { LoopOptions, Step } from './flow-sdk.js';
|
|
|
3
3
|
export declare const LOOP_NODE_TYPE = "core.logic.loop";
|
|
4
4
|
/** The `core.logic.loop` version `.loop()` emits unless `{ version }` pins another. */
|
|
5
5
|
export declare const LOOP_DEFAULT_VERSION = "2.4";
|
|
6
|
+
/**
|
|
7
|
+
* The definition a loop at `version` compiles against: the bundled one, else
|
|
8
|
+
* the one the flow carries (`carried`, a `.definitions()` lookup), else
|
|
9
|
+
* `undefined`. `serialize` and `check` both resolve through this, so they agree
|
|
10
|
+
* on a carried `core.logic.loop@1.0`'s handles and inputs (#1311).
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveLoopDefinition(version: string, carried?: (type: string, version: string) => unknown): unknown;
|
|
6
13
|
/** The version `.loop()` emits for these options. */
|
|
7
14
|
export declare function loopVersion(options: LoopOptions | undefined): string;
|
|
8
15
|
/**
|
|
9
16
|
* True when this loop version's body runs from the inner `start` handle and goes
|
|
10
17
|
* back through `continue` (2.x); false for the legacy `output`/`loopBack` shape.
|
|
11
|
-
*
|
|
18
|
+
*
|
|
19
|
+
* The answer is read off the definition's target handles. `definition` is the
|
|
20
|
+
* one the loop compiles against: the bundled one for `version` by default, or
|
|
21
|
+
* the one the flow carries (`.definitions([...])`) when the SDK bundles none —
|
|
22
|
+
* a designer-saved `core.logic.loop@1.0` keeps its `output`/`loopBack` handles
|
|
23
|
+
* only because its carried definition says so (#1311). A version with no
|
|
24
|
+
* definition at all counts as 2.x; its lookup fails later.
|
|
12
25
|
*/
|
|
13
|
-
export declare function loopHasInnerHandles(version: string): boolean;
|
|
26
|
+
export declare function loopHasInnerHandles(version: string, definition?: unknown): boolean;
|
|
14
27
|
/**
|
|
15
28
|
* The features this loop uses that its version's definition cannot express, in
|
|
16
29
|
* a fixed order (`parallel`, `completionCondition`, `breakEnabled`, `break()`).
|
|
17
30
|
* Empty when all are supported, or when no definition is bundled for the version
|
|
18
31
|
* (an unknown version is reported elsewhere, by the definition lookup).
|
|
19
32
|
*/
|
|
20
|
-
export declare function unsupportedLoopFeatures(options: LoopOptions | undefined, usesBreak: boolean): string[];
|
|
33
|
+
export declare function unsupportedLoopFeatures(options: LoopOptions | undefined, usesBreak: boolean, definition?: unknown): string[];
|
|
34
|
+
/** The inputs a loop definition declares (`collection`, `parallel`, `completionCondition`, `breakEnabled`, …). */
|
|
35
|
+
export declare function loopDeclaredInputs(definition: unknown): Record<string, unknown>;
|
|
21
36
|
/**
|
|
22
37
|
* The refusal for a loop whose options or body need a definition the requested
|
|
23
38
|
* version does not have, or `undefined` when there is nothing to refuse.
|
|
24
39
|
* `serialize` throws it; `check` reports it as `LOOP_VERSION_UNSUPPORTED` with
|
|
25
|
-
* the same text, so the author reads one message in both places.
|
|
40
|
+
* the same text, so the author reads one message in both places. `definition`
|
|
41
|
+
* is the one the loop compiles against (bundled, else carried), as for
|
|
42
|
+
* `loopHasInnerHandles`.
|
|
26
43
|
*/
|
|
27
|
-
export declare function loopVersionRefusal(name: string, options: LoopOptions | undefined, usesBreak: boolean): string | undefined;
|
|
44
|
+
export declare function loopVersionRefusal(name: string, options: LoopOptions | undefined, usesBreak: boolean, definition?: unknown): string | undefined;
|
|
28
45
|
/**
|
|
29
46
|
* The warning for `parallel: true` on a 1.0.0 loop, or `undefined`. The 1.0.0
|
|
30
47
|
* definition declares the input, so the loop compiles and a canvas 1.0.0 loop
|
|
@@ -32,7 +49,7 @@ export declare function loopVersionRefusal(name: string, options: LoopOptions |
|
|
|
32
49
|
* confirmed, so a new loop that needs that should use the default version.
|
|
33
50
|
* `check` reports it as `LOOP_PARALLEL_LEGACY`.
|
|
34
51
|
*/
|
|
35
|
-
export declare function loopParallelWarning(name: string, options: LoopOptions | undefined): string | undefined;
|
|
52
|
+
export declare function loopParallelWarning(name: string, options: LoopOptions | undefined, definition?: unknown): string | undefined;
|
|
36
53
|
/**
|
|
37
54
|
* True when a container body (or an arm/handler inside it, but not a NESTED
|
|
38
55
|
* container's body) breaks: a nested `.loop()`/`.doWhile()` owns its own
|
package/dist/loop-version.js
CHANGED
|
@@ -11,6 +11,15 @@ import { BUNDLED_DEFINITIONS } from './flow-definitions.js';
|
|
|
11
11
|
export const LOOP_NODE_TYPE = 'core.logic.loop';
|
|
12
12
|
/** The `core.logic.loop` version `.loop()` emits unless `{ version }` pins another. */
|
|
13
13
|
export const LOOP_DEFAULT_VERSION = '2.4';
|
|
14
|
+
/**
|
|
15
|
+
* The definition a loop at `version` compiles against: the bundled one, else
|
|
16
|
+
* the one the flow carries (`carried`, a `.definitions()` lookup), else
|
|
17
|
+
* `undefined`. `serialize` and `check` both resolve through this, so they agree
|
|
18
|
+
* on a carried `core.logic.loop@1.0`'s handles and inputs (#1311).
|
|
19
|
+
*/
|
|
20
|
+
export function resolveLoopDefinition(version, carried) {
|
|
21
|
+
return loopDefinition(version) ?? carried?.(LOOP_NODE_TYPE, version);
|
|
22
|
+
}
|
|
14
23
|
/** The version `.loop()` emits for these options. */
|
|
15
24
|
export function loopVersion(options) {
|
|
16
25
|
return options?.version ?? LOOP_DEFAULT_VERSION;
|
|
@@ -18,10 +27,16 @@ export function loopVersion(options) {
|
|
|
18
27
|
/**
|
|
19
28
|
* True when this loop version's body runs from the inner `start` handle and goes
|
|
20
29
|
* back through `continue` (2.x); false for the legacy `output`/`loopBack` shape.
|
|
21
|
-
*
|
|
30
|
+
*
|
|
31
|
+
* The answer is read off the definition's target handles. `definition` is the
|
|
32
|
+
* one the loop compiles against: the bundled one for `version` by default, or
|
|
33
|
+
* the one the flow carries (`.definitions([...])`) when the SDK bundles none —
|
|
34
|
+
* a designer-saved `core.logic.loop@1.0` keeps its `output`/`loopBack` handles
|
|
35
|
+
* only because its carried definition says so (#1311). A version with no
|
|
36
|
+
* definition at all counts as 2.x; its lookup fails later.
|
|
22
37
|
*/
|
|
23
|
-
export function loopHasInnerHandles(version) {
|
|
24
|
-
return
|
|
38
|
+
export function loopHasInnerHandles(version, definition = loopDefinition(version)) {
|
|
39
|
+
return definition ? targetHandleIdsOf(definition).has('continue') : true;
|
|
25
40
|
}
|
|
26
41
|
/** The loop options whose support depends on the version, in message order. */
|
|
27
42
|
const VERSIONED_OPTIONS = ['parallel', 'completionCondition', 'breakEnabled'];
|
|
@@ -31,24 +46,29 @@ const VERSIONED_OPTIONS = ['parallel', 'completionCondition', 'breakEnabled'];
|
|
|
31
46
|
* Empty when all are supported, or when no definition is bundled for the version
|
|
32
47
|
* (an unknown version is reported elsewhere, by the definition lookup).
|
|
33
48
|
*/
|
|
34
|
-
export function unsupportedLoopFeatures(options, usesBreak) {
|
|
35
|
-
|
|
36
|
-
if (!def)
|
|
49
|
+
export function unsupportedLoopFeatures(options, usesBreak, definition = loopDefinition(loopVersion(options))) {
|
|
50
|
+
if (!definition)
|
|
37
51
|
return [];
|
|
38
|
-
const inputs =
|
|
52
|
+
const inputs = loopDeclaredInputs(definition);
|
|
39
53
|
const out = VERSIONED_OPTIONS.filter((k) => options?.[k] !== undefined && !(k in inputs));
|
|
40
|
-
if (usesBreak && !
|
|
54
|
+
if (usesBreak && !targetHandleIdsOf(definition).has('break'))
|
|
41
55
|
out.push('break()');
|
|
42
56
|
return out;
|
|
43
57
|
}
|
|
58
|
+
/** The inputs a loop definition declares (`collection`, `parallel`, `completionCondition`, `breakEnabled`, …). */
|
|
59
|
+
export function loopDeclaredInputs(definition) {
|
|
60
|
+
return definition?.inputDefinition?.properties ?? {};
|
|
61
|
+
}
|
|
44
62
|
/**
|
|
45
63
|
* The refusal for a loop whose options or body need a definition the requested
|
|
46
64
|
* version does not have, or `undefined` when there is nothing to refuse.
|
|
47
65
|
* `serialize` throws it; `check` reports it as `LOOP_VERSION_UNSUPPORTED` with
|
|
48
|
-
* the same text, so the author reads one message in both places.
|
|
66
|
+
* the same text, so the author reads one message in both places. `definition`
|
|
67
|
+
* is the one the loop compiles against (bundled, else carried), as for
|
|
68
|
+
* `loopHasInnerHandles`.
|
|
49
69
|
*/
|
|
50
|
-
export function loopVersionRefusal(name, options, usesBreak) {
|
|
51
|
-
const missing = unsupportedLoopFeatures(options, usesBreak);
|
|
70
|
+
export function loopVersionRefusal(name, options, usesBreak, definition = loopDefinition(loopVersion(options))) {
|
|
71
|
+
const missing = unsupportedLoopFeatures(options, usesBreak, definition);
|
|
52
72
|
if (missing.length === 0)
|
|
53
73
|
return undefined;
|
|
54
74
|
const version = loopVersion(options);
|
|
@@ -63,8 +83,8 @@ export function loopVersionRefusal(name, options, usesBreak) {
|
|
|
63
83
|
* confirmed, so a new loop that needs that should use the default version.
|
|
64
84
|
* `check` reports it as `LOOP_PARALLEL_LEGACY`.
|
|
65
85
|
*/
|
|
66
|
-
export function loopParallelWarning(name, options) {
|
|
67
|
-
if (options?.parallel !== true || loopHasInnerHandles(loopVersion(options)))
|
|
86
|
+
export function loopParallelWarning(name, options, definition = loopDefinition(loopVersion(options))) {
|
|
87
|
+
if (options?.parallel !== true || loopHasInnerHandles(loopVersion(options), definition))
|
|
68
88
|
return undefined;
|
|
69
89
|
return `loop "${name}": parallel: true on { version: '${loopVersion(options)}' } is written to the node, but `
|
|
70
90
|
+ `that version is not confirmed to run iterations in parallel, so the loop may run them one by one. `
|
|
@@ -86,9 +106,11 @@ export function bodyUsesBreak(steps) {
|
|
|
86
106
|
function loopDefinition(version) {
|
|
87
107
|
return BUNDLED_DEFINITIONS[`${LOOP_NODE_TYPE}@${version}`];
|
|
88
108
|
}
|
|
89
|
-
|
|
109
|
+
/** The ids of a definition's target handles (`input`, `loopBack`, `continue`, `break`, …). */
|
|
110
|
+
function targetHandleIdsOf(definition) {
|
|
90
111
|
const ids = new Set();
|
|
91
|
-
|
|
112
|
+
const groups = definition?.handleConfiguration ?? [];
|
|
113
|
+
for (const group of groups) {
|
|
92
114
|
for (const h of group.handles ?? [])
|
|
93
115
|
if (h.type === 'target')
|
|
94
116
|
ids.add(h.id);
|
package/dist/merge.d.ts
CHANGED
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
* as decompile was faithful for every node (the common case).
|
|
27
27
|
*
|
|
28
28
|
* Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
|
|
29
|
-
* preserved from the original
|
|
30
|
-
*
|
|
31
|
-
* (
|
|
29
|
+
* preserved from the original, apart from the `layout.nodes` entries of
|
|
30
|
+
* removed nodes (dropped) and added nodes (node-placement.ts); `subflows` and
|
|
31
|
+
* edges (with their ids) come from `edited`; root `variables` merge as a
|
|
32
|
+
* delta per entry, like nodes (merge-variables.ts); `definitions` and
|
|
33
|
+
* `bindings` are unioned
|
|
32
34
|
* (original wins on conflict, since the original's are the tenant-resolved
|
|
33
35
|
* authority). A binding row's identity is what it binds, not its row id, and a
|
|
34
36
|
* row the no-edit recompile minted only for nodes merge kept from the original
|
package/dist/merge.js
CHANGED
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
* as decompile was faithful for every node (the common case).
|
|
27
27
|
*
|
|
28
28
|
* Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
|
|
29
|
-
* preserved from the original
|
|
30
|
-
*
|
|
31
|
-
* (
|
|
29
|
+
* preserved from the original, apart from the `layout.nodes` entries of
|
|
30
|
+
* removed nodes (dropped) and added nodes (node-placement.ts); `subflows` and
|
|
31
|
+
* edges (with their ids) come from `edited`; root `variables` merge as a
|
|
32
|
+
* delta per entry, like nodes (merge-variables.ts); `definitions` and
|
|
33
|
+
* `bindings` are unioned
|
|
32
34
|
* (original wins on conflict, since the original's are the tenant-resolved
|
|
33
35
|
* authority). A binding row's identity is what it binds, not its row id, and a
|
|
34
36
|
* row the no-edit recompile minted only for nodes merge kept from the original
|
|
@@ -261,6 +263,11 @@ function layoutNodesOf(f) {
|
|
|
261
263
|
const nodes = f.layout?.nodes;
|
|
262
264
|
return nodes && typeof nodes === 'object' && !Array.isArray(nodes) ? nodes : undefined;
|
|
263
265
|
}
|
|
266
|
+
/** The original's `layout.nodes` when it holds positions. An SDK compile writes `{}` there and keeps them on `node.ui`. */
|
|
267
|
+
function positionStoreOf(f) {
|
|
268
|
+
const nodes = layoutNodesOf(f);
|
|
269
|
+
return nodes && Object.keys(nodes).length > 0 ? nodes : undefined;
|
|
270
|
+
}
|
|
264
271
|
export function mergeFlow(original, edited, options = {}) {
|
|
265
272
|
const { result, findings } = mergeFlowFindings(original, edited, options);
|
|
266
273
|
reportOrRefuse(findings, options);
|
|
@@ -374,12 +381,13 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
374
381
|
// one already taken elsewhere in the file, gets a fresh id, from ids unused
|
|
375
382
|
// anywhere in the file. ──
|
|
376
383
|
result.edges = withFileUniqueEdgeIds(edited.edges ?? [], subflowScopes);
|
|
377
|
-
// ── Placement: a node the edit ADDED has
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
|
|
384
|
+
// ── Placement: a node the edit ADDED has no original position. Unless the
|
|
385
|
+
// author set one, it has compile's placeholder, an emission-index slot
|
|
386
|
+
// that can land on an original node. Put it next to its predecessor,
|
|
387
|
+
// collision-free, and store every added node's position where the
|
|
388
|
+
// original stores positions — see node-placement.ts. Root scope only: a
|
|
389
|
+
// subflow is taken from `edited` whole, layout included. ──
|
|
390
|
+
placeAddedNodes(finalNodes, result.edges, positionStoreOf(original) && layoutNodesOf(result), (id) => !origNodes.has(id));
|
|
383
391
|
// ── Definitions: the (type, version) pairs used by EVERY node in the file —
|
|
384
392
|
// root and subflows — original winning on conflict (its defs are the
|
|
385
393
|
// tenant-resolved authority). Selecting by root nodes only dropped the
|
|
@@ -398,7 +406,9 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
398
406
|
result.bindings = mergeBindings(original.bindings, edited.bindings);
|
|
399
407
|
result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal);
|
|
400
408
|
// metadata / solutionId / projectId / layout / runtime were carried by the
|
|
401
|
-
// initial clone of `original` and are intentionally left untouched
|
|
409
|
+
// initial clone of `original` and are intentionally left untouched, apart
|
|
410
|
+
// from the `layout.nodes` entries of removed nodes (dropped above) and added
|
|
411
|
+
// nodes (placement).
|
|
402
412
|
//
|
|
403
413
|
// The declared file-format `version` follows the same delta rule the nodes
|
|
404
414
|
// do. The original's wins by default, because a compile of an edited source
|
package/dist/node-placement.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* A child's position is relative to its container, as on the canvas.
|
|
20
20
|
*
|
|
21
21
|
* The position goes where the original stores positions: `layout.nodes[id]`
|
|
22
|
-
* when
|
|
22
|
+
* when its `layout.nodes` has entries, otherwise `node.ui`. Positions are read the
|
|
23
23
|
* way the designer reads them (flow-workbench `flow-schema`
|
|
24
24
|
* `workflow-format-conversion.ts` `mergeLayout`): a `layout.nodes[id]` entry
|
|
25
25
|
* wins over `node.ui`, field by field.
|
|
@@ -77,13 +77,14 @@ interface PlacedNode {
|
|
|
77
77
|
* its placeholder, and placed nodes avoid it.
|
|
78
78
|
*
|
|
79
79
|
* Where the result is written follows the file: when the original stores
|
|
80
|
-
* positions in `layout.nodes` (every canvas-saved file does),
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
* positions in `layout.nodes` (every canvas-saved file does), every added
|
|
81
|
+
* node's `node.ui`, placed or not, sticky notes included, replaces its
|
|
82
|
+
* `layout.nodes` entry, so the node has one position; otherwise
|
|
83
|
+
* `node.ui.position` is updated.
|
|
83
84
|
*
|
|
84
85
|
* @param nodes - The merged root nodes (mutated).
|
|
85
86
|
* @param edges - The merged root edges.
|
|
86
|
-
* @param layoutNodes - The merged file's `layout.nodes`, or undefined when the original
|
|
87
|
+
* @param layoutNodes - The merged file's `layout.nodes`, or undefined when the original keeps positions on `node.ui` (mutated).
|
|
87
88
|
* @param isAdded - Whether a node id is new in this merge.
|
|
88
89
|
*/
|
|
89
90
|
export declare function placeAddedNodes(nodes: PlacedNode[], edges: ReadonlyArray<{
|
package/dist/node-placement.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* A child's position is relative to its container, as on the canvas.
|
|
20
20
|
*
|
|
21
21
|
* The position goes where the original stores positions: `layout.nodes[id]`
|
|
22
|
-
* when
|
|
22
|
+
* when its `layout.nodes` has entries, otherwise `node.ui`. Positions are read the
|
|
23
23
|
* way the designer reads them (flow-workbench `flow-schema`
|
|
24
24
|
* `workflow-format-conversion.ts` `mergeLayout`): a `layout.nodes[id]` entry
|
|
25
25
|
* wins over `node.ui`, field by field.
|
|
@@ -83,6 +83,21 @@ function findFreePosition(candidate, size, others) {
|
|
|
83
83
|
}
|
|
84
84
|
return candidate;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Move each added node's `ui` into `layoutNodes`, replacing any entry under its
|
|
88
|
+
* id: the original can hold an entry for an id it has no node for.
|
|
89
|
+
*/
|
|
90
|
+
function moveAddedUi(nodes, layoutNodes, isAdded) {
|
|
91
|
+
for (const n of nodes) {
|
|
92
|
+
if (!isAdded(n.id))
|
|
93
|
+
continue;
|
|
94
|
+
if (n.ui === undefined)
|
|
95
|
+
delete layoutNodes[n.id];
|
|
96
|
+
else
|
|
97
|
+
layoutNodes[n.id] = n.ui;
|
|
98
|
+
delete n.ui;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
86
101
|
/**
|
|
87
102
|
* Give each node the edit added a position next to its predecessor, in place.
|
|
88
103
|
*
|
|
@@ -95,16 +110,19 @@ function findFreePosition(candidate, size, others) {
|
|
|
95
110
|
* its placeholder, and placed nodes avoid it.
|
|
96
111
|
*
|
|
97
112
|
* Where the result is written follows the file: when the original stores
|
|
98
|
-
* positions in `layout.nodes` (every canvas-saved file does),
|
|
99
|
-
*
|
|
100
|
-
*
|
|
113
|
+
* positions in `layout.nodes` (every canvas-saved file does), every added
|
|
114
|
+
* node's `node.ui`, placed or not, sticky notes included, replaces its
|
|
115
|
+
* `layout.nodes` entry, so the node has one position; otherwise
|
|
116
|
+
* `node.ui.position` is updated.
|
|
101
117
|
*
|
|
102
118
|
* @param nodes - The merged root nodes (mutated).
|
|
103
119
|
* @param edges - The merged root edges.
|
|
104
|
-
* @param layoutNodes - The merged file's `layout.nodes`, or undefined when the original
|
|
120
|
+
* @param layoutNodes - The merged file's `layout.nodes`, or undefined when the original keeps positions on `node.ui` (mutated).
|
|
105
121
|
* @param isAdded - Whether a node id is new in this merge.
|
|
106
122
|
*/
|
|
107
123
|
export function placeAddedNodes(nodes, edges, layoutNodes, isAdded) {
|
|
124
|
+
if (layoutNodes)
|
|
125
|
+
moveAddedUi(nodes, layoutNodes, isAdded);
|
|
108
126
|
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
109
127
|
const uiOf = (n) => {
|
|
110
128
|
const entry = layoutNodes?.[n.id];
|
|
@@ -149,13 +167,10 @@ export function placeAddedNodes(nodes, edges, layoutNodes, isAdded) {
|
|
|
149
167
|
.map((o) => ({ ...uiOf(o).position, ...sizeOf(o) }));
|
|
150
168
|
const position = findFreePosition(candidate, sizeOf(n), others);
|
|
151
169
|
const ui = { ...uiOf(n), position };
|
|
152
|
-
if (layoutNodes)
|
|
170
|
+
if (layoutNodes)
|
|
153
171
|
layoutNodes[id] = ui;
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
172
|
+
else
|
|
157
173
|
n.ui = ui;
|
|
158
|
-
}
|
|
159
174
|
pending.delete(id);
|
|
160
175
|
progress = true;
|
|
161
176
|
}
|
|
@@ -30,6 +30,7 @@ export const HITL_DIAGNOSTICS = {
|
|
|
30
30
|
HITL_FIELD_VALUE_NOT_REF: { severity: 'error', summary: 'a shown field filled from a literal' },
|
|
31
31
|
HITL_FIELD_VALUE_NOT_PATH: { severity: 'error', summary: 'a shown field filled from a computed expression' },
|
|
32
32
|
HITL_FIELD_DIRECTION: { severity: 'error', summary: 'a field direction that is not input, output or inOut' },
|
|
33
|
+
HITL_FIELD_VARIABLE: { severity: 'error', summary: "an asked field's variable that is not one identifier, or sits on a shown field" },
|
|
33
34
|
HITL_NO_OUTCOMES: { severity: 'error', summary: 'no outcomes, so the task never completes' },
|
|
34
35
|
HITL_OUTCOME_BAD_ID: { severity: 'error', summary: 'an outcome id that is not a non-empty string' },
|
|
35
36
|
HITL_DUP_OUTCOME_ID: { severity: 'error', summary: 'two outcomes on one id' },
|
|
@@ -225,6 +226,20 @@ export function checkHitl(inputs, ctx) {
|
|
|
225
226
|
for (const problem of formFieldSettingProblems(f)) {
|
|
226
227
|
at('HITL_FIELD_SETTING_INVALID', `Human task "${step}" gives the form field "${id}" ${problem}. The platform refuses the form.`);
|
|
227
228
|
}
|
|
229
|
+
// The landing variable is read as `$vars.<variable>`, so it has to be one
|
|
230
|
+
// identifier — not a path, not a `vars.` prefix — and only an asked field
|
|
231
|
+
// has one: a shown field's row is written without it (#1309).
|
|
232
|
+
const variable = f?.variable;
|
|
233
|
+
if (variable !== undefined) {
|
|
234
|
+
if (f?.direction !== 'output') {
|
|
235
|
+
at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" sets \`variable\` on a ${f?.direction === 'inOut' ? 'pre-filled' : 'shown'} field. `
|
|
236
|
+
+ `Only an asked field (direction 'output') lands its answer in a variable; the SDK writes the row without one.`, 'Drop `variable`, or make the field direction: \'output\'.');
|
|
237
|
+
}
|
|
238
|
+
else if (typeof variable !== 'string' || !/^[A-Za-z_$][\w$]*$/.test(variable)) {
|
|
239
|
+
at('HITL_FIELD_VARIABLE', `Human task "${step}"'s form field "${id}" has variable ${JSON.stringify(variable)}, which is not one identifier. `
|
|
240
|
+
+ `It is read as $vars.<variable>, so give the name only — no \`vars.\` prefix, no dots.`, typeof variable === 'string' && variable.startsWith('vars.') ? `variable: '${variable.slice(5)}'` : `variable: '${step}_${id}'`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
228
243
|
if (isAnswerField(f))
|
|
229
244
|
answers++; // an `output`, or an `inOut` whose reviewed value comes back
|
|
230
245
|
if (f?.direction === 'input' || f?.direction === 'inOut') {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* every edge out of an outcome-handle node to.
|
|
14
14
|
*/
|
|
15
15
|
import { Expr, toExpr } from '../../../core/expr.js';
|
|
16
|
-
import { formFieldJsonSchema, formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, formFieldSettingsWire, liftFormFieldSettings, sourceFormFieldType, wireFormFieldType, } from '../../../core/form-field-type.js';
|
|
16
|
+
import { formFieldJsonSchema, formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, formFieldSettingsWire, formFieldVariableName, liftFormFieldSettings, sourceFormFieldType, wireFormFieldType, } from '../../../core/form-field-type.js';
|
|
17
17
|
import { isAnswerField } from '../../../core/hitl-answers.js';
|
|
18
18
|
import { OUTCOME_HANDLE_NODE_TYPES, hitlExitModel, outcomeIdOf, outcomeSlug } from '../../../core/hitl-routing.js';
|
|
19
19
|
import { jsonClone } from '../../../core/json-clone.js';
|
|
@@ -82,8 +82,9 @@ function hitlInputs(inputs, nodeType, ctx) {
|
|
|
82
82
|
else {
|
|
83
83
|
// The answer's landing variable. The platform falls back to the field id
|
|
84
84
|
// when this is absent; naming it keeps the emitted flow explicit and
|
|
85
|
-
// matches what the designer writes
|
|
86
|
-
|
|
85
|
+
// matches what the designer writes — `vars.<Step>_<field>` when the author
|
|
86
|
+
// (or the canvas) named one, else the field's own id (#1309).
|
|
87
|
+
base.variable = `vars.${f.variable ?? f.id}`;
|
|
87
88
|
const required = f.required;
|
|
88
89
|
if (typeof required === 'boolean')
|
|
89
90
|
base.required = required;
|
|
@@ -331,6 +332,11 @@ function liftHitl(node, typeName, ctx) {
|
|
|
331
332
|
const id = String(f.id ?? '');
|
|
332
333
|
const direction = String(f.direction ?? 'output');
|
|
333
334
|
const value = direction === 'input' || direction === 'inOut' ? bindingExpr(f.binding) : undefined;
|
|
335
|
+
// An asked field's landing variable comes back when it is not the field's
|
|
336
|
+
// own id — the designer's `vars.<Step>_<field>` (#1309); `formFieldRowLoss`
|
|
337
|
+
// reports any other spelling.
|
|
338
|
+
const landing = direction === 'output' ? formFieldVariableName(f.variable) : undefined;
|
|
339
|
+
const variable = landing !== undefined && landing !== id ? landing : undefined;
|
|
334
340
|
// Every row key the source cannot carry is reported, never dropped
|
|
335
341
|
// silently: a warning, an error under --strict (#1250).
|
|
336
342
|
const loss = formFieldRowLoss(f, 'human task');
|
|
@@ -346,6 +352,7 @@ function liftHitl(node, typeName, ctx) {
|
|
|
346
352
|
direction,
|
|
347
353
|
...(value !== undefined ? { value } : {}),
|
|
348
354
|
...(typeof f.required === 'boolean' && direction !== 'input' ? { required: f.required } : {}),
|
|
355
|
+
...(variable !== undefined ? { variable } : {}),
|
|
349
356
|
...liftFormFieldSettings(f),
|
|
350
357
|
};
|
|
351
358
|
});
|
|
@@ -509,6 +516,7 @@ function printHitl({ inputs }, ctx) {
|
|
|
509
516
|
['direction', ctx.string(f.direction)],
|
|
510
517
|
['value', f.value !== undefined ? ctx.value(f.value) : undefined],
|
|
511
518
|
['required', f.required !== undefined ? String(f.required) : undefined],
|
|
519
|
+
['variable', f.variable !== undefined ? ctx.string(f.variable) : undefined],
|
|
512
520
|
...formFieldSettingsSource(f, ctx.value),
|
|
513
521
|
], ctx));
|
|
514
522
|
const outcomes = h.outcomes.map((o) => (typeof o === 'string' ? ctx.string(o) : obj([
|
|
@@ -596,6 +604,13 @@ function hitlAt(typeName, version, factory) {
|
|
|
596
604
|
.filter((f) => f?.direction === 'input')
|
|
597
605
|
.map((f) => ({ value: f.value })),
|
|
598
606
|
output: ({ inputs }) => ({ kind: 'reader', read: (read, report) => checkHitlRead(read, inputs, report) }),
|
|
607
|
+
// An asked field's answer lands in its `variable` (`vars.<Step>_<field>` as
|
|
608
|
+
// the designer names it), else in `vars.<id>` — serialize writes one or the
|
|
609
|
+
// other on every asked row — and later steps read it as `$vars.<name>`: the
|
|
610
|
+
// task declares that variable (#1309).
|
|
611
|
+
declares: ({ inputs }) => (inputs?.fields ?? [])
|
|
612
|
+
.map((f) => (f?.direction === 'output' ? (f.variable ?? f.id) : undefined))
|
|
613
|
+
.filter((v) => typeof v === 'string' && v !== ''),
|
|
599
614
|
exits: hitlExits,
|
|
600
615
|
// The base node and quick-form draw one `outcome-<id>` handle per outcome,
|
|
601
616
|
// whatever the version; their definitions declare only `completed`.
|
|
@@ -722,6 +722,22 @@ function liftProcess(node, ctx, prefix, kind, form) {
|
|
|
722
722
|
inputs[k] = legacyAgentTemplateInput(v);
|
|
723
723
|
}
|
|
724
724
|
const schema = def?.outputDefinition?.output?.schema?.properties;
|
|
725
|
+
const returns = schema && Object.keys(schema).length > 0
|
|
726
|
+
? Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')]))
|
|
727
|
+
: undefined;
|
|
728
|
+
// The designer writes the process's output schema on the definition whether
|
|
729
|
+
// or not the step waits for it. `agenticProcess()` refuses `returns` beside
|
|
730
|
+
// `completion: 'fire-and-forget'` (a dispatch publishes no output, so nothing
|
|
731
|
+
// could read one), so the schema is not carried into source — said, not
|
|
732
|
+
// dropped silently (#1312). Merge keeps the original's definition row, so a
|
|
733
|
+
// no-edit round trip loses nothing in the file.
|
|
734
|
+
if (fireAndForget && returns !== undefined) {
|
|
735
|
+
const id = String(node.id ?? '');
|
|
736
|
+
ctx.lossy(`process step ${JSON.stringify(id)} is fire-and-forget, so the ${Object.keys(returns).length} output `
|
|
737
|
+
+ `field(s) its definition declares (${Object.keys(returns).join(', ')}) are not written as \`returns\`: `
|
|
738
|
+
+ `a fire-and-forget dispatch publishes no output, and agenticProcess() refuses the pair. Compile writes the `
|
|
739
|
+
+ `definition without that schema. To read the process result, set completion: 'wait' and declare \`returns\`.`);
|
|
740
|
+
}
|
|
725
741
|
const spec = {
|
|
726
742
|
key,
|
|
727
743
|
...(name !== undefined ? { name } : {}),
|
|
@@ -729,8 +745,7 @@ function liftProcess(node, ctx, prefix, kind, form) {
|
|
|
729
745
|
...(!inSolution && folderPath !== undefined ? { folderPath } : {}),
|
|
730
746
|
...(inSolution && projectId !== undefined ? { projectId } : {}),
|
|
731
747
|
...(Object.keys(inputs).length ? { inputs: Object.fromEntries(Object.entries(inputs).map(([k, v]) => [k, lifted(v, ctx)])) } : {}),
|
|
732
|
-
...(
|
|
733
|
-
? { returns: Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, String(v?.type ?? 'string')])) } : {}),
|
|
748
|
+
...(returns !== undefined && !fireAndForget ? { returns } : {}),
|
|
734
749
|
...(form !== undefined ? { form } : {}),
|
|
735
750
|
...(fireAndForget ? { completion: 'fire-and-forget' } : {}),
|
|
736
751
|
};
|