@uipath/maestro-builder-sdk 6.66.1 → 6.66.3
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 +8 -8
- package/dist/api-members.md +44 -43
- package/dist/case/decompile.js +31 -18
- package/dist/case/merge.d.ts +3 -3
- package/dist/case/merge.js +12 -8
- package/dist/case/preserve.d.ts +12 -0
- package/dist/case/preserve.js +21 -0
- package/dist/case/serialize.d.ts +16 -0
- package/dist/case/serialize.js +271 -63
- package/dist/core/process-resource.d.ts +6 -1
- package/dist/core/process-resource.js +7 -6
- package/dist/core/refusals.d.ts +1 -0
- package/dist/core/refusals.js +1 -0
- package/dist/merge.js +31 -0
- package/dist/nodes/hitl/flow/check.js +23 -4
- package/dist/nodes/hitl/flow/node-type.js +44 -3
- package/dist/nodes/hitl/flow/spec.d.ts +33 -4
- package/package.json +2 -2
package/dist/case/serialize.d.ts
CHANGED
|
@@ -31,3 +31,19 @@ export interface ApplyPreservationOptions {
|
|
|
31
31
|
* same operation compile performs with the payload the source carries.
|
|
32
32
|
*/
|
|
33
33
|
export declare function applyPreservation(result: Record<string, unknown>, preservation: CaseJsonPreservation | undefined, options?: ApplyPreservationOptions): Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* The payload with each stage label and task name an edit renamed rewritten to
|
|
36
|
+
* the one compiled, so a renamed stage or task keeps its ids and wire fields
|
|
37
|
+
* instead of reading as a delete plus an add. Stages left over on both sides
|
|
38
|
+
* pair by the most task names shared, then the fewest not shared, then in order
|
|
39
|
+
* when as many are left on each side; a task left over pairs with one in the
|
|
40
|
+
* same stage whose input and output rows have the same names, and a task gone
|
|
41
|
+
* from its stage that another stage now holds under the same name and rows
|
|
42
|
+
* moved there.
|
|
43
|
+
*
|
|
44
|
+
* `wanted` is the payload's side (identities read from the original plan),
|
|
45
|
+
* `compiled` this compile's. Per stage, `missing` tasks are the payload's that
|
|
46
|
+
* the compiled stage no longer names, `extra` tasks the compiled stage's that
|
|
47
|
+
* the payload does not.
|
|
48
|
+
*/
|
|
49
|
+
export declare function followRenames(result: Record<string, unknown>, preservation: CaseJsonPreservation): CaseJsonPreservation;
|
package/dist/case/serialize.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* name) are resolved here to the generated ids.
|
|
10
10
|
*/
|
|
11
11
|
import { IdGen, offlineBindingSeed } from './ids.js';
|
|
12
|
-
import { isUnrestorableSlotId } from './preserve.js';
|
|
12
|
+
import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
|
|
13
13
|
import { v31TaskSets } from './task-sets.js';
|
|
14
14
|
import { camelizeName, defaultActionOutputVar } from './action-output-var.js';
|
|
15
15
|
import { buildConnectorInputs, nestConnectorBody, replayInputsOf, transportHttpMethod } from '../core/connector-inputs.js';
|
|
@@ -23,6 +23,7 @@ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_HYBRID_UNSUPPORTED, ex
|
|
|
23
23
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
|
|
24
24
|
import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './resource-identity.js';
|
|
25
25
|
import { ownEntry } from '../core/own-entry.js';
|
|
26
|
+
import { stableId } from '../core/stable-id.js';
|
|
26
27
|
import { resolveSchemaVersion } from '../schema-version.js';
|
|
27
28
|
import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
|
|
28
29
|
import { businessEventProblems } from './business-event-shape.js';
|
|
@@ -30,6 +31,13 @@ import { readEventFilterLeaf, eventFilterTreeLeaf } from '../core/event-filters.
|
|
|
30
31
|
import { responseJsonSchema } from '../core/connector-response-schema.js';
|
|
31
32
|
import { ruleShapeProblems } from './rule-options.js';
|
|
32
33
|
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
34
|
+
/** The ids a serialize pass hashed from element ids, by the document it returned. */
|
|
35
|
+
const ELEMENT_SEEDED_IDS = new WeakMap();
|
|
36
|
+
function elementSeededId(resolver, elementId, suffix) {
|
|
37
|
+
const id = stableId(`${elementId}${suffix}`);
|
|
38
|
+
resolver.elementSeededIds.push({ elementId, suffix, id });
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
33
41
|
/**
|
|
34
42
|
* Condition `displayName` allocator.
|
|
35
43
|
*
|
|
@@ -102,6 +110,7 @@ export function serialize(built, opts = {}) {
|
|
|
102
110
|
escalationIdByName: new Map(),
|
|
103
111
|
businessEvents: new Set((built.businessEvents ?? []).map((event) => event.name)),
|
|
104
112
|
conditionName: conditionNamer(built),
|
|
113
|
+
elementSeededIds: [],
|
|
105
114
|
};
|
|
106
115
|
// Preallocate SLA + escalation ids (before any rule is emitted) so a
|
|
107
116
|
// `sla-status-change` rule can resolve the `slaId`/`escalationId` it points at.
|
|
@@ -282,6 +291,7 @@ export function serialize(built, opts = {}) {
|
|
|
282
291
|
}
|
|
283
292
|
: {}),
|
|
284
293
|
};
|
|
294
|
+
ELEMENT_SEEDED_IDS.set(result, resolver.elementSeededIds);
|
|
285
295
|
const plan = stampConnectorConfigVersion(applyAuthorLayout(applyPreservation(result, caseJsonPreservation(built)), built.layout));
|
|
286
296
|
// Last, over the finished document: V32 requires case-unique condition names,
|
|
287
297
|
// and an author (or a decompiled pre-V32 plan) can repeat one. Rename the
|
|
@@ -396,6 +406,7 @@ export function applyPreservation(result, preservation, options = {}) {
|
|
|
396
406
|
const errors = caseJsonPreservationErrors(preservation);
|
|
397
407
|
if (errors.length)
|
|
398
408
|
throw new CompileRefusal('PRESERVATION_COLLISION', `invalid Case JSON preservation payload: ${errors.join('; ')}`);
|
|
409
|
+
preservation = followRenames(result, preservation);
|
|
399
410
|
applyPreservedIdentities(result, preservation);
|
|
400
411
|
Object.assign(result, preservation.topLevel ?? {});
|
|
401
412
|
Object.assign(result.metadata, preservation.metadata ?? {});
|
|
@@ -431,9 +442,15 @@ export function applyPreservation(result, preservation, options = {}) {
|
|
|
431
442
|
const row = rows.find((candidate) => candidate.name === name);
|
|
432
443
|
if (!row && options.overwrite)
|
|
433
444
|
continue;
|
|
445
|
+
// `custom` alone has nothing to mark when no row is written: an older SDK's
|
|
446
|
+
// companion for a variable an output writes, which this one migrates away.
|
|
447
|
+
if (!row && Object.keys(fields).every((field) => field === 'custom'))
|
|
448
|
+
continue;
|
|
434
449
|
if (!row)
|
|
435
450
|
throw new CompileRefusal('PRESERVATION_TARGET_MISSING', `preserved variable "${name}" was not regenerated in variables.${lane}.`);
|
|
436
451
|
for (const [field, value] of Object.entries(fields)) {
|
|
452
|
+
if (row[field] === value)
|
|
453
|
+
continue;
|
|
437
454
|
if (!options.overwrite && Object.prototype.hasOwnProperty.call(row, field))
|
|
438
455
|
throw new CompileRefusal('PRESERVATION_FIELD_COLLISION', `preserved variable "${name}" field "${field}" collides with serializer-owned output.`);
|
|
439
456
|
row[field] = value;
|
|
@@ -524,14 +541,171 @@ export function applyPreservation(result, preservation, options = {}) {
|
|
|
524
541
|
nodes.sort((left, right) => (order.get(String(left.id ?? '')) ?? Number.MAX_SAFE_INTEGER)
|
|
525
542
|
- (order.get(String(right.id ?? '')) ?? Number.MAX_SAFE_INTEGER));
|
|
526
543
|
}
|
|
544
|
+
// A stage the payload names that was not regenerated was deleted by the edit:
|
|
545
|
+
// its edges and canvas position go with it.
|
|
546
|
+
const nodeIds = new Set(result.nodes.map((node) => node.id));
|
|
547
|
+
const gone = new Set((preservation.identities?.stages ?? []).flatMap((stage) => (stage.id && !nodeIds.has(stage.id) ? [stage.id] : [])));
|
|
527
548
|
if (preservation.edges !== undefined)
|
|
528
|
-
result.edges = preservation.edges;
|
|
549
|
+
result.edges = preservation.edges.filter((edge) => !gone.has(edge?.source) && !gone.has(edge?.target));
|
|
529
550
|
if (preservation.layout !== undefined)
|
|
530
|
-
result.layout = preservation.layout;
|
|
551
|
+
result.layout = withoutLayoutNodes(preservation.layout, gone);
|
|
531
552
|
else if (preservation.omitLayout)
|
|
532
553
|
delete result.layout;
|
|
533
554
|
return result;
|
|
534
555
|
}
|
|
556
|
+
function withoutLayoutNodes(layout, gone) {
|
|
557
|
+
const nodes = layout?.nodes;
|
|
558
|
+
if (!gone.size || !nodes || typeof nodes !== 'object' || !Object.keys(nodes).some((id) => gone.has(id)))
|
|
559
|
+
return layout;
|
|
560
|
+
return { ...layout, nodes: Object.fromEntries(Object.entries(nodes).filter(([id]) => !gone.has(id))) };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The payload with each stage label and task name an edit renamed rewritten to
|
|
564
|
+
* the one compiled, so a renamed stage or task keeps its ids and wire fields
|
|
565
|
+
* instead of reading as a delete plus an add. Stages left over on both sides
|
|
566
|
+
* pair by the most task names shared, then the fewest not shared, then in order
|
|
567
|
+
* when as many are left on each side; a task left over pairs with one in the
|
|
568
|
+
* same stage whose input and output rows have the same names, and a task gone
|
|
569
|
+
* from its stage that another stage now holds under the same name and rows
|
|
570
|
+
* moved there.
|
|
571
|
+
*
|
|
572
|
+
* `wanted` is the payload's side (identities read from the original plan),
|
|
573
|
+
* `compiled` this compile's. Per stage, `missing` tasks are the payload's that
|
|
574
|
+
* the compiled stage no longer names, `extra` tasks the compiled stage's that
|
|
575
|
+
* the payload does not.
|
|
576
|
+
*/
|
|
577
|
+
export function followRenames(result, preservation) {
|
|
578
|
+
const wanted = preservation.identities?.stages ?? [];
|
|
579
|
+
if (!wanted.length)
|
|
580
|
+
return preservation;
|
|
581
|
+
const compiled = (result.nodes ?? []).filter((node) => String(node.type ?? '').endsWith('Stage'));
|
|
582
|
+
const labelOf = (stage) => String(stage.data?.label ?? '');
|
|
583
|
+
const tasksOf = (stage) => (stage.data?.tasks ?? []).flat().filter((task) => task && typeof task === 'object');
|
|
584
|
+
const compiledLabels = new Set(compiled.map(labelOf));
|
|
585
|
+
const wantedLabels = new Set(wanted.map((stage) => stage.label));
|
|
586
|
+
const unmatchedCompiled = compiled.filter((stage) => !wantedLabels.has(labelOf(stage)));
|
|
587
|
+
const stageLabels = new Map();
|
|
588
|
+
const paired = new Set();
|
|
589
|
+
const unmatchedWanted = wanted.filter((stage) => !compiledLabels.has(stage.label));
|
|
590
|
+
// Ranked across every pair, so a deleted stage that shares one task name with
|
|
591
|
+
// a renamed stage cannot take it from the stage that shares more.
|
|
592
|
+
const stagePairs = unmatchedWanted.flatMap((stage, from) => {
|
|
593
|
+
const names = new Set((stage.tasks ?? []).flat().map((task) => task.name));
|
|
594
|
+
return unmatchedCompiled.flatMap((candidate, to) => {
|
|
595
|
+
const tasks = tasksOf(candidate);
|
|
596
|
+
const shared = tasks.filter((task) => names.has(String(task.displayName ?? ''))).length;
|
|
597
|
+
return shared ? [{ from, to, shared, unshared: names.size + tasks.length - 2 * shared }] : [];
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
stagePairs.sort((a, b) => b.shared - a.shared || a.unshared - b.unshared || a.from - b.from || a.to - b.to);
|
|
601
|
+
for (const { from, to } of stagePairs) {
|
|
602
|
+
if (stageLabels.has(unmatchedWanted[from].label) || paired.has(unmatchedCompiled[to]))
|
|
603
|
+
continue;
|
|
604
|
+
paired.add(unmatchedCompiled[to]);
|
|
605
|
+
stageLabels.set(unmatchedWanted[from].label, labelOf(unmatchedCompiled[to]));
|
|
606
|
+
}
|
|
607
|
+
const unpairedWanted = unmatchedWanted.filter((stage) => !stageLabels.has(stage.label));
|
|
608
|
+
const unpairedCompiled = unmatchedCompiled.filter((stage) => !paired.has(stage));
|
|
609
|
+
if (unpairedWanted.length === unpairedCompiled.length)
|
|
610
|
+
unpairedWanted.forEach((stage, index) => stageLabels.set(stage.label, labelOf(unpairedCompiled[index])));
|
|
611
|
+
const rowNames = (inputs, outputs) => JSON.stringify([inputs, outputs].map((rows) => (Array.isArray(rows) ? rows : []).map((row) => String(row?.name ?? ''))));
|
|
612
|
+
const nameOf = (task) => String(task.displayName ?? '');
|
|
613
|
+
const stageSides = wanted.map((stage) => {
|
|
614
|
+
const target = compiled.find((candidate) => labelOf(candidate) === (stageLabels.get(stage.label) ?? stage.label));
|
|
615
|
+
const tasks = (stage.tasks ?? []).flat();
|
|
616
|
+
const current = target ? tasksOf(target) : [];
|
|
617
|
+
const currentNames = new Set(current.map(nameOf));
|
|
618
|
+
const names = new Set(tasks.map((task) => task.name));
|
|
619
|
+
return { stage, target, missing: tasks.filter((task) => !currentNames.has(task.name)), extra: current.filter((task) => !names.has(nameOf(task))) };
|
|
620
|
+
});
|
|
621
|
+
const targeted = new Set(stageSides.map((side) => side.target));
|
|
622
|
+
const extraTasks = [
|
|
623
|
+
...stageSides.flatMap(({ target, extra }) => (target ? extra.map((task) => ({ stage: labelOf(target), task })) : [])),
|
|
624
|
+
...compiled.filter((stage) => !targeted.has(stage)).flatMap((stage) => tasksOf(stage).map((task) => ({ stage: labelOf(stage), task }))),
|
|
625
|
+
];
|
|
626
|
+
const missingTasks = stageSides.flatMap(({ stage, missing }) => missing.map((task) => ({ stage: stage.label, task })));
|
|
627
|
+
const moves = new Map();
|
|
628
|
+
const claimed = new Set();
|
|
629
|
+
for (const { stage, task } of missingTasks) {
|
|
630
|
+
const rows = rowNames(task.inputs, task.outputs);
|
|
631
|
+
const to = extraTasks.filter((extra) => nameOf(extra.task) === task.name && rowNames(extra.task.data?.inputs, extra.task.data?.outputs) === rows);
|
|
632
|
+
if (to.length !== 1 || missingTasks.filter((other) => other.task.name === task.name).length !== 1)
|
|
633
|
+
continue;
|
|
634
|
+
if (!moves.has(stage))
|
|
635
|
+
moves.set(stage, new Map());
|
|
636
|
+
moves.get(stage).set(task.name, to[0].stage);
|
|
637
|
+
claimed.add(to[0].task);
|
|
638
|
+
}
|
|
639
|
+
const movedTo = (label, name) => moves.get(label)?.get(name);
|
|
640
|
+
const taskNames = new Map();
|
|
641
|
+
for (const side of stageSides) {
|
|
642
|
+
if (!side.target)
|
|
643
|
+
continue;
|
|
644
|
+
const missing = side.missing.filter((task) => !movedTo(side.stage.label, task.name));
|
|
645
|
+
const extra = side.extra
|
|
646
|
+
.filter((task) => !claimed.has(task))
|
|
647
|
+
.map((task) => ({ name: nameOf(task), rows: rowNames(task.data?.inputs, task.data?.outputs), task }));
|
|
648
|
+
// A deleted sibling can share a renamed task's rows, so the one whose content
|
|
649
|
+
// the renamed task still has wins, then the old name closest to the new one,
|
|
650
|
+
// then the earlier task.
|
|
651
|
+
const candidates = missing.flatMap((task, from) => extra.flatMap((candidate, to) => candidate.rows === rowNames(task.inputs, task.outputs)
|
|
652
|
+
? [{ from, to, same: Number(task.fingerprint !== undefined && task.fingerprint === taskFingerprint(candidate.task)), score: nameAffinity(task.name, candidate.name) }]
|
|
653
|
+
: []));
|
|
654
|
+
candidates.sort((a, b) => b.same - a.same || b.score - a.score || a.from - b.from || a.to - b.to);
|
|
655
|
+
const renames = new Map();
|
|
656
|
+
const taken = new Set();
|
|
657
|
+
for (const { from, to } of candidates) {
|
|
658
|
+
if (renames.has(missing[from].name) || taken.has(to))
|
|
659
|
+
continue;
|
|
660
|
+
renames.set(missing[from].name, extra[to].name);
|
|
661
|
+
taken.add(to);
|
|
662
|
+
}
|
|
663
|
+
if (renames.size)
|
|
664
|
+
taskNames.set(side.stage.label, renames);
|
|
665
|
+
}
|
|
666
|
+
if (!stageLabels.size && !taskNames.size && !moves.size)
|
|
667
|
+
return preservation;
|
|
668
|
+
const stageName = (label) => stageLabels.get(label) ?? label;
|
|
669
|
+
const taskName = (label, name) => taskNames.get(label)?.get(name) ?? name;
|
|
670
|
+
const ref = (entry) => ({ ...entry, stage: movedTo(entry.stage, entry.task) ?? stageName(entry.stage), task: taskName(entry.stage, entry.task) });
|
|
671
|
+
const stages = wanted.map((stage) => ({
|
|
672
|
+
...stage,
|
|
673
|
+
label: stageName(stage.label),
|
|
674
|
+
...(stage.tasks
|
|
675
|
+
? { tasks: stage.tasks.map((lane) => lane.filter((task) => !movedTo(stage.label, task.name)).map((task) => ({ ...task, name: taskName(stage.label, task.name) }))) }
|
|
676
|
+
: {}),
|
|
677
|
+
}));
|
|
678
|
+
for (const { stage: from, task } of missingTasks) {
|
|
679
|
+
const to = movedTo(from, task.name);
|
|
680
|
+
if (!to)
|
|
681
|
+
continue;
|
|
682
|
+
const target = stages.find((stage) => stage.label === to);
|
|
683
|
+
if (target)
|
|
684
|
+
target.tasks = [...(target.tasks ?? []), [task]];
|
|
685
|
+
else
|
|
686
|
+
stages.push({ label: to, tasks: [[task]] });
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
...preservation,
|
|
690
|
+
identities: {
|
|
691
|
+
...preservation.identities,
|
|
692
|
+
stages,
|
|
693
|
+
},
|
|
694
|
+
...(preservation.taskFields ? { taskFields: preservation.taskFields.map(ref) } : {}),
|
|
695
|
+
...(preservation.omitTaskElementIds ? { omitTaskElementIds: preservation.omitTaskElementIds.map(ref) } : {}),
|
|
696
|
+
...(preservation.omitStagePositions ? { omitStagePositions: preservation.omitStagePositions.map(stageName) } : {}),
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/** How much of a renamed name survives: the longer of the shared prefix and the shared suffix. */
|
|
700
|
+
function nameAffinity(before, after) {
|
|
701
|
+
let prefix = 0;
|
|
702
|
+
while (prefix < before.length && before[prefix] === after[prefix])
|
|
703
|
+
prefix++;
|
|
704
|
+
let suffix = 0;
|
|
705
|
+
while (suffix < before.length && suffix < after.length && before.at(-1 - suffix) === after.at(-1 - suffix))
|
|
706
|
+
suffix++;
|
|
707
|
+
return Math.max(prefix, suffix);
|
|
708
|
+
}
|
|
535
709
|
function applyPreservedIdentities(result, preservation) {
|
|
536
710
|
const identities = preservation.identities;
|
|
537
711
|
if (!identities)
|
|
@@ -543,11 +717,70 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
543
717
|
replacements.set(current.id, wanted);
|
|
544
718
|
current.id = wanted;
|
|
545
719
|
};
|
|
720
|
+
const nodes = result.nodes;
|
|
721
|
+
const stages = nodes.filter((node) => String(node.type).endsWith('Stage'));
|
|
722
|
+
const bagField = CASE_FORMAT_PROFILE.waitConnectorRuleBagField;
|
|
723
|
+
const ruleOutputRows = (conditions) => (conditions ?? []).flatMap((condition) => (condition?.rules ?? []).flat().flatMap((rule) => rule?.[bagField]?.outputs ?? []));
|
|
724
|
+
// Every output row id in the plan, task and rule rows alike, kept current as
|
|
725
|
+
// preserved ids are taken. Output ids are unique plan-wide (`claimOutputId`); a
|
|
726
|
+
// preserved id that another row already holds would undo that. Example: an
|
|
727
|
+
// older SDK wrote two action rows both `{ id: 'action', var: 'action' }`; this
|
|
728
|
+
// pass gives the second `action2`, and taking its preserved `action` would make
|
|
729
|
+
// it a duplicate.
|
|
730
|
+
const heldOutputIds = new Set();
|
|
731
|
+
for (const stage of stages) {
|
|
732
|
+
const rows = [
|
|
733
|
+
...ruleOutputRows(stage.data?.entryConditions),
|
|
734
|
+
...ruleOutputRows(stage.data?.exitConditions),
|
|
735
|
+
...(stage.data?.tasks ?? []).flat().flatMap((task) => [...(task?.data?.outputs ?? []), ...ruleOutputRows(task?.entryConditions)]),
|
|
736
|
+
];
|
|
737
|
+
for (const row of rows)
|
|
738
|
+
if (typeof row?.id === 'string')
|
|
739
|
+
heldOutputIds.add(row.id);
|
|
740
|
+
}
|
|
741
|
+
for (const row of ruleOutputRows(result.metadata?.caseExitRules))
|
|
742
|
+
if (typeof row?.id === 'string')
|
|
743
|
+
heldOutputIds.add(row.id);
|
|
744
|
+
const takeOutputRows = (outputRows, wantedRows) => {
|
|
745
|
+
// Match by name when unique on both sides, else by position.
|
|
746
|
+
const unique = (names, name) => names.filter((n) => n === name).length === 1;
|
|
747
|
+
const currentNames = outputRows.map((r) => r?.name);
|
|
748
|
+
const wantedNames = wantedRows.map((r) => r.name);
|
|
749
|
+
const named = new Set(wantedRows.filter((r) => unique(wantedNames, r.name) && unique(currentNames, r.name)).map((r) => r.name));
|
|
750
|
+
wantedRows.forEach((row, index) => {
|
|
751
|
+
const target = named.has(row.name)
|
|
752
|
+
? outputRows.find((r) => r?.name === row.name)
|
|
753
|
+
// A positional match never takes a row a name match owns.
|
|
754
|
+
: named.has(outputRows[index]?.name) ? undefined : outputRows[index];
|
|
755
|
+
const currentId = target?.id;
|
|
756
|
+
// An own-name row's id IS its variable's name (`emitTaskOutput`). A
|
|
757
|
+
// preserved id that differs — an `o<hash>` slot from a plan an older
|
|
758
|
+
// SDK wrote — would rename the variable out from under every
|
|
759
|
+
// `=vars.<name>` reader, so the typed rule wins and the row migrates.
|
|
760
|
+
if (target && OWN_NAME_OUTPUT_ROWS.has(target) && row.id !== target.id)
|
|
761
|
+
return;
|
|
762
|
+
if (target && typeof row.id === 'string' && row.id !== currentId && heldOutputIds.has(row.id))
|
|
763
|
+
return;
|
|
764
|
+
take(target, row.id);
|
|
765
|
+
if (target && typeof row.id === 'string' && typeof currentId === 'string') {
|
|
766
|
+
heldOutputIds.delete(currentId);
|
|
767
|
+
heldOutputIds.add(row.id);
|
|
768
|
+
}
|
|
769
|
+
if (typeof currentId === 'string' && typeof row.id === 'string' && target?.value === currentId) {
|
|
770
|
+
target.value = row.id;
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
};
|
|
546
774
|
const takeConditions = (current, wanted) => {
|
|
547
775
|
(wanted ?? []).forEach((condition, index) => {
|
|
548
776
|
const target = current?.[index];
|
|
549
777
|
take(target, condition.id);
|
|
550
|
-
(condition.rules ?? []).forEach((group, groupIndex) => group.forEach((rule, ruleIndex) =>
|
|
778
|
+
(condition.rules ?? []).forEach((group, groupIndex) => group.forEach((rule, ruleIndex) => {
|
|
779
|
+
const ruleTarget = target?.rules?.[groupIndex]?.[ruleIndex];
|
|
780
|
+
take(ruleTarget, rule.id);
|
|
781
|
+
if (rule.outputs)
|
|
782
|
+
takeOutputRows(ruleTarget?.[bagField]?.outputs ?? [], rule.outputs);
|
|
783
|
+
}));
|
|
551
784
|
});
|
|
552
785
|
};
|
|
553
786
|
const takeSlas = (current, wanted) => {
|
|
@@ -558,22 +791,8 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
558
791
|
});
|
|
559
792
|
};
|
|
560
793
|
take(result, identities.id);
|
|
561
|
-
const nodes = result.nodes;
|
|
562
794
|
const triggerNodes = nodes.filter((node) => node.type === 'case-management:Trigger' || node.type === 'uipath.case.trigger');
|
|
563
795
|
(identities.triggers ?? []).forEach((trigger, index) => take(triggerNodes[index], trigger.id));
|
|
564
|
-
const stages = nodes.filter((node) => String(node.type).endsWith('Stage'));
|
|
565
|
-
// Every task-output row id in the plan, kept current as preserved ids are
|
|
566
|
-
// taken. Output ids are unique plan-wide (`claimOutputId`); a preserved id that
|
|
567
|
-
// another row already holds would undo that. Example: an older SDK wrote two
|
|
568
|
-
// action rows both `{ id: 'action', var: 'action' }`; this pass gives the
|
|
569
|
-
// second `action2`, and taking its preserved `action` would make it a duplicate.
|
|
570
|
-
const heldOutputIds = new Set();
|
|
571
|
-
for (const stage of stages)
|
|
572
|
-
for (const lane of stage.data?.tasks ?? [])
|
|
573
|
-
for (const task of lane)
|
|
574
|
-
for (const row of task?.data?.outputs ?? [])
|
|
575
|
-
if (typeof row?.id === 'string')
|
|
576
|
-
heldOutputIds.add(row.id);
|
|
577
796
|
for (const stageIdentity of identities.stages ?? []) {
|
|
578
797
|
const stage = stages.find((candidate) => candidate.data?.label === stageIdentity.label);
|
|
579
798
|
take(stage, stageIdentity.id);
|
|
@@ -585,36 +804,7 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
585
804
|
take(task, taskIdentity.id);
|
|
586
805
|
takeConditions(task?.entryConditions, taskIdentity.entryConditions);
|
|
587
806
|
(taskIdentity.inputs ?? []).forEach((row, index) => take(task?.data?.inputs?.[index], row.id));
|
|
588
|
-
|
|
589
|
-
const wantedRows = taskIdentity.outputs ?? [];
|
|
590
|
-
// Match by name when unique on both sides, else by position.
|
|
591
|
-
const unique = (names, name) => names.filter((n) => n === name).length === 1;
|
|
592
|
-
const currentNames = outputRows.map((r) => r?.name);
|
|
593
|
-
const wantedNames = wantedRows.map((r) => r.name);
|
|
594
|
-
const named = new Set(wantedRows.filter((r) => unique(wantedNames, r.name) && unique(currentNames, r.name)).map((r) => r.name));
|
|
595
|
-
wantedRows.forEach((row, index) => {
|
|
596
|
-
const target = named.has(row.name)
|
|
597
|
-
? outputRows.find((r) => r?.name === row.name)
|
|
598
|
-
// A positional match never takes a row a name match owns.
|
|
599
|
-
: named.has(outputRows[index]?.name) ? undefined : outputRows[index];
|
|
600
|
-
const currentId = target?.id;
|
|
601
|
-
// An own-name row's id IS its variable's name (`emitTaskOutput`). A
|
|
602
|
-
// preserved id that differs — an `o<hash>` slot from a plan an older
|
|
603
|
-
// SDK wrote — would rename the variable out from under every
|
|
604
|
-
// `=vars.<name>` reader, so the typed rule wins and the row migrates.
|
|
605
|
-
if (target && OWN_NAME_OUTPUT_ROWS.has(target) && row.id !== target.id)
|
|
606
|
-
return;
|
|
607
|
-
if (target && typeof row.id === 'string' && row.id !== currentId && heldOutputIds.has(row.id))
|
|
608
|
-
return;
|
|
609
|
-
take(target, row.id);
|
|
610
|
-
if (target && typeof row.id === 'string' && typeof currentId === 'string') {
|
|
611
|
-
heldOutputIds.delete(currentId);
|
|
612
|
-
heldOutputIds.add(row.id);
|
|
613
|
-
}
|
|
614
|
-
if (typeof currentId === 'string' && typeof row.id === 'string' && target?.value === currentId) {
|
|
615
|
-
target.value = row.id;
|
|
616
|
-
}
|
|
617
|
-
});
|
|
807
|
+
takeOutputRows(task?.data?.outputs ?? [], taskIdentity.outputs ?? []);
|
|
618
808
|
}));
|
|
619
809
|
}
|
|
620
810
|
const metadata = result.metadata;
|
|
@@ -627,11 +817,12 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
627
817
|
return;
|
|
628
818
|
take(variables[lane]?.find((candidate) => candidate.name === row.name), row.id);
|
|
629
819
|
});
|
|
820
|
+
const longestFirst = [...replacements].sort((a, b) => b[0].length - a[0].length);
|
|
630
821
|
const replace = (value) => {
|
|
631
822
|
if (replacements.has(value))
|
|
632
823
|
return replacements.get(value);
|
|
633
824
|
let replaced = value;
|
|
634
|
-
for (const [current, wanted] of
|
|
825
|
+
for (const [current, wanted] of longestFirst)
|
|
635
826
|
replaced = replaced.replaceAll(current, wanted);
|
|
636
827
|
return replaced;
|
|
637
828
|
};
|
|
@@ -696,6 +887,33 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
696
887
|
rewrite(result);
|
|
697
888
|
for (const { row, source } of preservedOutputSources)
|
|
698
889
|
row.source = source;
|
|
890
|
+
// An id hashed from a generated element id is re-minted from the preserved one,
|
|
891
|
+
// wherever it is spelled, including inside a `=jsonString:` configuration.
|
|
892
|
+
const reseeded = new Map();
|
|
893
|
+
for (const entry of ELEMENT_SEEDED_IDS.get(result) ?? []) {
|
|
894
|
+
const elementId = replace(entry.elementId);
|
|
895
|
+
if (elementId !== entry.elementId)
|
|
896
|
+
reseeded.set(entry.id, stableId(`${elementId}${entry.suffix}`));
|
|
897
|
+
}
|
|
898
|
+
if (reseeded.size)
|
|
899
|
+
reseed(result, reseeded);
|
|
900
|
+
}
|
|
901
|
+
function reseed(value, ids) {
|
|
902
|
+
if (!value || typeof value !== 'object')
|
|
903
|
+
return;
|
|
904
|
+
const entries = Array.isArray(value) ? value.map((entry, index) => [index, entry]) : Object.entries(value);
|
|
905
|
+
for (const [key, entry] of entries) {
|
|
906
|
+
if (typeof entry !== 'string') {
|
|
907
|
+
reseed(entry, ids);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
let next = entry;
|
|
911
|
+
for (const [from, to] of ids)
|
|
912
|
+
if (next.includes(from))
|
|
913
|
+
next = next.replaceAll(from, to);
|
|
914
|
+
if (next !== entry)
|
|
915
|
+
value[key] = next;
|
|
916
|
+
}
|
|
699
917
|
}
|
|
700
918
|
function emitVariables(vars, ids, triggerNodeByRef) {
|
|
701
919
|
const inputs = [];
|
|
@@ -1091,7 +1309,7 @@ function resolvedSubscriptionBag(kind, spec, resolver, elementId, where) {
|
|
|
1091
1309
|
const folder = folderBindingRef(resolver, spec.folder, connection, { attribute: 'folder', where, target });
|
|
1092
1310
|
if (folder)
|
|
1093
1311
|
context.push(io('folderKey', 'string', { value: `=bindings.${folder.id}` }));
|
|
1094
|
-
context.push(io('method', 'string', {}), io('path', 'string', {}), io('objectName', 'string', { value: op.objectName ?? spec.event }), io('operation', 'string', { value: op.eventOperation ?? spec.event }), io('metadata', 'json', { body: subscriptionMetadata(rc, op, spec.filters ?? [], elementId) }));
|
|
1312
|
+
context.push(io('method', 'string', {}), io('path', 'string', {}), io('objectName', 'string', { value: op.objectName ?? spec.event }), io('operation', 'string', { value: op.eventOperation ?? spec.event }), io('metadata', 'json', { body: subscriptionMetadata(rc, op, spec.filters ?? [], elementId, resolver) }));
|
|
1095
1313
|
return {
|
|
1096
1314
|
serviceType: kind === 'trigger' ? CASE_FORMAT_PROFILE.eventServiceType : CASE_FORMAT_PROFILE.waitConnectorServiceType,
|
|
1097
1315
|
context,
|
|
@@ -1106,7 +1324,7 @@ function eventInput(where, elementId) {
|
|
|
1106
1324
|
? { name: 'body', type: 'json', target: 'body', body: { queryParams: where }, var: 'vbody', id: 'vbody', elementId }
|
|
1107
1325
|
: { name: 'body', type: 'json', target: 'body', value: '', var: 'vbody', id: 'vbody', elementId };
|
|
1108
1326
|
}
|
|
1109
|
-
function subscriptionMetadata(rc, op, filters, elementId) {
|
|
1327
|
+
function subscriptionMetadata(rc, op, filters, elementId, resolver) {
|
|
1110
1328
|
const instanceParameters = {
|
|
1111
1329
|
connectorKey: rc.connectorKey,
|
|
1112
1330
|
objectName: op.objectName ?? rc.nodeType.split('.').pop(),
|
|
@@ -1131,7 +1349,7 @@ function subscriptionMetadata(rc, op, filters, elementId) {
|
|
|
1131
1349
|
const { read } = judged;
|
|
1132
1350
|
return {
|
|
1133
1351
|
...eventFilterTreeLeaf(read),
|
|
1134
|
-
uiId:
|
|
1352
|
+
uiId: elementSeededId(resolver, elementId, `:${read.field}:${String(read.value)}:${index}`),
|
|
1135
1353
|
};
|
|
1136
1354
|
}).map((leaf) => ({
|
|
1137
1355
|
id: leaf.id,
|
|
@@ -1140,7 +1358,7 @@ function subscriptionMetadata(rc, op, filters, elementId) {
|
|
|
1140
1358
|
uiId: leaf.uiId,
|
|
1141
1359
|
})),
|
|
1142
1360
|
groups: [],
|
|
1143
|
-
uuId:
|
|
1361
|
+
uuId: elementSeededId(resolver, elementId, ':filters'),
|
|
1144
1362
|
} : null;
|
|
1145
1363
|
const configuration = {
|
|
1146
1364
|
essentialConfiguration: {
|
|
@@ -1182,16 +1400,6 @@ function subscriptionMetadata(rc, op, filters, elementId) {
|
|
|
1182
1400
|
},
|
|
1183
1401
|
};
|
|
1184
1402
|
}
|
|
1185
|
-
function stableUuid(seed) {
|
|
1186
|
-
let h1 = 0x811c9dc5;
|
|
1187
|
-
let h2 = 0x01000193;
|
|
1188
|
-
for (let index = 0; index < seed.length; index++) {
|
|
1189
|
-
h1 = (h1 ^ seed.charCodeAt(index)) * 0x01000193 >>> 0;
|
|
1190
|
-
h2 = (h2 + seed.charCodeAt(index) * (index + 7)) >>> 0;
|
|
1191
|
-
}
|
|
1192
|
-
const hex = (value, length) => value.toString(16).padStart(8, '0').slice(0, length);
|
|
1193
|
-
return `${hex(h1, 8)}-${hex(h2, 4)}-4${hex(h1 >>> 8, 3)}-8${hex(h2 >>> 8, 3)}-${hex(h1 ^ h2, 8)}${hex(h2, 4)}`;
|
|
1194
|
-
}
|
|
1195
1403
|
// ─── Reference-mode task io-binding (data.inputs[] / data.outputs[]) ───────────
|
|
1196
1404
|
//
|
|
1197
1405
|
// Wire shape verified against a real `uip maestro case` caseplan (the io-binding
|
|
@@ -80,7 +80,12 @@ export declare function processResourceLocation(spec: {
|
|
|
80
80
|
export declare function processResourceBindings(spec: {
|
|
81
81
|
name: string;
|
|
82
82
|
folderPath: string;
|
|
83
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The binding's resource kind. `'process'` (the default) for a process
|
|
85
|
+
* reference; `'app'` for an Action App, which carries no subtype.
|
|
86
|
+
*/
|
|
87
|
+
resource?: 'process' | 'app';
|
|
88
|
+
resourceSubType?: ProcessResourceSubType;
|
|
84
89
|
resourceKey?: string;
|
|
85
90
|
}, existing: ReadonlyMap<string, any>): {
|
|
86
91
|
ids: {
|
|
@@ -63,8 +63,9 @@ export function processResourceLocation(spec) {
|
|
|
63
63
|
* process share one pair.
|
|
64
64
|
*/
|
|
65
65
|
export function processResourceBindings(spec, existing) {
|
|
66
|
+
const resource = spec.resource ?? 'process';
|
|
66
67
|
const resourceKey = spec.resourceKey ?? `${spec.folderPath}.${spec.name}`;
|
|
67
|
-
const found = (attr) => [...existing.values()].find((b) => b.resource ===
|
|
68
|
+
const found = (attr) => [...existing.values()].find((b) => b.resource === resource && b.resourceKey === resourceKey && b.propertyAttribute === attr)?.id;
|
|
68
69
|
const already = { name: found('name'), folderPath: found('folderPath') };
|
|
69
70
|
if (already.name && already.folderPath) {
|
|
70
71
|
return { ids: { name: already.name, folderPath: already.folderPath }, entries: [] };
|
|
@@ -72,7 +73,7 @@ export function processResourceBindings(spec, existing) {
|
|
|
72
73
|
// A readable, stable id per process. Two processes sharing a NAME across folders
|
|
73
74
|
// would collide, so the id is suffixed until it is free — the queue emitter's
|
|
74
75
|
// shape, which this is a port of.
|
|
75
|
-
const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || 'Process';
|
|
76
|
+
const slug = spec.name.replace(/[^A-Za-z0-9]/g, '') || (resource === 'app' ? 'App' : 'Process');
|
|
76
77
|
let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
|
|
77
78
|
for (let n = 2; existing.has(`${base}Name`) || existing.has(`${base}FolderPath`); n++) {
|
|
78
79
|
base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}${n}`;
|
|
@@ -83,21 +84,21 @@ export function processResourceBindings(spec, existing) {
|
|
|
83
84
|
id: ids.name,
|
|
84
85
|
name: 'name',
|
|
85
86
|
type: 'string',
|
|
86
|
-
resource
|
|
87
|
+
resource,
|
|
87
88
|
resourceKey,
|
|
88
89
|
default: spec.name,
|
|
89
90
|
propertyAttribute: 'name',
|
|
90
|
-
resourceSubType: spec.resourceSubType,
|
|
91
|
+
...(spec.resourceSubType !== undefined ? { resourceSubType: spec.resourceSubType } : {}),
|
|
91
92
|
},
|
|
92
93
|
{
|
|
93
94
|
id: ids.folderPath,
|
|
94
95
|
name: 'folderPath',
|
|
95
96
|
type: 'string',
|
|
96
|
-
resource
|
|
97
|
+
resource,
|
|
97
98
|
resourceKey,
|
|
98
99
|
default: spec.folderPath,
|
|
99
100
|
propertyAttribute: 'folderPath',
|
|
100
|
-
resourceSubType: spec.resourceSubType,
|
|
101
|
+
...(spec.resourceSubType !== undefined ? { resourceSubType: spec.resourceSubType } : {}),
|
|
101
102
|
},
|
|
102
103
|
] };
|
|
103
104
|
}
|
package/dist/core/refusals.d.ts
CHANGED
|
@@ -306,6 +306,7 @@ export declare const REFUSALS: {
|
|
|
306
306
|
readonly HITL_APP_INCOMPLETE: "lint";
|
|
307
307
|
readonly HITL_APP_INPUT_REQUIRED: "lint";
|
|
308
308
|
readonly HITL_APP_INPUT_UNKNOWN: "lint";
|
|
309
|
+
readonly HITL_APP_LOCAL_HAS_FOLDER: "lint";
|
|
309
310
|
readonly HITL_APP_MISSING: "lint";
|
|
310
311
|
readonly HITL_APP_NO_SCHEMA: "lint";
|
|
311
312
|
readonly HITL_APP_OUTCOME_UNKNOWN: "lint";
|
package/dist/core/refusals.js
CHANGED
|
@@ -347,6 +347,7 @@ export const REFUSALS = {
|
|
|
347
347
|
HITL_APP_INCOMPLETE: 'lint',
|
|
348
348
|
HITL_APP_INPUT_REQUIRED: 'lint',
|
|
349
349
|
HITL_APP_INPUT_UNKNOWN: 'lint',
|
|
350
|
+
HITL_APP_LOCAL_HAS_FOLDER: 'lint',
|
|
350
351
|
HITL_APP_MISSING: 'lint',
|
|
351
352
|
HITL_APP_NO_SCHEMA: 'lint',
|
|
352
353
|
HITL_APP_OUTCOME_UNKNOWN: 'lint',
|
package/dist/merge.js
CHANGED
|
@@ -454,6 +454,7 @@ function mergeFlowFindings(original, edited, options) {
|
|
|
454
454
|
// not carried (#1133). ──
|
|
455
455
|
result.bindings = mergeBindings(original.bindings, edited.bindings);
|
|
456
456
|
result.bindings = withoutRowsMintedForKeptNodes(result.bindings, original, edited, options.baseline, keptFromOriginal, fieldwise);
|
|
457
|
+
result.bindings = withoutSupersededAppRows(result.bindings, originalNodes, allNodes);
|
|
457
458
|
// metadata / solutionId / projectId / layout / runtime were carried by the
|
|
458
459
|
// initial clone of `original` and are intentionally left untouched, apart
|
|
459
460
|
// from the `layout.nodes` entries of removed nodes (dropped above) and added
|
|
@@ -667,6 +668,36 @@ function mergeBindings(orig, edited) {
|
|
|
667
668
|
}
|
|
668
669
|
return out;
|
|
669
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* The `app` rows a human task's app resolves through: keyed by the app's `key`
|
|
673
|
+
* once it has a folder, by its `name` while it has none — flow-v1's rule
|
|
674
|
+
* (`hitl-serialization.ts`, #3910), and `appResourceBindings`'.
|
|
675
|
+
*/
|
|
676
|
+
function appRowKey(node) {
|
|
677
|
+
const app = node.inputs?.app;
|
|
678
|
+
if (!app || typeof app !== 'object')
|
|
679
|
+
return undefined;
|
|
680
|
+
const k = app.folderPath ? app.key : app.name;
|
|
681
|
+
return typeof k === 'string' && k !== '' ? k : undefined;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Drop the original's `app` rows that a human task's app resolved through and
|
|
685
|
+
* that no merged node resolves through any more. That happens when the edit
|
|
686
|
+
* moves an app between the two keys — the canvas's `'solution_folder'` folder
|
|
687
|
+
* (keyed by `key`) re-emitted as an in-solution app (folder `''`, keyed by
|
|
688
|
+
* `name`): the edit brings the new pair, and the union would keep the old one
|
|
689
|
+
* beside it, orphaned (builder-sdk#1486 review). Rows no original human task
|
|
690
|
+
* used (an agent escalation's, say) are not touched.
|
|
691
|
+
*/
|
|
692
|
+
function withoutSupersededAppRows(rows, originalNodes, mergedNodes) {
|
|
693
|
+
if (!rows)
|
|
694
|
+
return rows;
|
|
695
|
+
const keysOf = (nodes) => new Set(nodes.map(appRowKey).filter((k) => k !== undefined));
|
|
696
|
+
const before = keysOf(originalNodes);
|
|
697
|
+
const after = keysOf(mergedNodes);
|
|
698
|
+
return rows.filter((b) => !(b.resource === 'app' && typeof b.resourceKey === 'string'
|
|
699
|
+
&& before.has(b.resourceKey) && !after.has(b.resourceKey)));
|
|
700
|
+
}
|
|
670
701
|
/**
|
|
671
702
|
* Drop each added row that the no-edit recompile minted only for nodes the merge
|
|
672
703
|
* kept from the original (#1133). A row qualifies when the original lacks it, the
|