@uipath/maestro-builder-sdk 6.32.8 → 6.33.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 +41 -40
- package/dist/api-members.md +144 -141
- package/dist/case/case-sdk.d.ts +8 -2
- package/dist/case/case-sdk.js +8 -2
- package/dist/case/serialize.js +23 -8
- package/dist/case/task-sets.d.ts +32 -0
- package/dist/case/task-sets.js +43 -0
- package/dist/check.js +107 -5
- package/dist/core/actions.d.ts +47 -5
- package/dist/core/agent-resource-identity.d.ts +34 -0
- package/dist/core/agent-resource-identity.js +37 -0
- package/dist/decompile.js +57 -14
- package/dist/serialize.js +64 -29
- package/package.json +2 -2
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -1010,10 +1010,16 @@ declare class StageBuilder {
|
|
|
1010
1010
|
* scheduler rules whether they share a set or not, and the designer writes
|
|
1011
1011
|
* one task per set even for a stage its own SDD calls parallel.
|
|
1012
1012
|
*
|
|
1013
|
+
* A task without a `runs-sequentially` rule is always written to a set of its
|
|
1014
|
+
* own, placed right after the set `lane` names; `lane` only groups sequential
|
|
1015
|
+
* tasks. Case schema V31 made set membership behavioural: its V30→V31
|
|
1016
|
+
* migration rewrites the entry conditions of any non-sequential task that
|
|
1017
|
+
* shares a set (builder-sdk#1248).
|
|
1018
|
+
*
|
|
1013
1019
|
* @param displayName - The task's display name.
|
|
1014
1020
|
* @param fn - Receives a sub-builder for what the task does.
|
|
1015
|
-
* @param opts - `lane` is the
|
|
1016
|
-
*
|
|
1021
|
+
* @param opts - `lane` is the index of the set a `runs-sequentially` task joins
|
|
1022
|
+
* in `data.tasks[][]` (default 0). Any other task gets its own set after it.
|
|
1017
1023
|
* @returns This builder, so calls chain.
|
|
1018
1024
|
*/
|
|
1019
1025
|
task(displayName: string, fn: (t: TaskBuilder) => void, opts?: {
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -516,10 +516,16 @@ class StageBuilder {
|
|
|
516
516
|
* scheduler rules whether they share a set or not, and the designer writes
|
|
517
517
|
* one task per set even for a stage its own SDD calls parallel.
|
|
518
518
|
*
|
|
519
|
+
* A task without a `runs-sequentially` rule is always written to a set of its
|
|
520
|
+
* own, placed right after the set `lane` names; `lane` only groups sequential
|
|
521
|
+
* tasks. Case schema V31 made set membership behavioural: its V30→V31
|
|
522
|
+
* migration rewrites the entry conditions of any non-sequential task that
|
|
523
|
+
* shares a set (builder-sdk#1248).
|
|
524
|
+
*
|
|
519
525
|
* @param displayName - The task's display name.
|
|
520
526
|
* @param fn - Receives a sub-builder for what the task does.
|
|
521
|
-
* @param opts - `lane` is the
|
|
522
|
-
*
|
|
527
|
+
* @param opts - `lane` is the index of the set a `runs-sequentially` task joins
|
|
528
|
+
* in `data.tasks[][]` (default 0). Any other task gets its own set after it.
|
|
523
529
|
* @returns This builder, so calls chain.
|
|
524
530
|
*/
|
|
525
531
|
task(displayName, fn, opts = {}) {
|
package/dist/case/serialize.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { IdGen } from './ids.js';
|
|
12
12
|
import { isUnrestorableSlotId } from './preserve.js';
|
|
13
|
+
import { v31TaskSets } from './task-sets.js';
|
|
13
14
|
import { buildConnectorInputs, nestConnectorBody, transportHttpMethod } from '../core/connector-inputs.js';
|
|
14
15
|
import { EQUALS_EXPR_DIALECT } from '../core/connector-checks.js';
|
|
15
16
|
import { bindingLabelRefusal } from '../core/binding-messages.js';
|
|
@@ -109,6 +110,8 @@ export function serialize(built, opts = {}) {
|
|
|
109
110
|
built.stages.forEach((stage) => prealloc(stage.slaRules, stage.label));
|
|
110
111
|
const stageIds = [];
|
|
111
112
|
const taskIds = []; // [stageIndex][lane][taskIndex]
|
|
113
|
+
// The task sets as written: one set per parallel task (see `laneSets`).
|
|
114
|
+
const stageLanes = built.stages.map((stage) => laneSets(stage.lanes));
|
|
112
115
|
// Task displayNames must be unique across the whole case: the platform rejects
|
|
113
116
|
// duplicates (CASE_MGMT_STAGE_TASK_NAME_DUPLICATE, CRITICAL), and a bare
|
|
114
117
|
// `selected-tasks-completed` reference resolves against a single name→id map, so
|
|
@@ -121,7 +124,7 @@ export function serialize(built, opts = {}) {
|
|
|
121
124
|
resolver.stageIdByLabel.set(stage.label, sid);
|
|
122
125
|
stageIds.push(sid);
|
|
123
126
|
taskIds.push([]);
|
|
124
|
-
|
|
127
|
+
stageLanes[si].forEach((lane, li) => {
|
|
125
128
|
taskIds[si].push([]);
|
|
126
129
|
lane.forEach((task) => {
|
|
127
130
|
if (seenTaskNames.has(task.name))
|
|
@@ -144,7 +147,7 @@ export function serialize(built, opts = {}) {
|
|
|
144
147
|
const triggerNodeByRef = new Map();
|
|
145
148
|
built.triggers.forEach((t, i) => triggerNodeByRef.set(t, triggerNodes[i]));
|
|
146
149
|
built.stages.forEach((stage, si) => {
|
|
147
|
-
nodes.push(emitStage(stage, si, stageIds[si], taskIds[si], ids, resolver));
|
|
150
|
+
nodes.push(emitStage(stage, stageLanes[si], si, stageIds[si], taskIds[si], ids, resolver));
|
|
148
151
|
});
|
|
149
152
|
// ── Metadata (case-level) ──
|
|
150
153
|
const metadata = {
|
|
@@ -382,7 +385,7 @@ export function applyPreservation(result, preservation, options = {}) {
|
|
|
382
385
|
.filter((node) => String(node.type ?? '').endsWith('Stage'));
|
|
383
386
|
for (const entry of preservation.taskFields ?? []) {
|
|
384
387
|
const stage = stageNodes.find((candidate) => candidate.data?.label === entry.stage);
|
|
385
|
-
const task = stage
|
|
388
|
+
const task = stageTask(stage, entry.task);
|
|
386
389
|
// A brownfield edit may deliberately delete a typed task or its stage. Its
|
|
387
390
|
// attached designer-only fields leave with that construct; surviving tasks
|
|
388
391
|
// still collide-refuse below instead of letting preservation overwrite
|
|
@@ -408,7 +411,7 @@ export function applyPreservation(result, preservation, options = {}) {
|
|
|
408
411
|
}
|
|
409
412
|
for (const entry of preservation.omitTaskElementIds ?? []) {
|
|
410
413
|
const stage = stageNodes.find((candidate) => candidate.data?.label === entry.stage);
|
|
411
|
-
const task = stage
|
|
414
|
+
const task = stageTask(stage, entry.task);
|
|
412
415
|
if (task)
|
|
413
416
|
delete task.elementId;
|
|
414
417
|
}
|
|
@@ -483,8 +486,8 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
483
486
|
takeConditions(stage?.data?.entryConditions, stageIdentity.entryConditions);
|
|
484
487
|
takeConditions(stage?.data?.exitConditions, stageIdentity.exitConditions);
|
|
485
488
|
takeSlas(stage?.data?.slaRules, stageIdentity.slaRules);
|
|
486
|
-
(stageIdentity.tasks ?? []).forEach((lane
|
|
487
|
-
const task = stage
|
|
489
|
+
(stageIdentity.tasks ?? []).forEach((lane) => lane.forEach((taskIdentity) => {
|
|
490
|
+
const task = stageTask(stage, taskIdentity.name);
|
|
488
491
|
take(task, taskIdentity.id);
|
|
489
492
|
takeConditions(task?.entryConditions, taskIdentity.entryConditions);
|
|
490
493
|
(taskIdentity.inputs ?? []).forEach((row, index) => take(task?.data?.inputs?.[index], row.id));
|
|
@@ -729,13 +732,25 @@ function emitTriggers(triggers, ids, resolver) {
|
|
|
729
732
|
};
|
|
730
733
|
});
|
|
731
734
|
}
|
|
732
|
-
|
|
735
|
+
/** A stage's task sets as the serializer writes them — one set per parallel task (`v31TaskSets`). */
|
|
736
|
+
function laneSets(lanes) {
|
|
737
|
+
return v31TaskSets(lanes, (task) => task.entry.some((condition) => condition.rules.some((group) => group.some((r) => r.rule === 'runs-sequentially'))));
|
|
738
|
+
}
|
|
739
|
+
/** One emitted task of a stage node, by its (case-unique) display name, in any set. */
|
|
740
|
+
function stageTask(stage, name) {
|
|
741
|
+
for (const lane of stage?.data?.tasks ?? [])
|
|
742
|
+
for (const candidate of lane)
|
|
743
|
+
if (candidate.displayName === name)
|
|
744
|
+
return candidate;
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
function emitStage(stage, lanes, si, stageId, laneTaskIds, ids, resolver) {
|
|
733
748
|
const data = {
|
|
734
749
|
[CASE_FORMAT_PROFILE.stageLabelField]: stage.label,
|
|
735
750
|
parentElement: { id: 'root', type: 'case-management:root' },
|
|
736
751
|
isInvalidDropTarget: false,
|
|
737
752
|
isPendingParent: false,
|
|
738
|
-
tasks:
|
|
753
|
+
tasks: lanes.map((lane, li) => lane.map((task, ti) => emitTask(task, stage.label, laneTaskIds[li][ti], ids, resolver))),
|
|
739
754
|
};
|
|
740
755
|
if (stage.description)
|
|
741
756
|
data.description = stage.description;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task sets (`data.tasks[i]`) in the shape Case schema V31+ keeps unchanged.
|
|
3
|
+
*
|
|
4
|
+
* V31 made a set a sequential chain. Its V30→V31 migration — which the product
|
|
5
|
+
* runs on every V30 document it reads — keeps a set unchanged only when it holds
|
|
6
|
+
* one task, or when every task in it declares a `runs-sequentially` rule. Every
|
|
7
|
+
* other task in a shared set has its entry conditions REPLACED by a generated
|
|
8
|
+
* `runs-sequentially` condition, so two parallel `current-stage-entered` tasks
|
|
9
|
+
* written into one set ran one after the other, under conditions the author
|
|
10
|
+
* never wrote, and nothing reported it (builder-sdk#1248).
|
|
11
|
+
*
|
|
12
|
+
* At V30 the regrouping is free: the converter reads a set's position only for
|
|
13
|
+
* tasks whose every rule is `runs-sequentially`; every other task runs on its
|
|
14
|
+
* own entry condition wherever it sits. A task the migration would rewrite
|
|
15
|
+
* therefore moves to a set of its own right after the set it was written in, so
|
|
16
|
+
* the stage's flattened task order — the order the converter writes its rules
|
|
17
|
+
* in — is unchanged too. Tasks the migration treats as sequential keep their
|
|
18
|
+
* set. The only case that reorders is a set that mixed both kinds: its
|
|
19
|
+
* sequential tasks stay together and the others follow.
|
|
20
|
+
*
|
|
21
|
+
* Generic over the task shape so the serializer (built tasks) and the tests
|
|
22
|
+
* (emitted JSON) apply the one rule.
|
|
23
|
+
*/
|
|
24
|
+
export declare function v31TaskSets<T>(sets: readonly (readonly T[])[], runsSequentially: (task: T) => boolean): T[][];
|
|
25
|
+
/** The V30→V31 migration's own test, on emitted task JSON: any rule of any entry condition is `runs-sequentially`. */
|
|
26
|
+
export declare function taskJsonRunsSequentially(task: {
|
|
27
|
+
entryConditions?: Array<{
|
|
28
|
+
rules?: Array<Array<{
|
|
29
|
+
rule?: string;
|
|
30
|
+
}>>;
|
|
31
|
+
}>;
|
|
32
|
+
}): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task sets (`data.tasks[i]`) in the shape Case schema V31+ keeps unchanged.
|
|
3
|
+
*
|
|
4
|
+
* V31 made a set a sequential chain. Its V30→V31 migration — which the product
|
|
5
|
+
* runs on every V30 document it reads — keeps a set unchanged only when it holds
|
|
6
|
+
* one task, or when every task in it declares a `runs-sequentially` rule. Every
|
|
7
|
+
* other task in a shared set has its entry conditions REPLACED by a generated
|
|
8
|
+
* `runs-sequentially` condition, so two parallel `current-stage-entered` tasks
|
|
9
|
+
* written into one set ran one after the other, under conditions the author
|
|
10
|
+
* never wrote, and nothing reported it (builder-sdk#1248).
|
|
11
|
+
*
|
|
12
|
+
* At V30 the regrouping is free: the converter reads a set's position only for
|
|
13
|
+
* tasks whose every rule is `runs-sequentially`; every other task runs on its
|
|
14
|
+
* own entry condition wherever it sits. A task the migration would rewrite
|
|
15
|
+
* therefore moves to a set of its own right after the set it was written in, so
|
|
16
|
+
* the stage's flattened task order — the order the converter writes its rules
|
|
17
|
+
* in — is unchanged too. Tasks the migration treats as sequential keep their
|
|
18
|
+
* set. The only case that reorders is a set that mixed both kinds: its
|
|
19
|
+
* sequential tasks stay together and the others follow.
|
|
20
|
+
*
|
|
21
|
+
* Generic over the task shape so the serializer (built tasks) and the tests
|
|
22
|
+
* (emitted JSON) apply the one rule.
|
|
23
|
+
*/
|
|
24
|
+
export function v31TaskSets(sets, runsSequentially) {
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const set of sets) {
|
|
27
|
+
if (set.length <= 1 || set.every(runsSequentially)) {
|
|
28
|
+
out.push([...set]);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const sequential = set.filter(runsSequentially);
|
|
32
|
+
if (sequential.length)
|
|
33
|
+
out.push(sequential);
|
|
34
|
+
for (const task of set)
|
|
35
|
+
if (!runsSequentially(task))
|
|
36
|
+
out.push([task]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/** The V30→V31 migration's own test, on emitted task JSON: any rule of any entry condition is `runs-sequentially`. */
|
|
41
|
+
export function taskJsonRunsSequentially(task) {
|
|
42
|
+
return (task.entryConditions ?? []).some((condition) => (condition.rules ?? []).some((group) => group.some((r) => r?.rule === 'runs-sequentially')));
|
|
43
|
+
}
|
package/dist/check.js
CHANGED
|
@@ -34,9 +34,29 @@ import { plainHttpOnErrorMessage } from './nodes/http/flow/family.js';
|
|
|
34
34
|
import { resolveVersion, resolveVersionCarried } from './core/nodes/versioned.js';
|
|
35
35
|
import { declaredErrorPortRefusal } from './core/error-port.js';
|
|
36
36
|
import { bodyUsesBreak, loopParallelWarning, loopVersionRefusal } from './loop-version.js';
|
|
37
|
+
import { derivedResourceNodeId } from './core/agent-resource-identity.js';
|
|
38
|
+
const newResourceIdLedger = () => ({ explicit: new Map(), taken: new Map(), agentSources: new Map() });
|
|
39
|
+
/** INLINE_AGENT_RESOURCE_NODE_ID for each explicit `nodeId` another node of the file also gets. */
|
|
40
|
+
function resourceNodeIdClashes(ledger) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const [id, { step, what }] of ledger.explicit) {
|
|
43
|
+
const other = ledger.taken.get(id);
|
|
44
|
+
if (other === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({
|
|
47
|
+
level: 'error', code: 'INLINE_AGENT_RESOURCE_NODE_ID', step,
|
|
48
|
+
message: `Inline agent step "${step}" gives its ${what} the \`nodeId\` "${id}", which is already `
|
|
49
|
+
+ `${other}. Node ids are unique in a flow, so compile cannot write both. Give the resource another id, `
|
|
50
|
+
+ 'or omit `nodeId` to derive one.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
37
55
|
export function check(built, opts = {}) {
|
|
38
56
|
let diags = [];
|
|
39
|
-
const
|
|
57
|
+
const ledger = newResourceIdLedger();
|
|
58
|
+
const uses = collectConnectionUses(() => { diags = checkFlow(built, [], opts, ledger); });
|
|
59
|
+
diags.push(...resourceNodeIdClashes(ledger));
|
|
40
60
|
for (const { id, message } of connectionConnectorConflicts(uses, opts)) {
|
|
41
61
|
diags.push({ level: 'error', code: 'CONNECTION_CONNECTOR_CONFLICT', step: id, message });
|
|
42
62
|
}
|
|
@@ -87,7 +107,7 @@ function checkStepIds(stepNames, trigger, diags) {
|
|
|
87
107
|
* to notice a flow that (transitively) calls itself before the serializer
|
|
88
108
|
* recurses forever.
|
|
89
109
|
*/
|
|
90
|
-
function checkFlow(built, ancestors, opts = {}) {
|
|
110
|
+
function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger()) {
|
|
91
111
|
const diags = [];
|
|
92
112
|
if (!built.id)
|
|
93
113
|
diags.push({ level: 'error', code: 'NO_ID', message: 'flow() requires an id.' });
|
|
@@ -516,7 +536,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
516
536
|
checkAgent(s.name, s.spec.inputs, diags);
|
|
517
537
|
}
|
|
518
538
|
if (s.spec.kind === 'inlineAgent') {
|
|
519
|
-
checkInlineAgent(s.name, s.spec.inputs, diags, opts);
|
|
539
|
+
checkInlineAgent(s.name, s.spec.inputs, diags, opts, ledger);
|
|
520
540
|
}
|
|
521
541
|
{
|
|
522
542
|
const rule = OUTPUT_READ_RULES[s.spec.kind];
|
|
@@ -878,6 +898,15 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
878
898
|
}
|
|
879
899
|
reportUnreachableHitlOutcomes(hitlChoiceNoPorts, exprs, diags);
|
|
880
900
|
checkRegisteredFamilies(registeredSteps, unresolvedSteps, stepNames, diags);
|
|
901
|
+
// The ids this scope's other nodes get, for the file-wide nodeId check in `check`.
|
|
902
|
+
for (const n of stepNames)
|
|
903
|
+
if (!ledger.taken.has(n))
|
|
904
|
+
ledger.taken.set(n, `the id of step "${n}"`);
|
|
905
|
+
if (!ledger.taken.has(triggerId))
|
|
906
|
+
ledger.taken.set(triggerId, 'the id of the trigger');
|
|
907
|
+
for (const ep of built.entryPoints ?? [])
|
|
908
|
+
if (!ledger.taken.has(ep.id))
|
|
909
|
+
ledger.taken.set(ep.id, `the id of entry point "${ep.id}"`);
|
|
881
910
|
// Entry points: unique roots with valid triggers, colliding with nothing.
|
|
882
911
|
const epIds = new Set();
|
|
883
912
|
for (const ep of built.entryPoints ?? []) {
|
|
@@ -1294,7 +1323,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1294
1323
|
for (const [stepName, child] of subflowChildren) {
|
|
1295
1324
|
if (ancestors.includes(child) || child === built)
|
|
1296
1325
|
continue;
|
|
1297
|
-
for (const d of checkFlow(child, [...ancestors, built], opts)) {
|
|
1326
|
+
for (const d of checkFlow(child, [...ancestors, built], opts, ledger)) {
|
|
1298
1327
|
diags.push({
|
|
1299
1328
|
...d,
|
|
1300
1329
|
code: d.code,
|
|
@@ -3061,7 +3090,7 @@ export function jsonTextDirective(text, returnKeys) {
|
|
|
3061
3090
|
* ALSO asks for JSON text makes the model pack its whole answer, serialized, into
|
|
3062
3091
|
* one string field. See {@link jsonTextDirective}.
|
|
3063
3092
|
*/
|
|
3064
|
-
function checkInlineAgent(step, inputs, diags, opts = {}) {
|
|
3093
|
+
function checkInlineAgent(step, inputs, diags, opts = {}, ledger = newResourceIdLedger()) {
|
|
3065
3094
|
const at = (code, message, suggestion) => {
|
|
3066
3095
|
diags.push({ level: 'error', code, message, step, ...(suggestion ? { suggestion } : {}) });
|
|
3067
3096
|
};
|
|
@@ -3168,6 +3197,79 @@ function checkInlineAgent(step, inputs, diags, opts = {}) {
|
|
|
3168
3197
|
+ `and the one v1's smoke check for this node type FAILS a flow for keeping (it is the definition's `
|
|
3169
3198
|
+ `\`inputDefaults.model\`, not a choice). Pick a current model: \`uip agent model list\`.`);
|
|
3170
3199
|
}
|
|
3200
|
+
// Where each resource node sits (`nodeId`, `source`): decompile writes them
|
|
3201
|
+
// for a node the canvas placed, and compile writes them as given. A blank or
|
|
3202
|
+
// repeated id would make compile refuse the flow or overwrite another node,
|
|
3203
|
+
// and a `source` that is not a uuid names a directory the platform does not
|
|
3204
|
+
// expect (the agent's own `source` has the same rule).
|
|
3205
|
+
const asList = (v) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);
|
|
3206
|
+
const placed = [
|
|
3207
|
+
...asList(inputs?.context).map((r) => ({ what: `context index "${r?.name ?? ''}"`, kind: 'Context', nodeId: r?.nodeId, source: r?.source })),
|
|
3208
|
+
...asList(inputs?.tools).map((r) => ({ what: `${r?.kind ?? ''} tool`, kind: 'Tool', nodeId: r?.nodeId, source: r?.source })),
|
|
3209
|
+
...asList(inputs?.escalation).map((r) => ({ what: `escalation "${r?.name ?? ''}"`, kind: 'Escalation', nodeId: r?.nodeId, source: r?.source })),
|
|
3210
|
+
];
|
|
3211
|
+
// The ids compile derives for the resources with no `nodeId`: the n-th one of
|
|
3212
|
+
// a kind gets `<step><Kind>`, then `<step><Kind>2`, ... An explicit id equal to
|
|
3213
|
+
// one of them makes compile refuse the flow, or rename the derived node.
|
|
3214
|
+
for (const kind of ['Context', 'Tool', 'Escalation']) {
|
|
3215
|
+
const derived = placed.filter((r) => r.kind === kind && r.nodeId === undefined);
|
|
3216
|
+
derived.forEach((r, n) => {
|
|
3217
|
+
const id = derivedResourceNodeId(step, kind, n);
|
|
3218
|
+
if (!ledger.taken.has(id))
|
|
3219
|
+
ledger.taken.set(id, `the id compile derives for the ${r.what} of inline agent step "${step}"`);
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
/** Explicit `source` -> the resource on THIS agent that has it: two would write one resource.json. */
|
|
3223
|
+
const sources = new Map();
|
|
3224
|
+
for (const r of placed) {
|
|
3225
|
+
if (r.nodeId !== undefined) {
|
|
3226
|
+
const id = typeof r.nodeId === 'string' ? r.nodeId : '';
|
|
3227
|
+
const other = ledger.explicit.get(id);
|
|
3228
|
+
if (id.trim() === '') {
|
|
3229
|
+
at('INLINE_AGENT_RESOURCE_NODE_ID', `Inline agent step "${step}" gives its ${r.what} the \`nodeId\` ${JSON.stringify(r.nodeId)}, which is `
|
|
3230
|
+
+ `not a node id. Omit \`nodeId\` to derive one, or give the id the node has in the flow.`);
|
|
3231
|
+
}
|
|
3232
|
+
else if (other !== undefined) {
|
|
3233
|
+
at('INLINE_AGENT_RESOURCE_NODE_ID', `Inline agent step "${step}" gives its ${r.what} the \`nodeId\` "${id}", which the ${other.what} `
|
|
3234
|
+
+ `(on step "${other.step}") already has. Node ids are unique in a flow.`);
|
|
3235
|
+
}
|
|
3236
|
+
else {
|
|
3237
|
+
ledger.explicit.set(id, { step, what: r.what });
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
if (r.source === undefined)
|
|
3241
|
+
continue;
|
|
3242
|
+
// Not trimmed: compile writes `source` as given, into `inputs.source` and the directory name.
|
|
3243
|
+
if (!GUID_RE.test(String(r.source))) {
|
|
3244
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_NOT_GUID', `Inline agent step "${step}" gives its ${r.what} the \`source\` ${JSON.stringify(r.source)}, which is not `
|
|
3245
|
+
+ `a uuid${/^\s|\s$/.test(String(r.source)) ? ' (it has spaces around it)' : ''}. \`source\` is the resource's `
|
|
3246
|
+
+ `directory under the agent's own (\`resources/<source>/resource.json\`) and the node's \`inputs.source\`. `
|
|
3247
|
+
+ 'Omit it and one is derived from the flow id, the step and the resource.');
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
const key = String(r.source).toLowerCase();
|
|
3251
|
+
const first = sources.get(key);
|
|
3252
|
+
if (first !== undefined) {
|
|
3253
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_DUP', `Inline agent step "${step}" gives its ${first} and its ${r.what} the same \`source\` "${String(r.source)}". `
|
|
3254
|
+
+ 'Both would write `resources/<source>/resource.json` under this agent, and the second file would replace '
|
|
3255
|
+
+ 'the first. Give each resource its own `source`, or omit `source` to derive one.');
|
|
3256
|
+
}
|
|
3257
|
+
else {
|
|
3258
|
+
sources.set(key, r.what);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
if (typeof inputs?.source === 'string' && GUID_RE.test(inputs.source.trim())) {
|
|
3262
|
+
const key = inputs.source.trim().toLowerCase();
|
|
3263
|
+
const other = ledger.agentSources.get(key);
|
|
3264
|
+
if (other !== undefined && other !== step) {
|
|
3265
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_DUP', `Inline agent steps "${other}" and "${step}" have the same \`source\` "${inputs.source}". Both would write `
|
|
3266
|
+
+ '`<source>/agent.json`, and the second file would replace the first. Give each agent its own `source`, '
|
|
3267
|
+
+ 'or omit it to derive one.');
|
|
3268
|
+
}
|
|
3269
|
+
else {
|
|
3270
|
+
ledger.agentSources.set(key, step);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3171
3273
|
if (inputs?.source !== undefined && !GUID_RE.test(String(inputs.source).trim())) {
|
|
3172
3274
|
at('INLINE_AGENT_SOURCE_NOT_GUID', `Inline agent step "${step}"'s \`source\` is ${JSON.stringify(inputs.source)}, which is not a uuid. `
|
|
3173
3275
|
+ `\`source\` is the agent's own DIRECTORY name, and the platform resolves its configuration at `
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -1131,7 +1131,43 @@ export type BuiltinToolName = 'analyzefiles' | 'summarize' | 'batchtransform';
|
|
|
1131
1131
|
* `tool.mcp.*`/`uipath.agent.resource.mcp.*` resolves to nothing on this tenant
|
|
1132
1132
|
* (no MCP server exists to point at, so there is no definition to derive).
|
|
1133
1133
|
*/
|
|
1134
|
-
export type ToolRef = BuiltinToolRef | ConnectorToolRef | ProcessToolRef | IxpToolRef | McpToolRef | RemoteA2aToolRef | ClientSideToolRef | HttpRequestToolRef;
|
|
1134
|
+
export type ToolRef = (BuiltinToolRef | ConnectorToolRef | ProcessToolRef | IxpToolRef | McpToolRef | RemoteA2aToolRef | ClientSideToolRef | HttpRequestToolRef) & AgentResourcePlacement;
|
|
1135
|
+
/**
|
|
1136
|
+
* Where one of an inline agent's resources (a context index, a tool, an
|
|
1137
|
+
* escalation) sits in the `.flow`: the id of its own resource NODE and the
|
|
1138
|
+
* directory that holds its `resource.json`.
|
|
1139
|
+
*
|
|
1140
|
+
* @remarks
|
|
1141
|
+
* Omit both when you write a flow: compile derives them, so a recompile is not
|
|
1142
|
+
* a diff. Decompile writes them only for a file whose resource node another
|
|
1143
|
+
* tool placed (the canvas, `uip maestro flow node add`) under an id or directory
|
|
1144
|
+
* compile would not derive, so a no-edit round trip keeps the node, its layout
|
|
1145
|
+
* entry and its directory instead of replacing them.
|
|
1146
|
+
*
|
|
1147
|
+
* ```ts
|
|
1148
|
+
* // decompiled from a canvas file that named the index's node `ctx_7f2a1b`
|
|
1149
|
+
* context: [{
|
|
1150
|
+
* name: 'Example SOP Index', id: '11111111-2222-4333-8444-555555555555',
|
|
1151
|
+
* nodeId: 'ctx_7f2a1b', source: 'd84e2c17-6a3f-4c59-b0e2-7f1a9c5b3d28',
|
|
1152
|
+
* }]
|
|
1153
|
+
* ```
|
|
1154
|
+
*/
|
|
1155
|
+
export interface AgentResourcePlacement {
|
|
1156
|
+
/**
|
|
1157
|
+
* The resource node's id. Defaults to `<agent step>Context`, `<agent step>Tool`
|
|
1158
|
+
* or `<agent step>Escalation`, numbered from 2 for a second resource of the
|
|
1159
|
+
* same kind (`<agent step>Context2`). Compile writes it as given and refuses an
|
|
1160
|
+
* id another node in the flow already has (`check`: `INLINE_AGENT_RESOURCE_NODE_ID`).
|
|
1161
|
+
*/
|
|
1162
|
+
nodeId?: string;
|
|
1163
|
+
/**
|
|
1164
|
+
* The resource's directory id, a uuid: the node's `inputs.source` and the
|
|
1165
|
+
* `id` of `<agent source>/resources/<source>/resource.json`. Defaults to one
|
|
1166
|
+
* derived from the flow id, the agent step and the resource, so a given flow
|
|
1167
|
+
* emits the same uuid every time.
|
|
1168
|
+
*/
|
|
1169
|
+
source?: string;
|
|
1170
|
+
}
|
|
1135
1171
|
/**
|
|
1136
1172
|
* An MCP server the agent may call tools on. The node type is minted from the
|
|
1137
1173
|
* server's name and key exactly as the platform's own mapper does
|
|
@@ -1398,9 +1434,9 @@ export interface IxpToolRef {
|
|
|
1398
1434
|
/**
|
|
1399
1435
|
* One escalation an inline agent may raise — an Action Center app task a human
|
|
1400
1436
|
* completes (the `coded-action-app` variant, the one this tenant's registry
|
|
1401
|
-
* serves).
|
|
1437
|
+
* serves), placed in the flow by {@link AgentResourcePlacement}.
|
|
1402
1438
|
*/
|
|
1403
|
-
export interface EscalationRef {
|
|
1439
|
+
export interface EscalationRef extends AgentResourcePlacement {
|
|
1404
1440
|
/** The escalation's name — required by the definition (`minLength: 1`). */
|
|
1405
1441
|
name: string;
|
|
1406
1442
|
/** When the agent should escalate — what a reviewer sees as the reason. */
|
|
@@ -1463,13 +1499,14 @@ export interface EscalationRef {
|
|
|
1463
1499
|
labels?: string[];
|
|
1464
1500
|
}
|
|
1465
1501
|
/**
|
|
1466
|
-
* One Context Grounding index an inline agent is grounded on
|
|
1502
|
+
* One Context Grounding index an inline agent is grounded on, placed in the
|
|
1503
|
+
* flow by {@link AgentResourcePlacement}.
|
|
1467
1504
|
*
|
|
1468
1505
|
* @remarks
|
|
1469
1506
|
* The optional fields mirror the platform's own `inputDefaults` for an index
|
|
1470
1507
|
* resource, so omitting them emits exactly what the designer would.
|
|
1471
1508
|
*/
|
|
1472
|
-
export interface ContextIndexRef {
|
|
1509
|
+
export interface ContextIndexRef extends AgentResourcePlacement {
|
|
1473
1510
|
/** The index's display name on the tenant, e.g. `'Billing Dispute SOP Index'`. */
|
|
1474
1511
|
name: string;
|
|
1475
1512
|
/**
|
|
@@ -1496,6 +1533,11 @@ export interface ContextIndexRef {
|
|
|
1496
1533
|
threshold?: number;
|
|
1497
1534
|
/** Restrict to one file type; `'All'` (the platform's default) or e.g. `'pdf'`. */
|
|
1498
1535
|
fileExtension?: string;
|
|
1536
|
+
/**
|
|
1537
|
+
* The resource node's label on the canvas. Defaults to `name`. Only the
|
|
1538
|
+
* canvas shows it; the index is found by `name` and `id`.
|
|
1539
|
+
*/
|
|
1540
|
+
label?: string;
|
|
1499
1541
|
}
|
|
1500
1542
|
/**
|
|
1501
1543
|
* Every action kind, keyed by kind, to the body of its spec — the arm's fields
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity compile derives for an inline agent's resource node when the
|
|
3
|
+
* author gives none: its node id and its resource directory (`inputs.source`).
|
|
4
|
+
*
|
|
5
|
+
* Serialize uses these to name what it emits. Decompile uses the same functions
|
|
6
|
+
* to decide whether a file's resource node carries the identity compile would
|
|
7
|
+
* derive anyway; when it does not (the canvas or the CLI placed the node), the
|
|
8
|
+
* decompiled source spells it out as `nodeId` / `source`
|
|
9
|
+
* (`AgentResourcePlacement`). One definition for both directions keeps
|
|
10
|
+
* them inverses: a second copy in decompile would drift from serialize's.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolRef } from './actions.js';
|
|
13
|
+
/** The resource handles whose nodes compile names after the agent step. */
|
|
14
|
+
export type AgentResourceKind = 'Context' | 'Tool' | 'Escalation' | 'Memory';
|
|
15
|
+
/**
|
|
16
|
+
* The node id compile gives the `n`-th (0-based) resource of `kind` on agent
|
|
17
|
+
* step `agentStep` when no `nodeId` is set: `<step><Kind>`, then
|
|
18
|
+
* `<step><Kind>2`, `<step><Kind>3`, ... Characters a node id cannot hold become
|
|
19
|
+
* `_`, as compile's own id claim does. This assumes no other node already holds
|
|
20
|
+
* that id, which is the case for a flow compile wrote.
|
|
21
|
+
*/
|
|
22
|
+
export declare function derivedResourceNodeId(agentStep: string, kind: AgentResourceKind, n: number): string;
|
|
23
|
+
/** The directory compile derives for a context index (`id` = the index id). */
|
|
24
|
+
export declare function derivedContextSource(flowId: string, agentStep: string, indexId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* What tells one tool on an agent from another for its derived directory: the
|
|
27
|
+
* built-in's name, the connector operation, the IxP project, the client-side or
|
|
28
|
+
* HTTP tool's name, else the resource key.
|
|
29
|
+
*/
|
|
30
|
+
export declare function toolResourceSeed(tr: ToolRef): string;
|
|
31
|
+
/** The directory compile derives for a tool. */
|
|
32
|
+
export declare function derivedToolSource(flowId: string, agentStep: string, tr: ToolRef): string;
|
|
33
|
+
/** The directory compile derives for an escalation (`name` = the escalation's name). */
|
|
34
|
+
export declare function derivedEscalationSource(flowId: string, agentStep: string, name: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fnv1aX4Uuid } from './stable-id.js';
|
|
2
|
+
/**
|
|
3
|
+
* The node id compile gives the `n`-th (0-based) resource of `kind` on agent
|
|
4
|
+
* step `agentStep` when no `nodeId` is set: `<step><Kind>`, then
|
|
5
|
+
* `<step><Kind>2`, `<step><Kind>3`, ... Characters a node id cannot hold become
|
|
6
|
+
* `_`, as compile's own id claim does. This assumes no other node already holds
|
|
7
|
+
* that id, which is the case for a flow compile wrote.
|
|
8
|
+
*/
|
|
9
|
+
export function derivedResourceNodeId(agentStep, kind, n) {
|
|
10
|
+
const base = `${agentStep}${kind}`.replace(/[^A-Za-z0-9_]/g, '_');
|
|
11
|
+
return n === 0 ? base : `${base}${n + 1}`;
|
|
12
|
+
}
|
|
13
|
+
/** The directory compile derives for a context index (`id` = the index id). */
|
|
14
|
+
export function derivedContextSource(flowId, agentStep, indexId) {
|
|
15
|
+
return fnv1aX4Uuid(`${flowId}/${agentStep}/context/${indexId}`);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What tells one tool on an agent from another for its derived directory: the
|
|
19
|
+
* built-in's name, the connector operation, the IxP project, the client-side or
|
|
20
|
+
* HTTP tool's name, else the resource key.
|
|
21
|
+
*/
|
|
22
|
+
export function toolResourceSeed(tr) {
|
|
23
|
+
return tr.kind === 'builtin' ? tr.tool
|
|
24
|
+
: tr.kind === 'connector' ? `${tr.connector}.${tr.operation}`
|
|
25
|
+
: tr.kind === 'ixp' ? tr.projectId
|
|
26
|
+
: tr.kind === 'clientside' ? `clientside.${tr.name}`
|
|
27
|
+
: tr.kind === 'httpRequest' ? `httprequest.${tr.name ?? 'default'}`
|
|
28
|
+
: tr.key;
|
|
29
|
+
}
|
|
30
|
+
/** The directory compile derives for a tool. */
|
|
31
|
+
export function derivedToolSource(flowId, agentStep, tr) {
|
|
32
|
+
return fnv1aX4Uuid(`${flowId}/${agentStep}/tool/${toolResourceSeed(tr)}`);
|
|
33
|
+
}
|
|
34
|
+
/** The directory compile derives for an escalation (`name` = the escalation's name). */
|
|
35
|
+
export function derivedEscalationSource(flowId, agentStep, name) {
|
|
36
|
+
return fnv1aX4Uuid(`${flowId}/${agentStep}/escalation/${name}`);
|
|
37
|
+
}
|