@uipath/maestro-builder-sdk 6.42.0 → 6.44.0
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 +104 -103
- package/dist/api-members.md +319 -317
- package/dist/check.d.ts +0 -19
- package/dist/check.js +140 -1042
- package/dist/compile-cli.js +2 -1
- package/dist/core/actions.d.ts +10 -713
- package/dist/core/actions.js +0 -193
- package/dist/core/agent-sidecar.d.ts +186 -0
- package/dist/core/agent-sidecar.js +409 -0
- package/dist/core/agent-tool-resources.d.ts +62 -0
- package/dist/core/agent-tool-resources.js +81 -0
- package/dist/{config.js → core/connector-configuration.js} +2 -2
- package/dist/core/form-field-type.d.ts +7 -0
- package/dist/core/form-field-type.js +12 -2
- package/dist/core/node-id-placeholder.d.ts +8 -0
- package/dist/core/node-id-placeholder.js +10 -0
- package/dist/core/ts-literal.d.ts +6 -0
- package/dist/core/ts-literal.js +15 -0
- package/dist/core/typed-raw-node.js +0 -5
- package/dist/decompile.js +239 -857
- package/dist/flow-expr-check.js +5 -0
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +139 -6
- package/dist/flow-sdk.d.ts +41 -5
- package/dist/flow-sdk.js +15 -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/nodes/agent/flow/check.d.ts +88 -0
- package/dist/nodes/agent/flow/check.js +873 -0
- package/dist/nodes/agent/flow/factories.d.ts +134 -0
- package/dist/nodes/agent/flow/factories.js +143 -0
- package/dist/nodes/agent/flow/family.d.ts +3 -0
- package/dist/nodes/agent/flow/family.js +8 -0
- package/dist/nodes/agent/flow/index.d.ts +3 -0
- package/dist/nodes/agent/flow/index.js +3 -0
- package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
- package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
- package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
- package/dist/nodes/agent/flow/inline-agent.js +526 -0
- package/dist/nodes/agent/flow/node-type.d.ts +20 -0
- package/dist/nodes/agent/flow/node-type.js +422 -0
- package/dist/nodes/agent/flow/resources.d.ts +502 -0
- package/dist/nodes/agent/flow/resources.js +1409 -0
- package/dist/nodes/agent/flow/spec.d.ts +448 -0
- package/dist/nodes/agent/flow/spec.js +1 -0
- package/dist/nodes/hitl/flow/check.js +15 -0
- package/dist/nodes/hitl/flow/node-type.js +18 -3
- package/dist/nodes/process/flow/check.d.ts +40 -2
- package/dist/nodes/process/flow/check.js +156 -0
- package/dist/nodes/process/flow/factories.d.ts +58 -3
- package/dist/nodes/process/flow/factories.js +59 -2
- package/dist/nodes/process/flow/node-type.d.ts +4 -1
- package/dist/nodes/process/flow/node-type.js +198 -12
- package/dist/nodes/process/flow/spec.d.ts +112 -5
- package/dist/serialize.d.ts +9 -54
- package/dist/serialize.js +102 -2777
- package/package.json +2 -2
- /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
- /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
package/dist/decompile.js
CHANGED
|
@@ -35,10 +35,7 @@ import { DEFAULT_TRIGGER_ID, Expr } from './core/expr.js';
|
|
|
35
35
|
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
36
36
|
import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
|
|
37
37
|
import { commentText } from './core/comment-text.js';
|
|
38
|
-
import { tsLiteral, tsNumber, tsText } from './core/ts-literal.js';
|
|
39
|
-
import { ownEntry } from './core/own-entry.js';
|
|
40
|
-
import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexSlug, parseContextIndexType, } from './core/context-index.js';
|
|
41
|
-
import { derivedContextSource, derivedEscalationSource, derivedResourceNodeId, derivedToolSource, } from './core/agent-resource-identity.js';
|
|
38
|
+
import { tsKey as key, tsLiteral, tsNumber, tsObject as objLit, tsOptions as opts, tsText } from './core/ts-literal.js';
|
|
42
39
|
import { wireExprBody } from './core/wire-expr.js';
|
|
43
40
|
import { hasExits } from './core/step-ports.js';
|
|
44
41
|
import { parseIxpProjectType } from './core/actions.js';
|
|
@@ -51,7 +48,6 @@ import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
|
|
|
51
48
|
import { contributionPort, forwardFanOut } from './core/definition-handles.js';
|
|
52
49
|
import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
|
|
53
50
|
import { eventFilterFromTreeLeaf } from './core/event-filters.js';
|
|
54
|
-
import { formFieldRowLoss, formFieldRowLossMessage, formFieldSettingsSource, liftFormFieldSettings, sourceFormFieldType, } from './core/form-field-type.js';
|
|
55
51
|
/**
|
|
56
52
|
* The trigger id of the flow currently being decompiled, so a reference through a
|
|
57
53
|
* RENAMED trigger is still recognized as `input('x')` rather than falling back to
|
|
@@ -195,14 +191,30 @@ function shippedVersions(type) {
|
|
|
195
191
|
* The options argument of `.branch()`, `.switch()` and `.parallel()`: the exact
|
|
196
192
|
* `version`, and a canvas `label` that differs from the node id (what compile
|
|
197
193
|
* writes when none is given). No `updates`: those constructs refuse them.
|
|
194
|
+
*
|
|
195
|
+
* `node` is the construct's own node (a `.parallel`'s join); `fork` is the
|
|
196
|
+
* merge node a designer-drawn parallel split leaves from, written as
|
|
197
|
+
* `{ fork }` (#1310). A `.parallel` whose arms rejoin on a plain step has no
|
|
198
|
+
* join node, so `node` may be absent; the fork then supplies the version.
|
|
198
199
|
*/
|
|
199
|
-
function forkOptsArg(node) {
|
|
200
|
+
function forkOptsArg(node, fork) {
|
|
200
201
|
const parts = [];
|
|
201
|
-
const ver = verClause(node);
|
|
202
|
+
const ver = node ? verClause(node) : fork ? verClause(fork) : '';
|
|
202
203
|
if (ver)
|
|
203
204
|
parts.push(ver);
|
|
204
|
-
if (hasRenamedLabel(node, node.id))
|
|
205
|
+
if (node && hasRenamedLabel(node, node.id))
|
|
205
206
|
parts.push(`label: ${tsLiteral(node.display.label)}`);
|
|
207
|
+
if (fork) {
|
|
208
|
+
// The fork's own version, when the join's clause does not already say it
|
|
209
|
+
// (no join node, or a different version on each).
|
|
210
|
+
const forkVer = node && node.typeVersion !== fork.typeVersion ? verClause(fork) : '';
|
|
211
|
+
const forkParts = [
|
|
212
|
+
`name: ${tsLiteral(fork.id)}`,
|
|
213
|
+
...(hasRenamedLabel(fork, fork.id) ? [`label: ${tsLiteral(fork.display.label)}`] : []),
|
|
214
|
+
...(forkVer ? [forkVer] : []),
|
|
215
|
+
];
|
|
216
|
+
parts.push(forkParts.length === 1 ? `fork: ${tsLiteral(fork.id)}` : `fork: { ${forkParts.join(', ')} }`);
|
|
217
|
+
}
|
|
206
218
|
return parts.length ? `, { ${parts.join(', ')} }` : '';
|
|
207
219
|
}
|
|
208
220
|
/**
|
|
@@ -263,12 +275,7 @@ const T = {
|
|
|
263
275
|
triggerForm: 'core.trigger.form',
|
|
264
276
|
triggerConversation: 'core.trigger.conversation',
|
|
265
277
|
hitl: 'uipath.human-in-the-loop',
|
|
266
|
-
inlineAgent: 'uipath.agent.autonomous',
|
|
267
|
-
conversationalAgent: 'uipath.agent.conversational',
|
|
268
|
-
voiceAgent: 'uipath.agent.voice',
|
|
269
278
|
triggerVoice: 'core.trigger.voice',
|
|
270
|
-
createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
|
|
271
|
-
endCall: 'uipath.conversational.voice.end-call',
|
|
272
279
|
decision: 'core.logic.decision',
|
|
273
280
|
switch: 'core.logic.switch',
|
|
274
281
|
merge: 'core.logic.merge',
|
|
@@ -287,28 +294,6 @@ function isTrigger(type) {
|
|
|
287
294
|
function boolLit(v) {
|
|
288
295
|
return v ? 'true' : 'false';
|
|
289
296
|
}
|
|
290
|
-
/** An object key: a bare identifier when legal, otherwise a quoted string. */
|
|
291
|
-
function key(k) {
|
|
292
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : tsLiteral(k);
|
|
293
|
-
}
|
|
294
|
-
/** Render an options object from present entries; '' when nothing is present. */
|
|
295
|
-
function opts(entries) {
|
|
296
|
-
const parts = entries.filter(([, v]) => v !== undefined).map(([k, v]) => `${key(k)}: ${v}`);
|
|
297
|
-
return parts.length ? `{ ${parts.join(', ')} }` : '';
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Plain JSON data as source, in the `{ k: v }` / `[a, b]` spelling the
|
|
301
|
-
* printers use (a form field's `options`, `rules`, `defaultValue`).
|
|
302
|
-
*/
|
|
303
|
-
function dataSource(v) {
|
|
304
|
-
if (Array.isArray(v))
|
|
305
|
-
return `[${v.map(dataSource).join(', ')}]`;
|
|
306
|
-
if (v !== null && typeof v === 'object') {
|
|
307
|
-
const parts = Object.entries(v).filter(([, x]) => x !== undefined).map(([k, x]) => `${key(k)}: ${dataSource(x)}`);
|
|
308
|
-
return parts.length ? `{ ${parts.join(', ')} }` : '{}';
|
|
309
|
-
}
|
|
310
|
-
return tsLiteral(v);
|
|
311
|
-
}
|
|
312
297
|
/** A collector for indented source lines. */
|
|
313
298
|
class Emit {
|
|
314
299
|
indentUnit;
|
|
@@ -353,6 +338,19 @@ function unwrapSource(v) {
|
|
|
353
338
|
}
|
|
354
339
|
return undefined;
|
|
355
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* The JS body of a condition slot (a decision's `expression`, a switch case's
|
|
343
|
+
* `expression`), whichever spelling the file uses: a bare JS string (the classic
|
|
344
|
+
* CLI), a `{ type: 'jsExpression', expression }` envelope (what Studio Web
|
|
345
|
+
* writes), or a `=js:`-prefixed string, with or without a space after the colon
|
|
346
|
+
* (`=js: ($vars.x ?? 0) > 0`). Before this, the envelope was stringified to
|
|
347
|
+
* `[object Object]` and the prefixed spelling was kept inside the template, so
|
|
348
|
+
* every designer-saved decision came back as source compile refused (#1308).
|
|
349
|
+
*/
|
|
350
|
+
function conditionText(v) {
|
|
351
|
+
const raw = unwrapSource(v);
|
|
352
|
+
return raw === undefined ? String(v ?? '') : wireExprBody(raw).trim();
|
|
353
|
+
}
|
|
356
354
|
/** A plain (boolean/number) input value, unwrapping a literal EV envelope if present. */
|
|
357
355
|
function unwrapPlain(v) {
|
|
358
356
|
if (v && typeof v === 'object' && 'expression' in v && v.type === 'literal') {
|
|
@@ -445,12 +443,6 @@ function emitVal(v, imp, inputNames) {
|
|
|
445
443
|
function emitBareExpr(raw, imp, inputNames) {
|
|
446
444
|
return emitRef(raw, imp, inputNames);
|
|
447
445
|
}
|
|
448
|
-
/** Render an object literal `{ k: <src>, … }` from pre-rendered value sources; '' when empty. */
|
|
449
|
-
function objLit(entries) {
|
|
450
|
-
if (entries.length === 0)
|
|
451
|
-
return '';
|
|
452
|
-
return `{ ${entries.map(([k, v]) => `${key(k)}: ${v}`).join(', ')} }`;
|
|
453
|
-
}
|
|
454
446
|
/** A map of `{name: value}` inputs (headers / query / connector fields) → object-literal source. */
|
|
455
447
|
function emitValMap(m, imp, inputNames) {
|
|
456
448
|
if (!m || typeof m !== 'object')
|
|
@@ -652,6 +644,26 @@ function liftRegistered(node, impl = registeredImpl(node), graph) {
|
|
|
652
644
|
throw new Error(`decompile: ${message}`);
|
|
653
645
|
warnOnce(message);
|
|
654
646
|
},
|
|
647
|
+
sidecar: (path) => runReadSidecar?.(path),
|
|
648
|
+
flowId: runFlowId,
|
|
649
|
+
definitionOf: (n) => runDefs.get(`${n.type}@${n.typeVersion}`) ?? runDefs.get(n.type),
|
|
650
|
+
attached: (port, limit = Infinity) => {
|
|
651
|
+
const found = [];
|
|
652
|
+
for (const e of graph?.outEdges(node.id) ?? []) {
|
|
653
|
+
if (e.sourcePort !== port || found.length >= limit)
|
|
654
|
+
continue;
|
|
655
|
+
const n = graph.byId.get(e.targetNodeId);
|
|
656
|
+
if (n === undefined)
|
|
657
|
+
continue;
|
|
658
|
+
graph.accounted.add(e);
|
|
659
|
+
found.push(n);
|
|
660
|
+
}
|
|
661
|
+
return found;
|
|
662
|
+
},
|
|
663
|
+
attachedIds: (port) => (graph?.outEdges(node.id) ?? [])
|
|
664
|
+
.filter((e) => e.sourcePort === port && graph.byId.has(e.targetNodeId))
|
|
665
|
+
.map((e) => e.targetNodeId),
|
|
666
|
+
absorb: (id, compiledId) => { runAbsorbed.set(id, compiledId); },
|
|
655
667
|
outPorts: () => (graph?.outEdges(node.id) ?? []).map((e) => e.sourcePort),
|
|
656
668
|
convergedFanOut: () => graph?.convergedFanOut(node.id) === true,
|
|
657
669
|
};
|
|
@@ -713,35 +725,12 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
713
725
|
if (lifted !== undefined)
|
|
714
726
|
return lifted;
|
|
715
727
|
switch (node.type) {
|
|
716
|
-
case T.createOutgoingCall:
|
|
717
|
-
case T.endCall: {
|
|
718
|
-
const i = (node.inputs ?? {});
|
|
719
|
-
const v = (x) => {
|
|
720
|
-
const raw = typeof x === 'string' ? x : String(x?.expression ?? x ?? '');
|
|
721
|
-
return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames)
|
|
722
|
-
: raw.startsWith('$vars.') ? emitBareExpr(raw, imp, inputNames)
|
|
723
|
-
: tsLiteral(raw);
|
|
724
|
-
};
|
|
725
|
-
if (node.type === T.createOutgoingCall) {
|
|
726
|
-
return `${imp.use('createOutgoingCall')}(${opts([['from', v(i.from)], ['to', v(i.to)]])})`;
|
|
727
|
-
}
|
|
728
|
-
return `${imp.use('endCall')}(${opts([['callContext', v(i.callContext)]])})`;
|
|
729
|
-
}
|
|
730
|
-
case T.inlineAgent:
|
|
731
|
-
case T.conversationalAgent:
|
|
732
|
-
case T.voiceAgent:
|
|
733
|
-
return emitAgentCluster(node, imp, inputNames, graph);
|
|
734
728
|
default:
|
|
735
729
|
// A connector action node: `uipath.connector.<key>.<action>` (but NOT the
|
|
736
730
|
// event nodes `uipath.connector.event.*` / `.trigger.*`, which are triggers).
|
|
737
731
|
if (node.type.startsWith('uipath.connector.') && !node.type.startsWith('uipath.connector.event.') && !node.type.startsWith('uipath.connector.trigger.')) {
|
|
738
732
|
return emitConnector(node, imp, inputNames, o);
|
|
739
733
|
}
|
|
740
|
-
{
|
|
741
|
-
const published = emitPublishedResource(node, imp, inputNames);
|
|
742
|
-
if (published !== undefined)
|
|
743
|
-
return published;
|
|
744
|
-
}
|
|
745
734
|
// A mid-flow connector EVENT wait (`uipath.connector.event.<key>.<event>`
|
|
746
735
|
// placed as a step rather than as the start node).
|
|
747
736
|
if (node.type.startsWith('uipath.connector.event.')) {
|
|
@@ -837,688 +826,6 @@ function connectorObjectName(configuration) {
|
|
|
837
826
|
* Returns `undefined` when the node is not one of them, so the caller can fall
|
|
838
827
|
* through to the next arm.
|
|
839
828
|
*/
|
|
840
|
-
/** The `maxIterations` Studio Web writes into every inline agent's `agent.json` (flow-workbench `getDefaultAgentContent()`). */
|
|
841
|
-
const STUDIO_WEB_DEFAULT_MAX_ITERATIONS = 25;
|
|
842
|
-
/**
|
|
843
|
-
* The agent configuration a SHELL agent node keeps in its sidecar, read from
|
|
844
|
-
* `<source>/agent.json` in the node's own input names: the prompts, the model,
|
|
845
|
-
* the run settings (`temperature`, `maxTokenPerResponse`, `maxIterations`,
|
|
846
|
-
* `mode`), the `guardrails` and (voice) the `voice` settings. Empty when the
|
|
847
|
-
* node carries prompts itself (an embedded node, e.g. a `.flow` saved by Studio
|
|
848
|
-
* Web with the self-contained flag on), or when no sidecar can be read.
|
|
849
|
-
*
|
|
850
|
-
* A run setting equal to the default a sidecar writer fills in (`temperature`
|
|
851
|
-
* 0, `maxTokens` 16384, `mode` `'standard'`) is not recovered: recompiling
|
|
852
|
-
* without it writes the same sidecar, and recovering it would add an argument
|
|
853
|
-
* the author never wrote to every agent. `mode: 'standard'` is the exception on
|
|
854
|
-
* a 1.3 node, where naming it is what selected that definition. The same goes
|
|
855
|
-
* for the inline agent's `maxIterations` 25: the SDK omits it when unset, but
|
|
856
|
-
* Studio Web always writes it (its `getDefaultAgentContent()` default), so
|
|
857
|
-
* every Studio Web-saved agent would otherwise decompile with `maxIterations: 25`.
|
|
858
|
-
*/
|
|
859
|
-
function sidecarAgentInputs(node) {
|
|
860
|
-
const inputs = (node.inputs ?? {});
|
|
861
|
-
if (typeof inputs.systemPrompt === 'string' || typeof inputs.userPrompt === 'string')
|
|
862
|
-
return {};
|
|
863
|
-
const source = unwrapSource(inputs.source);
|
|
864
|
-
if (!source)
|
|
865
|
-
return {};
|
|
866
|
-
const path = `${source}/agent.json`;
|
|
867
|
-
const raw = runReadSidecar?.(path);
|
|
868
|
-
if (raw === undefined) {
|
|
869
|
-
warnOnce(`agent step "${node.id}" carries no prompts and ${path} was not found beside the .flow, `
|
|
870
|
-
+ 'so its prompts, model and run settings decompile empty. Keep <source>/agent.json beside the .flow '
|
|
871
|
-
+ '(download the whole project, not just the .flow).');
|
|
872
|
-
return {};
|
|
873
|
-
}
|
|
874
|
-
let agent;
|
|
875
|
-
try {
|
|
876
|
-
agent = JSON.parse(raw);
|
|
877
|
-
}
|
|
878
|
-
catch {
|
|
879
|
-
warnOnce(`agent step "${node.id}": ${path} is not valid JSON, so its prompts, model and run settings decompile empty.`);
|
|
880
|
-
return {};
|
|
881
|
-
}
|
|
882
|
-
const messages = (Array.isArray(agent?.messages) ? agent.messages : []);
|
|
883
|
-
const content = (role) => {
|
|
884
|
-
const c = messages.find((m) => m.role === role)?.content;
|
|
885
|
-
return typeof c === 'string' ? c : undefined;
|
|
886
|
-
};
|
|
887
|
-
const settings = (agent?.settings ?? {});
|
|
888
|
-
const system = content('system');
|
|
889
|
-
const user = content('user');
|
|
890
|
-
const isVoice = node.type === T.voiceAgent;
|
|
891
|
-
const numberUnless = (v, dflt) => typeof v === 'number' && v !== dflt ? v : undefined;
|
|
892
|
-
// The voice agent's temperature and token cap live in `settings.voice`, not here.
|
|
893
|
-
const temperature = isVoice ? undefined : numberUnless(settings.temperature, 0);
|
|
894
|
-
const maxTokenPerResponse = isVoice ? undefined : numberUnless(settings.maxTokens, 16384);
|
|
895
|
-
// Studio Web's default for the inline agent; the other families keep any value.
|
|
896
|
-
const maxIterations = node.type === T.inlineAgent
|
|
897
|
-
? numberUnless(settings.maxIterations, STUDIO_WEB_DEFAULT_MAX_ITERATIONS)
|
|
898
|
-
: typeof settings.maxIterations === 'number' ? settings.maxIterations : undefined;
|
|
899
|
-
// `mode` is a harness choice on the inline agent only; the conversational and
|
|
900
|
-
// voice sidecars always carry their family's fixed value. `'standard'` is the
|
|
901
|
-
// sidecar's default, so only another value is the author's. (Naming it no
|
|
902
|
-
// longer selects a definition version — the node's `{ version }` carries that,
|
|
903
|
-
// flow-builder-sdk#956.)
|
|
904
|
-
const mode = node.type === T.inlineAgent && typeof settings.mode === 'string'
|
|
905
|
-
&& settings.mode !== 'standard'
|
|
906
|
-
? settings.mode : undefined;
|
|
907
|
-
return {
|
|
908
|
-
...(system === undefined ? {} : { systemPrompt: system }),
|
|
909
|
-
...(user === undefined ? {} : { userPrompt: user }),
|
|
910
|
-
...(typeof settings.model === 'string' ? { model: settings.model } : {}),
|
|
911
|
-
...(temperature === undefined ? {} : { temperature }),
|
|
912
|
-
...(maxTokenPerResponse === undefined ? {} : { maxTokenPerResponse }),
|
|
913
|
-
...(maxIterations === undefined ? {} : { maxIterations }),
|
|
914
|
-
...(mode === undefined ? {} : { mode }),
|
|
915
|
-
...(Array.isArray(agent?.guardrails) && agent.guardrails.length > 0 ? { guardrails: agent.guardrails } : {}),
|
|
916
|
-
...(isVoice && settings.voice && typeof settings.voice === 'object' ? { voice: settings.voice } : {}),
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Where a context-index resource node keeps its identity, read in the order
|
|
921
|
-
* that holds for every writer.
|
|
922
|
-
*
|
|
923
|
-
* SDK compile writes `indexId` / `indexName` / `folderPath` onto the node. The
|
|
924
|
-
* canvas and the CLI do not: their node carries only `inputs.source`, and the
|
|
925
|
-
* identity lives in two other places.
|
|
926
|
-
*
|
|
927
|
-
* - The node TYPE, `…context.index.<slug>.<uuid>`: the uuid is the index id
|
|
928
|
-
* (the `ContextIndexRef.id` JSDoc says to read it there).
|
|
929
|
-
* - The resource sidecar `<agent source>/resources/<inputs.source>/resource.json`,
|
|
930
|
-
* whose `name` is the index's real name (the one the slug is made from) and
|
|
931
|
-
* whose `folderPath` is its folder. `display.label` is only what the canvas
|
|
932
|
-
* shows, and a canvas rename changes it.
|
|
933
|
-
*
|
|
934
|
-
* So the id comes from the type (then `inputs.indexId`). The name is the first
|
|
935
|
-
* of `inputs.indexName`, sidecar `name`, sidecar `indexName`, label that slugs to
|
|
936
|
-
* the type's slug, else the first one present in that order. Compile derives
|
|
937
|
-
* the type from `slug(name)` + id, so when the chosen name does not slug back to
|
|
938
|
-
* the type's slug the recompiled node gets a different type, and that is warned.
|
|
939
|
-
*/
|
|
940
|
-
function contextIndexIdentity(agent, c, rPlain) {
|
|
941
|
-
const fromType = parseContextIndexType(c.type);
|
|
942
|
-
const sidecar = contextResourceSidecar(agent, c);
|
|
943
|
-
const sidecarText = (k) => typeof sidecar?.[k] === 'string' && sidecar[k] !== '' ? sidecar[k] : undefined;
|
|
944
|
-
const label = typeof c.display?.label === 'string' && c.display.label !== '' ? c.display.label : undefined;
|
|
945
|
-
// The canvas can set both sidecar `name` and `indexName`, and they can differ.
|
|
946
|
-
// The one that slugs to the type's slug is the one the type was built from, so
|
|
947
|
-
// it wins; the fixed order is the fallback when none matches.
|
|
948
|
-
const candidates = [rPlain(c, 'indexName'), sidecarText('name'), sidecarText('indexName'), label]
|
|
949
|
-
.filter((n) => n !== undefined);
|
|
950
|
-
const name = (fromType?.slug ? candidates.find((n) => contextIndexSlug(n) === fromType.slug) : undefined)
|
|
951
|
-
?? candidates[0] ?? '';
|
|
952
|
-
const id = (fromType?.id || undefined) ?? rPlain(c, 'indexId') ?? '';
|
|
953
|
-
if (fromType !== undefined && fromType.slug !== '' && contextIndexSlug(name) !== fromType.slug) {
|
|
954
|
-
warnOnce(`context index "${c.id}" on agent "${agent.id}": its node type names the index "${fromType.slug}", `
|
|
955
|
-
+ `but the name decompile recovered, ${JSON.stringify(name)}, slugs to "${contextIndexSlug(name)}", so compile `
|
|
956
|
-
+ `will write a different node type. Set \`name\` to the index's real name`
|
|
957
|
-
+ (sidecar === undefined ? ', or keep the resources/ folder beside the .flow so decompile can read it.' : '.'));
|
|
958
|
-
}
|
|
959
|
-
return { name, id, folderPath: rPlain(c, 'folderPath') ?? sidecarText('folderPath'), sidecar };
|
|
960
|
-
}
|
|
961
|
-
/** The retrieval modes `ContextIndexRef.retrievalMode` can hold. */
|
|
962
|
-
const CONTEXT_RETRIEVAL_MODES = new Set(['semantic', 'structured', 'deeprag', 'batchtransform']);
|
|
963
|
-
/** The `settings` keys of a context `resource.json` that {@link contextIndexSettings} reads or checks. */
|
|
964
|
-
const CONTEXT_SETTINGS_KEYS = new Set([
|
|
965
|
-
'retrievalMode', 'query', 'folderPathPrefix', 'threshold', 'resultCount', 'fileExtension',
|
|
966
|
-
'citationMode', 'webSearchGrounding', 'outputColumns',
|
|
967
|
-
]);
|
|
968
|
-
/**
|
|
969
|
-
* A context index's retrieval settings, as the `context: [...]` entry's option
|
|
970
|
-
* rows (`retrievalMode`, `query`, `threshold`, `resultCount`, `fileExtension`),
|
|
971
|
-
* each left out when it is what compile writes anyway.
|
|
972
|
-
*
|
|
973
|
-
* The inverse of `contextResourceJson` (serialize.ts). Read from the sidecar's
|
|
974
|
-
* `settings` first: a node the canvas or the CLI placed keeps its inputs minimal
|
|
975
|
-
* (`inputs.source`), so `resource.json` is the only copy, and it is what the
|
|
976
|
-
* runtime reads. The node's own inputs (the SDK writes both) answer when there
|
|
977
|
-
* is no sidecar. Before #1183 neither was read, so a no-edit round trip reset
|
|
978
|
-
* every setting to the SDK default without a word.
|
|
979
|
-
*
|
|
980
|
-
* The query is variant-tagged in the sidecar and mode-tagged on the node:
|
|
981
|
-
*
|
|
982
|
-
* - `dynamic` / `prompt` → a plain string (the guidance the model reads);
|
|
983
|
-
* - `static` / `text-builder` whose text is a `=js:` expression → that
|
|
984
|
-
* expression, which compile writes back as `text-builder` / `static`;
|
|
985
|
-
* - `static` with `''` is the canvas's spelling of "no query", as the SDK's
|
|
986
|
-
* `dynamic` with `''` is: left out.
|
|
987
|
-
*
|
|
988
|
-
* A setting the SDK cannot express (a `static` query of plain text, an
|
|
989
|
-
* `argument` query, deeprag citations off, batchtransform web grounding off or
|
|
990
|
-
* output columns, a folder-path prefix, an unknown mode or key) is reported:
|
|
991
|
-
* a warning, or an error under `--strict`. Compile would write the SDK's
|
|
992
|
-
* value instead, so it is never dropped silently.
|
|
993
|
-
*/
|
|
994
|
-
function contextIndexSettings(agent, c, sidecar) {
|
|
995
|
-
const settings = sidecar?.settings && typeof sidecar.settings === 'object' && !Array.isArray(sidecar.settings)
|
|
996
|
-
? sidecar.settings : undefined;
|
|
997
|
-
const ni = (c.inputs ?? {});
|
|
998
|
-
const where = `context index "${c.id}" on agent "${agent.id}"`;
|
|
999
|
-
const lossy = (what) => {
|
|
1000
|
-
const message = `${where}: ${what}, which \`context: [...]\` cannot express, so compile writes the SDK's `
|
|
1001
|
-
+ `value instead. Edit that setting in the canvas after merging, or keep the original resource.json.`;
|
|
1002
|
-
if (runStrict)
|
|
1003
|
-
throw new Error(`decompile: ${message}`);
|
|
1004
|
-
warnOnce(message);
|
|
1005
|
-
};
|
|
1006
|
-
const textOf = (v) => (typeof v === 'string' ? v : undefined);
|
|
1007
|
-
const numOf = (v) => {
|
|
1008
|
-
const raw = unwrapPlain(v);
|
|
1009
|
-
return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;
|
|
1010
|
-
};
|
|
1011
|
-
const valueOf = (v) => (v && typeof v === 'object' ? v.value : undefined);
|
|
1012
|
-
// Retrieval mode.
|
|
1013
|
-
const rawMode = textOf(settings?.retrievalMode) ?? textOf(unwrapSource(ni.retrievalMode));
|
|
1014
|
-
let mode = CONTEXT_INDEX_DEFAULTS.retrievalMode;
|
|
1015
|
-
if (rawMode !== undefined && rawMode !== '') {
|
|
1016
|
-
if (CONTEXT_RETRIEVAL_MODES.has(rawMode))
|
|
1017
|
-
mode = rawMode;
|
|
1018
|
-
else
|
|
1019
|
-
lossy(`its retrieval mode is ${JSON.stringify(rawMode)}`);
|
|
1020
|
-
}
|
|
1021
|
-
const sidecarQuery = (() => {
|
|
1022
|
-
const q = settings?.query;
|
|
1023
|
-
if (!q || typeof q !== 'object')
|
|
1024
|
-
return undefined;
|
|
1025
|
-
if (q.variant === 'dynamic')
|
|
1026
|
-
return { kind: 'prompt', text: textOf(q.description) ?? '' };
|
|
1027
|
-
if (q.variant === 'static')
|
|
1028
|
-
return { kind: 'text', text: textOf(q.value) ?? '' };
|
|
1029
|
-
if (q.variant === 'argument')
|
|
1030
|
-
return { kind: 'argument', text: textOf(q.value) ?? '' };
|
|
1031
|
-
lossy(`its query has variant ${JSON.stringify(q.variant)}`);
|
|
1032
|
-
return undefined;
|
|
1033
|
-
})();
|
|
1034
|
-
const nodeQuery = (() => {
|
|
1035
|
-
const q = ni.query;
|
|
1036
|
-
if (typeof q === 'string')
|
|
1037
|
-
return q === '' ? undefined : { kind: q.startsWith('=js:') ? 'text' : 'prompt', text: q };
|
|
1038
|
-
if (!q || typeof q !== 'object')
|
|
1039
|
-
return undefined;
|
|
1040
|
-
const t = q;
|
|
1041
|
-
if (t.mode === 'text-builder')
|
|
1042
|
-
return { kind: 'text', text: textOf(t.textValue) ?? '' };
|
|
1043
|
-
if (t.mode === 'variable')
|
|
1044
|
-
return { kind: 'argument', text: textOf(t.argumentPath) ?? '' };
|
|
1045
|
-
return { kind: 'prompt', text: textOf(t.promptValue) ?? '' };
|
|
1046
|
-
})();
|
|
1047
|
-
const query = sidecarQuery ?? nodeQuery;
|
|
1048
|
-
let querySrc;
|
|
1049
|
-
if (query?.kind === 'prompt' && query.text !== '')
|
|
1050
|
-
querySrc = tsLiteral(query.text);
|
|
1051
|
-
else if (query?.kind === 'text' && query.text.startsWith('=js:'))
|
|
1052
|
-
querySrc = tsLiteral(query.text);
|
|
1053
|
-
else if (query?.kind === 'text' && query.text !== '')
|
|
1054
|
-
lossy(`its query is static text (${JSON.stringify(query.text)})`);
|
|
1055
|
-
else if (query?.kind === 'argument')
|
|
1056
|
-
lossy(`its query is the agent argument ${JSON.stringify(query.text)}`);
|
|
1057
|
-
// Numbers and the file extension.
|
|
1058
|
-
const threshold = numOf(settings?.threshold) ?? numOf(ni.threshold);
|
|
1059
|
-
const resultCount = numOf(settings?.resultCount) ?? numOf(ni.resultCount);
|
|
1060
|
-
const fileExtension = textOf(valueOf(settings?.fileExtension)) ?? textOf(unwrapSource(ni.fileExtension));
|
|
1061
|
-
// The node's flat `All` is the platform's node default for every mode; only
|
|
1062
|
-
// the resource's per-mode default is what compile would write anyway.
|
|
1063
|
-
const defaultExtension = ownEntry(CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, mode) ?? 'All';
|
|
1064
|
-
const extensionSrc = fileExtension === undefined || fileExtension === '' || fileExtension === defaultExtension
|
|
1065
|
-
|| (settings?.fileExtension === undefined && fileExtension === 'All')
|
|
1066
|
-
? undefined : tsLiteral(fileExtension);
|
|
1067
|
-
// What compile writes as constants; anything else has no field.
|
|
1068
|
-
if (mode === 'deeprag') {
|
|
1069
|
-
const citation = textOf(valueOf(settings?.citationMode)) ?? textOf(unwrapSource(ni.citations));
|
|
1070
|
-
if (citation !== undefined && !['Inline', 'enabled'].includes(citation))
|
|
1071
|
-
lossy(`its citation mode is ${JSON.stringify(citation)}`);
|
|
1072
|
-
}
|
|
1073
|
-
if (mode === 'batchtransform') {
|
|
1074
|
-
const grounding = textOf(valueOf(settings?.webSearchGrounding)) ?? textOf(valueOf(ni.webSearchGrounding));
|
|
1075
|
-
if (grounding !== undefined && !['Enabled', 'enabled'].includes(grounding))
|
|
1076
|
-
lossy(`its web search grounding is ${JSON.stringify(grounding)}`);
|
|
1077
|
-
const columns = settings?.outputColumns ?? ni.outputColumns;
|
|
1078
|
-
if (Array.isArray(columns) && columns.length > 0)
|
|
1079
|
-
lossy(`it has ${columns.length} output column(s)`);
|
|
1080
|
-
}
|
|
1081
|
-
// The folder-path prefix: variant-tagged in the sidecar, mode-tagged on the node.
|
|
1082
|
-
const field = (v, k) => (v && typeof v === 'object' ? textOf(v[k]) : undefined);
|
|
1083
|
-
const prefixText = settings?.folderPathPrefix !== undefined
|
|
1084
|
-
? field(settings.folderPathPrefix, 'value') ?? field(settings.folderPathPrefix, 'description') ?? ''
|
|
1085
|
-
: field(ni.folderPathPrefix, 'textValue') ?? field(ni.folderPathPrefix, 'promptValue') ?? '';
|
|
1086
|
-
if (prefixText !== '')
|
|
1087
|
-
lossy(`its folder-path prefix is ${JSON.stringify(prefixText)}`);
|
|
1088
|
-
for (const k of Object.keys(settings ?? {})) {
|
|
1089
|
-
if (!CONTEXT_SETTINGS_KEYS.has(k))
|
|
1090
|
-
lossy(`its resource.json settings carry ${JSON.stringify(k)}`);
|
|
1091
|
-
}
|
|
1092
|
-
return [
|
|
1093
|
-
['query', querySrc],
|
|
1094
|
-
['retrievalMode', mode !== CONTEXT_INDEX_DEFAULTS.retrievalMode ? tsLiteral(mode) : undefined],
|
|
1095
|
-
['threshold', threshold !== undefined && threshold !== CONTEXT_INDEX_DEFAULTS.threshold ? tsNumber(threshold) : undefined],
|
|
1096
|
-
['resultCount', resultCount !== undefined && resultCount !== CONTEXT_INDEX_DEFAULTS.resultCount ? tsNumber(resultCount) : undefined],
|
|
1097
|
-
['fileExtension', extensionSrc],
|
|
1098
|
-
];
|
|
1099
|
-
}
|
|
1100
|
-
/**
|
|
1101
|
-
* A context resource's `resource.json` sidecar, parsed; `undefined` when it is
|
|
1102
|
-
* not beside the `.flow`, or (with a warning) when it is not a JSON object.
|
|
1103
|
-
*/
|
|
1104
|
-
function contextResourceSidecar(agent, c) {
|
|
1105
|
-
const agentSource = unwrapSource(agent.inputs?.source);
|
|
1106
|
-
const resourceSource = unwrapSource(c.inputs?.source);
|
|
1107
|
-
if (!agentSource || !resourceSource)
|
|
1108
|
-
return undefined;
|
|
1109
|
-
const path = `${agentSource}/resources/${resourceSource}/resource.json`;
|
|
1110
|
-
const raw = runReadSidecar?.(path);
|
|
1111
|
-
if (raw === undefined)
|
|
1112
|
-
return undefined;
|
|
1113
|
-
try {
|
|
1114
|
-
const json = JSON.parse(raw);
|
|
1115
|
-
if (json && typeof json === 'object' && !Array.isArray(json))
|
|
1116
|
-
return json;
|
|
1117
|
-
}
|
|
1118
|
-
catch {
|
|
1119
|
-
// Reported below with the non-object case.
|
|
1120
|
-
}
|
|
1121
|
-
warnOnce(`context index "${c.id}": ${path} is not valid JSON, so its name and folder decompile from the node.`);
|
|
1122
|
-
return undefined;
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* An AGENT and its attached resources — the inline autonomous agent, the
|
|
1126
|
-
* conversational agent, and the voice agent.
|
|
1127
|
-
*
|
|
1128
|
-
* The cluster is a node plus artifact nodes on its `tool` / `context` /
|
|
1129
|
-
* `escalation` / `memory` handles, so this walks those edges and recovers each
|
|
1130
|
-
* resource as an argument rather than as a step (see {@link ARTIFACT_PORTS}).
|
|
1131
|
-
*
|
|
1132
|
-
* **The author's input NAMES are not recoverable, and that is by design.** The
|
|
1133
|
-
* platform does not send an agent the names you declared: it keys each argument
|
|
1134
|
-
* by the reference PATH, `__`-joined (`$vars.prep.output` → `prep__output`), and
|
|
1135
|
-
* only those flat names survive in the emitted file. Recovery therefore uses
|
|
1136
|
-
* them for both the `inputs` keys and the `{{input.…}}` placeholders — the same
|
|
1137
|
-
* arguments reach the model, spelled the platform's way. (Node ids are
|
|
1138
|
-
* preserved exactly; only these logical names change.)
|
|
1139
|
-
*/
|
|
1140
|
-
function emitAgentCluster(node, imp, inputNames, graph) {
|
|
1141
|
-
// A key on the node wins over the sidecar's copy of it.
|
|
1142
|
-
const i = { ...sidecarAgentInputs(node), ...(node.inputs ?? {}) };
|
|
1143
|
-
const plain = (v) => {
|
|
1144
|
-
const raw = unwrapSource(v);
|
|
1145
|
-
return raw === undefined || raw === '' ? undefined : raw;
|
|
1146
|
-
};
|
|
1147
|
-
const num = (v) => {
|
|
1148
|
-
const raw = unwrapPlain(v);
|
|
1149
|
-
return typeof raw === 'number' ? String(raw) : undefined;
|
|
1150
|
-
};
|
|
1151
|
-
// Prompts. A sidecar prompt already speaks `{{input.<flatName>}}`, so this
|
|
1152
|
-
// leaves it alone. An EMBEDDED node's prompt (Studio Web with the
|
|
1153
|
-
// self-contained flag on, or an SDK before #927) carries each input as its
|
|
1154
|
-
// rendered binding instead; the bindings in `agentInputVariables` are exactly
|
|
1155
|
-
// what to put back — longest first, so a shorter binding cannot eat a longer
|
|
1156
|
-
// one's prefix.
|
|
1157
|
-
const vars = (Array.isArray(i.agentInputVariables) ? i.agentInputVariables : []);
|
|
1158
|
-
const refs = vars
|
|
1159
|
-
.filter((v) => typeof v.id === 'string' && typeof v.binding === 'string')
|
|
1160
|
-
.map((v) => ({ flatName: v.id, binding: v.binding }))
|
|
1161
|
-
.sort((a, b) => b.binding.length - a.binding.length);
|
|
1162
|
-
const unrender = (text) => {
|
|
1163
|
-
let out = text;
|
|
1164
|
-
for (const r of refs)
|
|
1165
|
-
out = out.split(r.binding).join(`{{input.${r.flatName}}}`);
|
|
1166
|
-
return out;
|
|
1167
|
-
};
|
|
1168
|
-
const promptSrc = (v) => {
|
|
1169
|
-
const raw = plain(v);
|
|
1170
|
-
return raw === undefined ? undefined : tsLiteral(unrender(raw));
|
|
1171
|
-
};
|
|
1172
|
-
const inputRows = refs
|
|
1173
|
-
// Emit in the file's own order, not the length-sorted one.
|
|
1174
|
-
.slice()
|
|
1175
|
-
.sort((a, b) => vars.findIndex((v) => v.id === a.flatName) - vars.findIndex((v) => v.id === b.flatName))
|
|
1176
|
-
.map((r) => {
|
|
1177
|
-
return [r.flatName, emitBareExpr(wireExprBody(r.binding), imp, inputNames)];
|
|
1178
|
-
});
|
|
1179
|
-
const outVars = (Array.isArray(i.agentOutputVariables) ? i.agentOutputVariables : []);
|
|
1180
|
-
const returns = outVars.length
|
|
1181
|
-
? objLit(outVars.map((v) => [String(v.id ?? ''), tsLiteral(String(v.type ?? 'string'))]))
|
|
1182
|
-
: undefined;
|
|
1183
|
-
// The resources, from the agent's own artifact edges. Each edge read here is
|
|
1184
|
-
// carried by the agent spec (edge accounting, #1125); `limit` is for a port
|
|
1185
|
-
// the spec carries only one resource on.
|
|
1186
|
-
const resource = (port, limit = Infinity) => {
|
|
1187
|
-
const found = [];
|
|
1188
|
-
for (const e of graph?.outEdges(node.id) ?? []) {
|
|
1189
|
-
if (e.sourcePort !== port || found.length >= limit)
|
|
1190
|
-
continue;
|
|
1191
|
-
const n = graph.byId.get(e.targetNodeId);
|
|
1192
|
-
if (n === undefined)
|
|
1193
|
-
continue;
|
|
1194
|
-
graph.accounted.add(e);
|
|
1195
|
-
found.push(n);
|
|
1196
|
-
}
|
|
1197
|
-
return found;
|
|
1198
|
-
};
|
|
1199
|
-
// Record what compile will call each folded resource node. Serialize mints
|
|
1200
|
-
// `uid(scope, \`${step.name}<Kind>\`)` per resource, in spec order, and the
|
|
1201
|
-
// spec order is this edge order: the first is `<agent><Kind>`, the n-th
|
|
1202
|
-
// `<agent><Kind><n>` (claimId's dedupe). Only the first memory is recovered,
|
|
1203
|
-
// so every further memory maps to `undefined`.
|
|
1204
|
-
//
|
|
1205
|
-
// When a port's nodes do not ALL carry those ids (the canvas or the CLI named
|
|
1206
|
-
// them), every resource on the port is written with its own `nodeId`, and
|
|
1207
|
-
// compile keeps the file's ids. All of them, not only the odd one: compile
|
|
1208
|
-
// numbers the derived ids in order, so a derived id after an explicit one
|
|
1209
|
-
// would come out one lower than the file's.
|
|
1210
|
-
// This reads the edges directly, not through `resource()`: it must see EVERY
|
|
1211
|
-
// artifact edge, including ones the spec does not carry, and it must not
|
|
1212
|
-
// account for any edge.
|
|
1213
|
-
const keepIds = new Set();
|
|
1214
|
-
for (const [port, kind, emittedCount] of [
|
|
1215
|
-
['context', 'Context', Infinity], ['tool', 'Tool', Infinity],
|
|
1216
|
-
['escalation', 'Escalation', Infinity], ['memory', 'Memory', 1],
|
|
1217
|
-
]) {
|
|
1218
|
-
const targets = (graph?.outEdges(node.id) ?? [])
|
|
1219
|
-
.filter((e) => e.sourcePort === port && graph.byId.has(e.targetNodeId))
|
|
1220
|
-
.map((e) => e.targetNodeId);
|
|
1221
|
-
const keep = port !== 'memory'
|
|
1222
|
-
&& targets.some((id, k) => id !== derivedResourceNodeId(node.id, kind, k));
|
|
1223
|
-
targets.forEach((id, k) => {
|
|
1224
|
-
if (keep)
|
|
1225
|
-
keepIds.add(id);
|
|
1226
|
-
runAbsorbed.set(id, keep ? id : k >= emittedCount ? undefined : derivedResourceNodeId(node.id, kind, k));
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* The `nodeId` / `source` entries for resource node `r`: each only when it
|
|
1231
|
-
* differs from what compile derives (`derivedSource`), so a flow compile
|
|
1232
|
-
* wrote decompiles without them.
|
|
1233
|
-
*/
|
|
1234
|
-
const placement = (r, derivedSource) => {
|
|
1235
|
-
const source = unwrapSource((r.inputs ?? {}).source);
|
|
1236
|
-
return [
|
|
1237
|
-
['nodeId', keepIds.has(r.id) ? tsLiteral(r.id) : undefined],
|
|
1238
|
-
['source', source !== undefined && source !== '' && source !== derivedSource ? tsLiteral(source) : undefined],
|
|
1239
|
-
];
|
|
1240
|
-
};
|
|
1241
|
-
/** A tool's options plus its placement; `seed` is the ref compile derives the tool's directory from. */
|
|
1242
|
-
const toolOpts = (t, seed, entries) => opts([...entries, ...placement(t, derivedToolSource(runFlowId, node.id, seed))]);
|
|
1243
|
-
const rInputs = (n) => (n.inputs ?? {});
|
|
1244
|
-
const rPlain = (n, k) => {
|
|
1245
|
-
const raw = unwrapSource(rInputs(n)[k]);
|
|
1246
|
-
return raw === undefined || raw === '' ? undefined : raw;
|
|
1247
|
-
};
|
|
1248
|
-
const toolSrc = resource('tool').map((t) => {
|
|
1249
|
-
const ti = rInputs(t);
|
|
1250
|
-
const type = t.type;
|
|
1251
|
-
if (type === 'uipath.agent.resource.tool.clientside') {
|
|
1252
|
-
const schemaTypes = (v) => {
|
|
1253
|
-
const props = v?.properties;
|
|
1254
|
-
if (!props || Object.keys(props).length === 0)
|
|
1255
|
-
return undefined;
|
|
1256
|
-
return objLit(Object.entries(props).map(([k, x]) => [k, tsLiteral(String(x?.type ?? 'string'))]));
|
|
1257
|
-
};
|
|
1258
|
-
return toolOpts(t, { kind: 'clientside', name: rPlain(t, 'name') ?? '' }, [
|
|
1259
|
-
['kind', tsLiteral('clientside')],
|
|
1260
|
-
['name', tsLiteral(rPlain(t, 'name') ?? '')],
|
|
1261
|
-
['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
|
|
1262
|
-
['inputs', schemaTypes(ti.inputSchema)],
|
|
1263
|
-
['returns', schemaTypes(ti.outputSchema)],
|
|
1264
|
-
]);
|
|
1265
|
-
}
|
|
1266
|
-
if (type === 'uipath.agent.resource.tool.builtin.httprequest') {
|
|
1267
|
-
// A field is authored only when it left prompt mode (text-builder).
|
|
1268
|
-
const fixed = (k) => {
|
|
1269
|
-
const row = ti[k];
|
|
1270
|
-
if (!row || row.mode !== 'text-builder')
|
|
1271
|
-
return undefined;
|
|
1272
|
-
const v = row.textValue;
|
|
1273
|
-
return typeof v === 'number' ? String(v) : typeof v === 'string' ? tsLiteral(v) : JSON.stringify(v);
|
|
1274
|
-
};
|
|
1275
|
-
// `name` is not recovered, so compile derives the directory from the default.
|
|
1276
|
-
return toolOpts(t, { kind: 'httpRequest' }, [
|
|
1277
|
-
['kind', tsLiteral('httpRequest')],
|
|
1278
|
-
['url', fixed('url')], ['method', fixed('method')],
|
|
1279
|
-
['headers', fixed('headers')], ['params', fixed('params')],
|
|
1280
|
-
['body', fixed('body')], ['timeout', fixed('timeout')],
|
|
1281
|
-
]);
|
|
1282
|
-
}
|
|
1283
|
-
if (type.startsWith('uipath.agent.resource.tool.mcp.')) {
|
|
1284
|
-
return toolOpts(t, { kind: 'mcp', name: '', key: rPlain(t, 'referenceKey') ?? '' }, [
|
|
1285
|
-
['kind', tsLiteral('mcp')],
|
|
1286
|
-
['name', tsLiteral(rPlain(t, 'name') ?? '')],
|
|
1287
|
-
['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
|
|
1288
|
-
// The mapper's field name for the canonical server slug.
|
|
1289
|
-
['slug', rPlain(t, 'serverUrl') !== undefined ? tsLiteral(rPlain(t, 'serverUrl')) : undefined],
|
|
1290
|
-
['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
|
|
1291
|
-
['mcpType', rPlain(t, 'mcpType') !== undefined ? tsLiteral(rPlain(t, 'mcpType')) : undefined],
|
|
1292
|
-
]);
|
|
1293
|
-
}
|
|
1294
|
-
if (type.startsWith('uipath.agent.resource.tool.a2a.')) {
|
|
1295
|
-
return toolOpts(t, { kind: 'a2a', name: '', slug: '', key: rPlain(t, 'referenceKey') ?? '' }, [
|
|
1296
|
-
['kind', tsLiteral('a2a')],
|
|
1297
|
-
['name', tsLiteral(rPlain(t, 'name') ?? '')],
|
|
1298
|
-
['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
|
|
1299
|
-
['slug', tsLiteral(rPlain(t, 'slug') ?? '')],
|
|
1300
|
-
['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
|
|
1301
|
-
]);
|
|
1302
|
-
}
|
|
1303
|
-
if (type.startsWith('uipath.agent.resource.tool.builtin.')) {
|
|
1304
|
-
const tool = type.slice('uipath.agent.resource.tool.builtin.'.length);
|
|
1305
|
-
return toolOpts(t, { kind: 'builtin', tool }, [
|
|
1306
|
-
['kind', tsLiteral('builtin')],
|
|
1307
|
-
['tool', tsLiteral(tool)],
|
|
1308
|
-
['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
|
|
1309
|
-
]);
|
|
1310
|
-
}
|
|
1311
|
-
if (type.startsWith('uipath.agent.resource.tool.ixp.')) {
|
|
1312
|
-
const tail = type.slice('uipath.agent.resource.tool.ixp.'.length);
|
|
1313
|
-
const projectId = tail.slice(tail.indexOf('.') + 1);
|
|
1314
|
-
return toolOpts(t, { kind: 'ixp', name: '', projectId }, [
|
|
1315
|
-
['kind', tsLiteral('ixp')],
|
|
1316
|
-
['projectId', tsLiteral(projectId)],
|
|
1317
|
-
['name', tsLiteral(rPlain(t, 'name') ?? '')],
|
|
1318
|
-
]);
|
|
1319
|
-
}
|
|
1320
|
-
// The per-instance resource kinds (process / api / flow / maestro / agent /
|
|
1321
|
-
// function): the key is the node type's tail, the rest is a binding pair.
|
|
1322
|
-
const PROCESS_NS = [
|
|
1323
|
-
['uipath.agent.resource.tool.processorchestration.', 'maestro'],
|
|
1324
|
-
['uipath.agent.resource.tool.process.', 'process'],
|
|
1325
|
-
['uipath.agent.resource.tool.function.', 'function'],
|
|
1326
|
-
['uipath.agent.resource.tool.api.', 'api'],
|
|
1327
|
-
['uipath.agent.resource.tool.flow.', 'flow'],
|
|
1328
|
-
['uipath.agent.resource.tool.agent.', 'agent'],
|
|
1329
|
-
];
|
|
1330
|
-
const ns = PROCESS_NS.find(([prefix]) => type.startsWith(prefix));
|
|
1331
|
-
if (ns) {
|
|
1332
|
-
const def = runDefs.get(`${type}@${t.typeVersion}`) ?? runDefs.get(type);
|
|
1333
|
-
const values = (def?.model?.bindings?.values ?? []);
|
|
1334
|
-
const bound = (n) => {
|
|
1335
|
-
const row = values.find((v) => v.name === n);
|
|
1336
|
-
return typeof row?.default === 'string' ? row.default : undefined;
|
|
1337
|
-
};
|
|
1338
|
-
const key = type.slice(ns[0].length);
|
|
1339
|
-
return toolOpts(t, { kind: 'process', name: '', folderPath: '', key }, [
|
|
1340
|
-
['kind', tsLiteral(ns[1])],
|
|
1341
|
-
['key', tsLiteral(key)],
|
|
1342
|
-
['name', tsLiteral(bound('name') ?? rPlain(t, 'name') ?? '')],
|
|
1343
|
-
['folderPath', tsLiteral(bound('folderPath') ?? '')],
|
|
1344
|
-
]);
|
|
1345
|
-
}
|
|
1346
|
-
return opts([['kind', tsLiteral('builtin')], ['tool', tsLiteral('summarize')]]); // unreachable in practice
|
|
1347
|
-
});
|
|
1348
|
-
const contextSrc = resource('context').map((c) => {
|
|
1349
|
-
const { name, id, folderPath, sidecar } = contextIndexIdentity(node, c, rPlain);
|
|
1350
|
-
// Compile labels the node with `name`; a canvas rename is kept as `label`.
|
|
1351
|
-
const label = typeof c.display?.label === 'string' && c.display.label !== '' && c.display.label !== name
|
|
1352
|
-
? c.display.label : undefined;
|
|
1353
|
-
return opts([
|
|
1354
|
-
['name', tsLiteral(name)],
|
|
1355
|
-
['id', tsLiteral(id)],
|
|
1356
|
-
['folderPath', folderPath !== undefined ? tsLiteral(folderPath) : undefined],
|
|
1357
|
-
['folderKey', rPlain(c, 'folderKey') !== undefined ? tsLiteral(rPlain(c, 'folderKey')) : undefined],
|
|
1358
|
-
// `query` and the retrieval settings, from resource.json or the node (#1183).
|
|
1359
|
-
...contextIndexSettings(node, c, sidecar),
|
|
1360
|
-
['label', label !== undefined ? tsLiteral(label) : undefined],
|
|
1361
|
-
...placement(c, derivedContextSource(runFlowId, node.id, id)),
|
|
1362
|
-
]);
|
|
1363
|
-
});
|
|
1364
|
-
const escalationSrc = resource('escalation').map((e) => {
|
|
1365
|
-
const ei = rInputs(e);
|
|
1366
|
-
const recipients = Array.isArray(ei.recipients)
|
|
1367
|
-
? ei.recipients.map((r) => tsText(r.value))
|
|
1368
|
-
: [];
|
|
1369
|
-
const outcomeNames = Object.keys((ei.outcomeMapping ?? {}));
|
|
1370
|
-
const isQuickForm = e.type === 'uipath.agent.resource.escalation.quick-form';
|
|
1371
|
-
const rows = isQuickForm
|
|
1372
|
-
? (ei.schema?.fields ?? [])
|
|
1373
|
-
: [];
|
|
1374
|
-
const app = ei.app;
|
|
1375
|
-
const extra = (ei._additionalProps ?? {});
|
|
1376
|
-
return opts([
|
|
1377
|
-
['variant', isQuickForm ? tsLiteral('quick-form') : undefined],
|
|
1378
|
-
['name', tsLiteral(rPlain(e, 'name') ?? '')],
|
|
1379
|
-
['description', rPlain(e, 'description') !== undefined ? tsLiteral(rPlain(e, 'description')) : undefined],
|
|
1380
|
-
['fields', rows.length ? `[${rows.map((f) => {
|
|
1381
|
-
const id = String(f.id ?? '');
|
|
1382
|
-
const direction = String(f.direction ?? 'output');
|
|
1383
|
-
// Every row key the source cannot carry is reported, never dropped
|
|
1384
|
-
// silently: a warning, an error under --strict (#1250).
|
|
1385
|
-
const loss = formFieldRowLoss(f, 'escalation');
|
|
1386
|
-
if (loss.length > 0) {
|
|
1387
|
-
const message = formFieldRowLossMessage(`quick-form escalation ${JSON.stringify(rPlain(e, 'name') ?? e.id)} on agent ${JSON.stringify(node.id)}`, id, loss);
|
|
1388
|
-
if (runStrict)
|
|
1389
|
-
throw new Error(`decompile: ${message}`);
|
|
1390
|
-
warnOnce(message);
|
|
1391
|
-
}
|
|
1392
|
-
return opts([
|
|
1393
|
-
['id', tsText(f.id)],
|
|
1394
|
-
// serialize defaults a row's label to its id.
|
|
1395
|
-
['label', typeof f.label === 'string' && f.label !== id ? tsLiteral(f.label) : undefined],
|
|
1396
|
-
['type', tsLiteral(sourceFormFieldType(f.type ?? 'string'))],
|
|
1397
|
-
['direction', tsLiteral(direction)],
|
|
1398
|
-
['required', typeof f.required === 'boolean' && direction !== 'input' ? String(f.required) : undefined],
|
|
1399
|
-
...formFieldSettingsSource(liftFormFieldSettings(f), dataSource),
|
|
1400
|
-
]);
|
|
1401
|
-
}).join(', ')}]` : undefined],
|
|
1402
|
-
['app', app === undefined ? undefined : opts([
|
|
1403
|
-
['key', tsText(app.key)],
|
|
1404
|
-
['name', tsText(app.name)],
|
|
1405
|
-
['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? tsLiteral(app.folderPath) : undefined],
|
|
1406
|
-
])],
|
|
1407
|
-
['recipients', recipients.length ? `[${recipients.join(', ')}]` : undefined],
|
|
1408
|
-
['outcomes', outcomeNames.length ? `[${outcomeNames.map((o) => tsLiteral(o)).join(', ')}]` : undefined],
|
|
1409
|
-
['taskTitle', typeof extra.taskTitle === 'string' && extra.taskTitle !== '' ? tsLiteral(extra.taskTitle) : undefined],
|
|
1410
|
-
// 'medium' is the platform default.
|
|
1411
|
-
['priority', typeof extra.priority === 'string' && extra.priority !== 'medium' ? tsLiteral(extra.priority) : undefined],
|
|
1412
|
-
...placement(e, derivedEscalationSource(runFlowId, node.id, rPlain(e, 'name') ?? '')),
|
|
1413
|
-
]);
|
|
1414
|
-
});
|
|
1415
|
-
const mem = resource('memory', 1)[0];
|
|
1416
|
-
const memorySrc = mem === undefined ? undefined : (() => {
|
|
1417
|
-
// The memory's id is the node type's tail; the def carries it too.
|
|
1418
|
-
const tail = mem.type.slice('uipath.agent.resource.memory.'.length);
|
|
1419
|
-
const id = tail.slice(tail.indexOf('.') + 1);
|
|
1420
|
-
const mi = rInputs(mem);
|
|
1421
|
-
return opts([
|
|
1422
|
-
['name', tsLiteral(rPlain(mem, 'name') ?? '')],
|
|
1423
|
-
['id', tsLiteral(id)],
|
|
1424
|
-
['description', rPlain(mem, 'description') !== undefined ? tsLiteral(rPlain(mem, 'description')) : undefined],
|
|
1425
|
-
// The platform defaults: few-shot on, similarity 0, k 3, hybrid search.
|
|
1426
|
-
['dynamicFewShotLearning', unwrapPlain(mi.dynamicFewShotLearning) === false ? 'false' : undefined],
|
|
1427
|
-
['semanticSimilarity', num(mi.semanticSimilarity) !== undefined && num(mi.semanticSimilarity) !== '0' ? num(mi.semanticSimilarity) : undefined],
|
|
1428
|
-
['kValue', num(mi.kValue) !== undefined && num(mi.kValue) !== '3' ? num(mi.kValue) : undefined],
|
|
1429
|
-
['searchMode', rPlain(mem, 'searchMode') !== undefined && rPlain(mem, 'searchMode') !== 'hybrid'
|
|
1430
|
-
? tsLiteral(rPlain(mem, 'searchMode')) : undefined],
|
|
1431
|
-
]);
|
|
1432
|
-
})();
|
|
1433
|
-
const guardrails = Array.isArray(i.guardrails) && i.guardrails.length > 0
|
|
1434
|
-
? JSON.stringify(i.guardrails)
|
|
1435
|
-
: undefined;
|
|
1436
|
-
const list = (items) => (items.length ? `[${items.join(', ')}]` : undefined);
|
|
1437
|
-
if (node.type === T.voiceAgent) {
|
|
1438
|
-
const v = (i.voice ?? {});
|
|
1439
|
-
const voiceSrc = Object.keys(v).length === 0 ? undefined : opts([
|
|
1440
|
-
['model', typeof v.model === 'string' ? tsLiteral(v.model) : undefined],
|
|
1441
|
-
['persona', typeof v.persona === 'string' ? tsLiteral(v.persona) : undefined],
|
|
1442
|
-
['temperature', num(v.temperature)],
|
|
1443
|
-
['maxTokens', num(v.maxTokens)],
|
|
1444
|
-
]);
|
|
1445
|
-
return `${imp.use('voiceAgent')}(${opts([
|
|
1446
|
-
['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
|
|
1447
|
-
['inputs', inputRows.length ? objLit(inputRows) : undefined],
|
|
1448
|
-
['callContext', (() => {
|
|
1449
|
-
const raw = plain(i.callContext) ?? '';
|
|
1450
|
-
const js = wireExprBody(raw);
|
|
1451
|
-
// Anything but a `$vars.` reference stays the wire text verbatim.
|
|
1452
|
-
return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : tsLiteral(raw);
|
|
1453
|
-
})()],
|
|
1454
|
-
['voice', voiceSrc],
|
|
1455
|
-
['maxIterations', num(i.maxIterations)],
|
|
1456
|
-
['source', tsLiteral(plain(i.source) ?? '')],
|
|
1457
|
-
])})`;
|
|
1458
|
-
}
|
|
1459
|
-
if (node.type === T.conversationalAgent) {
|
|
1460
|
-
const st = (i.conversationalAgentSettings ?? {});
|
|
1461
|
-
const settingExpr = (k) => {
|
|
1462
|
-
// `unwrapSource` rather than a string test: since the envelope reaches
|
|
1463
|
-
// these five keys (migrate's `NESTED_BINDING_INPUTS`), a bare string is
|
|
1464
|
-
// only what a pre-1.6 file carries. Reading strings alone made every
|
|
1465
|
-
// modern file decompile to a conversational agent with no turn wiring.
|
|
1466
|
-
const raw = unwrapSource(st[k]);
|
|
1467
|
-
if (raw === undefined || raw === '' || raw === '=js:')
|
|
1468
|
-
return undefined;
|
|
1469
|
-
// Only a `=js:` value is an expression: `emitRef` lowers a single path to
|
|
1470
|
-
// `out(...)` and anything composite to a `js` template. Any other string
|
|
1471
|
-
// is literal text, as the product reads it (flow-schema
|
|
1472
|
-
// `coerceToExpressionValue`), even when it looks like `$vars.…`.
|
|
1473
|
-
return raw.startsWith('=js:') ? emitRef(raw.slice(4), imp, inputNames) : tsLiteral(raw);
|
|
1474
|
-
};
|
|
1475
|
-
// `'simple'` mode DERIVES the four runtime fields from `context`, so only
|
|
1476
|
-
// the context binding is the author's; recovering the derived ones would
|
|
1477
|
-
// re-emit them as hand-bound custom fields.
|
|
1478
|
-
const mode = typeof st.mode === 'string' ? st.mode : 'simple';
|
|
1479
|
-
const settingsSrc = mode === 'custom'
|
|
1480
|
-
? opts([
|
|
1481
|
-
['mode', tsLiteral('custom')],
|
|
1482
|
-
['conversationId', settingExpr('conversationId')],
|
|
1483
|
-
['exchangeId', settingExpr('exchangeId')],
|
|
1484
|
-
['messages', settingExpr('messages')],
|
|
1485
|
-
['userSettings', settingExpr('userSettings')],
|
|
1486
|
-
])
|
|
1487
|
-
: opts([['context', settingExpr('context')]]);
|
|
1488
|
-
return `${imp.use('conversationalAgent')}(${opts([
|
|
1489
|
-
['model', tsLiteral(plain(i.model) ?? '')],
|
|
1490
|
-
['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
|
|
1491
|
-
// `settings` is required on ConversationalAgentInputs, so an empty one
|
|
1492
|
-
// stays as `{}`, which check then reports as CONV_AGENT_NO_TURN.
|
|
1493
|
-
['settings', settingsSrc || '{}'],
|
|
1494
|
-
['temperature', num(i.temperature)],
|
|
1495
|
-
['maxTokenPerResponse', num(i.maxTokenPerResponse)],
|
|
1496
|
-
['maxIterations', num(i.maxIterations)],
|
|
1497
|
-
['guardrails', guardrails],
|
|
1498
|
-
['source', tsLiteral(plain(i.source) ?? '')],
|
|
1499
|
-
])})`;
|
|
1500
|
-
}
|
|
1501
|
-
return `${imp.use('inlineAgent')}(${opts([
|
|
1502
|
-
['model', tsLiteral(plain(i.model) ?? '')],
|
|
1503
|
-
['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
|
|
1504
|
-
['userPrompt', promptSrc(i.userPrompt) ?? tsLiteral('')],
|
|
1505
|
-
['inputs', inputRows.length ? objLit(inputRows) : undefined],
|
|
1506
|
-
['returns', returns],
|
|
1507
|
-
['temperature', num(i.temperature)],
|
|
1508
|
-
['maxTokenPerResponse', num(i.maxTokenPerResponse)],
|
|
1509
|
-
['maxIterations', num(i.maxIterations)],
|
|
1510
|
-
['mode', plain(i.mode) !== undefined ? tsLiteral(plain(i.mode)) : undefined],
|
|
1511
|
-
['guardrails', guardrails],
|
|
1512
|
-
['context', list(contextSrc)],
|
|
1513
|
-
['tools', list(toolSrc)],
|
|
1514
|
-
['escalation', list(escalationSrc)],
|
|
1515
|
-
['memory', memorySrc],
|
|
1516
|
-
// The agent's directory uuid is derived from the flow and step ids, but a
|
|
1517
|
-
// decompiled flow must keep the ORIGINAL — the sidecar lives at
|
|
1518
|
-
// `<source>/agent.json`, so a re-derived one would point at nothing.
|
|
1519
|
-
['source', tsLiteral(plain(i.source) ?? '')],
|
|
1520
|
-
])})`;
|
|
1521
|
-
}
|
|
1522
829
|
/**
|
|
1523
830
|
* A connector EVENT subscription, shared by the trigger (`onEvent`) and the
|
|
1524
831
|
* mid-flow wait (`waitForEvent`) — the same node body in both places, so the
|
|
@@ -1608,83 +915,6 @@ function emitEventSubscription(node, imp, inputNames) {
|
|
|
1608
915
|
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
1609
916
|
]) + unboundMarker(node, { connection: conn, folder }, connector, event);
|
|
1610
917
|
}
|
|
1611
|
-
/**
|
|
1612
|
-
* Read back the expression template older SDK builds emitted on an in-solution
|
|
1613
|
-
* `agent()` input (#1013): `{ type: 'literal', expression: '{{ <js> }}' }`.
|
|
1614
|
-
* serialize now emits the platform's `{ type: 'jsExpression', expression }`
|
|
1615
|
-
* instead, but `.flow` files written between 2026-08-10 and the fix still carry
|
|
1616
|
-
* the template, and the runtime evaluates it (the agent receives the value, not
|
|
1617
|
-
* the braces). So on an in-solution agent node a literal that is exactly one
|
|
1618
|
-
* `{{ … }}` is that expression, and it decompiles to the reference it was
|
|
1619
|
-
* written from rather than to a string. Anything else is returned unchanged.
|
|
1620
|
-
*/
|
|
1621
|
-
function legacyAgentTemplateInput(v) {
|
|
1622
|
-
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
1623
|
-
return v;
|
|
1624
|
-
const o = v;
|
|
1625
|
-
if (o.type !== 'literal' || typeof o.expression !== 'string')
|
|
1626
|
-
return v;
|
|
1627
|
-
const m = /^\{\{\s*([\s\S]+?)\s*\}\}$/.exec(o.expression);
|
|
1628
|
-
if (!m || m[1].includes('}}') || m[1].includes('{{'))
|
|
1629
|
-
return v;
|
|
1630
|
-
return { ...o, type: 'jsExpression', expression: m[1] };
|
|
1631
|
-
}
|
|
1632
|
-
function emitPublishedResource(node, imp, inputNames) {
|
|
1633
|
-
// The published AGENT resource — the same shape as the process family's
|
|
1634
|
-
// node types (identity in the definition's binding pair, contract in its
|
|
1635
|
-
// output schema), which lift themselves (nodes/process/flow/).
|
|
1636
|
-
const FAMILIES = [
|
|
1637
|
-
{ prefix: 'uipath.core.agent.', factory: 'agent' },
|
|
1638
|
-
];
|
|
1639
|
-
const family = FAMILIES.find((f) => node.type.startsWith(f.prefix));
|
|
1640
|
-
if (!family)
|
|
1641
|
-
return undefined;
|
|
1642
|
-
const key = node.type.slice(family.prefix.length);
|
|
1643
|
-
const def = runDefs.get(`${node.type}@${node.typeVersion}`) ?? runDefs.get(node.type);
|
|
1644
|
-
const values = (def?.model?.bindings?.values ?? []);
|
|
1645
|
-
const binding = (name) => {
|
|
1646
|
-
const row = values.find((v) => v.name === name);
|
|
1647
|
-
return typeof row?.default === 'string' ? row.default : undefined;
|
|
1648
|
-
};
|
|
1649
|
-
// The binding pair is the identity the runtime resolves by; without a
|
|
1650
|
-
// definition to read there is nothing to recover it from.
|
|
1651
|
-
const name = binding('name');
|
|
1652
|
-
const folderPath = binding('folderPath');
|
|
1653
|
-
// A sibling project of the same solution (#951): the model names its project
|
|
1654
|
-
// instead of a folder. `isInSolution` is flow-core's marker; an in-solution
|
|
1655
|
-
// agent emitted before #951 carries only `projectId`. Read back as
|
|
1656
|
-
// `location: 'in-solution'` so the recompile takes the in-solution path
|
|
1657
|
-
// instead of a published one with an empty folder.
|
|
1658
|
-
const projectId = typeof def?.model?.projectId === 'string' ? def.model.projectId : undefined;
|
|
1659
|
-
const inSolution = def?.model?.isInSolution === true || projectId !== undefined;
|
|
1660
|
-
const inputs = { ...(node.inputs ?? {}) };
|
|
1661
|
-
// The design-time fire-and-forget switch is an OPTION on the factory, not a
|
|
1662
|
-
// process argument — serialize adds it to `inputs` after rendering the
|
|
1663
|
-
// author's map, so it comes back off here.
|
|
1664
|
-
const fireAndForget = unwrapPlain(inputs.fireAndForget) === true;
|
|
1665
|
-
delete inputs.fireAndForget;
|
|
1666
|
-
if (family.factory === 'agent' && (def?.model?.isInSolution === true || def?.model?.projectId !== undefined)) {
|
|
1667
|
-
for (const [k, v] of Object.entries(inputs))
|
|
1668
|
-
inputs[k] = legacyAgentTemplateInput(v);
|
|
1669
|
-
}
|
|
1670
|
-
const inputRows = Object.entries(inputs)
|
|
1671
|
-
.map(([k, v]) => [k, emitVal(v, imp, inputNames)]);
|
|
1672
|
-
const schema = def?.outputDefinition?.output?.schema?.properties;
|
|
1673
|
-
const returns = schema && Object.keys(schema).length > 0
|
|
1674
|
-
? objLit(Object.entries(schema).map(([k, v]) => [k, tsLiteral(String(v?.type ?? 'string'))]))
|
|
1675
|
-
: undefined;
|
|
1676
|
-
return `${imp.use(family.factory)}(${opts([
|
|
1677
|
-
['key', tsLiteral(key)],
|
|
1678
|
-
['name', name !== undefined ? tsLiteral(name) : undefined],
|
|
1679
|
-
['location', inSolution ? tsLiteral('in-solution') : undefined],
|
|
1680
|
-
['folderPath', !inSolution && folderPath !== undefined ? tsLiteral(folderPath) : undefined],
|
|
1681
|
-
['projectId', inSolution && projectId !== undefined ? tsLiteral(projectId) : undefined],
|
|
1682
|
-
['inputs', inputRows.length ? objLit(inputRows) : undefined],
|
|
1683
|
-
['returns', returns],
|
|
1684
|
-
['form', family.form !== undefined ? tsLiteral(family.form) : undefined],
|
|
1685
|
-
['completion', fireAndForget ? tsLiteral('fire-and-forget') : undefined],
|
|
1686
|
-
])})`;
|
|
1687
|
-
}
|
|
1688
918
|
function emitConnector(node, imp, inputNames, o) {
|
|
1689
919
|
const split = splitConnector(node.type);
|
|
1690
920
|
const detail = (node.inputs?.detail ?? {});
|
|
@@ -2312,7 +1542,11 @@ function forkRegion(edges, stop, scope, ipdom, ctx, refInto) {
|
|
|
2312
1542
|
const targets = edges.map((e) => e.targetNodeId);
|
|
2313
1543
|
const join = commonPostDominator(targets, ipdom);
|
|
2314
1544
|
const joinNode = join !== undefined ? ctx.graph.byId.get(join) : undefined;
|
|
2315
|
-
|
|
1545
|
+
// Arms may rejoin at the region's own continuation (`stop`) when that is a
|
|
1546
|
+
// plain step: the arms end in `.stepToRef(stop)` and nothing follows them
|
|
1547
|
+
// here. Not at a merge node that is `stop`: the enclosing construct continues
|
|
1548
|
+
// from it, so a `.parallel` inside this arm cannot also own it.
|
|
1549
|
+
if (join === undefined || joinNode === undefined || !parallelJoinable(joinNode) || (join === stop && joinNode.type === T.merge)) {
|
|
2316
1550
|
if (ctx.o.bestEffort === false)
|
|
2317
1551
|
throw new Error(`decompile: unstructured fan-out (${edges.length} edges).`);
|
|
2318
1552
|
ctx.graph.accounted.add(edges[0]);
|
|
@@ -2324,22 +1558,75 @@ function forkRegion(edges, stop, scope, ipdom, ctx, refInto) {
|
|
|
2324
1558
|
}
|
|
2325
1559
|
for (const e of edges)
|
|
2326
1560
|
ctx.graph.accounted.add(e);
|
|
2327
|
-
const fork = parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto);
|
|
1561
|
+
const fork = parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto, undefined, edges[0].sourceNodeId);
|
|
2328
1562
|
if (fork.next.brk || fork.next.cont)
|
|
2329
1563
|
return [fork.seg, fork.next.brk ? '.break()' : '.continue()'];
|
|
2330
1564
|
return [fork.seg, ...region(fork.next.cursor, stop, scope, ipdom, ctx, refInto)];
|
|
2331
1565
|
}
|
|
2332
1566
|
/**
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
1567
|
+
* Whether a fan-out whose arms all reconverge at `join` folds to a
|
|
1568
|
+
* `.parallel(...)`: at a merge node, the SDK's own join; at any other step (a
|
|
1569
|
+
* step, a decision, a loop, …), the canvas shape of several steps feeding one
|
|
1570
|
+
* (#1310), which the arms spell by ending in `.stepToRef(join)` — serialize
|
|
1571
|
+
* writes no merge node when no arm hands it a tail. Not at an End or a
|
|
1572
|
+
* Terminate: arms that all end share the End by name (#1231/#1239) and nothing
|
|
1573
|
+
* follows, which is another fold (#1267).
|
|
2338
1574
|
*/
|
|
2339
|
-
function
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
1575
|
+
function parallelJoinable(join) {
|
|
1576
|
+
return join.type !== T.end && join.type !== T.terminate;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* The `.parallel(join, [arms])` segment for a fork whose `targets` (one per
|
|
1580
|
+
* edge; the caller accounts those edges) meet at `join`. Each arm is the
|
|
1581
|
+
* region up to `join`.
|
|
1582
|
+
*
|
|
1583
|
+
* At a merge node, `join` names the `.parallel` and `next` is where the walk
|
|
1584
|
+
* goes after it: its `output` edge, accounted by `continueOn` (`brk` / `cont`
|
|
1585
|
+
* when that edge leaves through the enclosing loop's `break` / spelled
|
|
1586
|
+
* `continue`). At any other step (#1310) the arms end in `.stepToRef(join)`,
|
|
1587
|
+
* the `.parallel` gets a fresh name (`forkName`, after `source`) because no
|
|
1588
|
+
* node carries one, and `next` is `join` itself: the main path emits it, and
|
|
1589
|
+
* its only incoming edges are the arms' refs.
|
|
1590
|
+
*
|
|
1591
|
+
* `fork` is the merge node a designer-drawn split leaves from, written as
|
|
1592
|
+
* `{ fork }` (#1310); the caller accounts its outgoing edges.
|
|
1593
|
+
*/
|
|
1594
|
+
function parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto, fork, source) {
|
|
1595
|
+
const atMerge = joinNode.type === T.merge;
|
|
1596
|
+
const name = atMerge ? join : forkName(source ?? fork?.id, ctx);
|
|
1597
|
+
const arms = targets.map((t, i) => {
|
|
1598
|
+
const segs = region(t, join, scope, ipdom, ctx, refInto);
|
|
1599
|
+
// The arm hands the join a tail only when its walk ran up to the join; a
|
|
1600
|
+
// path that already left (`.break()`, `.continue()`, a ref, an End, a
|
|
1601
|
+
// Terminate) needs no ref — and gets none.
|
|
1602
|
+
if (!atMerge && !endsPath(segs[segs.length - 1]))
|
|
1603
|
+
segs.push(`.stepToRef(${tsLiteral(join)})`);
|
|
1604
|
+
return armCb(`a${i}`, segs);
|
|
1605
|
+
});
|
|
1606
|
+
const seg = `.parallel(${tsLiteral(name)}, [\n${arms.map((a) => reindent(a, ' ')).join(',\n')},\n]${forkOptsArg(atMerge ? joinNode : undefined, fork)})`;
|
|
1607
|
+
return { seg, next: atMerge ? continueOn(joinNode, 'output', ctx, refInto) : { cursor: join } };
|
|
1608
|
+
}
|
|
1609
|
+
/** Whether a chain segment ends its path, so nothing may follow it in the same list. */
|
|
1610
|
+
function endsPath(seg) {
|
|
1611
|
+
return seg !== undefined && /^\.(stepToRef|break|continue|return|terminate)\(/.test(seg);
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* A name for a `.parallel(...)` that creates no merge node: `<source>Fork`,
|
|
1615
|
+
* numbered when that is a node id, a declared input, variable or output, or a
|
|
1616
|
+
* name already minted. No node carries it (the arms join on a plain step by
|
|
1617
|
+
* ref), but it must still not collide with any of those: check reads a
|
|
1618
|
+
* `$vars.<name>` of a parallel's name as a read of its merge, so a variable
|
|
1619
|
+
* of that name would fail PARALLEL_HAS_NO_OUTPUT on recompile.
|
|
1620
|
+
*/
|
|
1621
|
+
function forkName(source, ctx) {
|
|
1622
|
+
const base = `${source ?? 'parallel'}Fork`;
|
|
1623
|
+
const globals = new Set(readFlowGlobals(ctx.flow.variables).map((g) => g.name ?? g.id));
|
|
1624
|
+
const taken = (n) => ctx.graph.byId.has(n) || globals.has(n) || (ctx.forkNames?.has(n) ?? false);
|
|
1625
|
+
let name = base;
|
|
1626
|
+
for (let i = 2; taken(name); i++)
|
|
1627
|
+
name = `${base}${i}`;
|
|
1628
|
+
(ctx.forkNames ??= new Set()).add(name);
|
|
1629
|
+
return name;
|
|
2343
1630
|
}
|
|
2344
1631
|
/** Re-indent every non-empty line of a (possibly multi-line) segment by `pad`. */
|
|
2345
1632
|
function reindent(seg, pad) {
|
|
@@ -2506,21 +1793,50 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
|
|
|
2506
1793
|
return segs;
|
|
2507
1794
|
}
|
|
2508
1795
|
if (node.type === T.merge) {
|
|
2509
|
-
// A
|
|
2510
|
-
//
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
1796
|
+
// A merge node with several `output` edges is a FORK the designer drew
|
|
1797
|
+
// (#1310): Studio Web's parallel split is a `core.logic.merge` with one
|
|
1798
|
+
// incoming edge and one edge per arm, the arms rejoining at a second
|
|
1799
|
+
// merge node (or at a plain step). `.parallel(join, arms, { fork })`
|
|
1800
|
+
// recreates both nodes and every edge.
|
|
1801
|
+
const outs = scopeSuccEdges(ctx.graph, node.id, scope).filter((e) => e.sourcePort === 'output');
|
|
1802
|
+
const join = outs.length >= 2 ? ipdom.get(node.id) : undefined;
|
|
1803
|
+
const joinNode = join !== undefined && join !== EXIT ? ctx.graph.byId.get(join) : undefined;
|
|
1804
|
+
// As in `forkRegion`: a plain-step join may be the region's own `stop`.
|
|
1805
|
+
if (join !== undefined && joinNode !== undefined && parallelJoinable(joinNode) && !(join === stop && joinNode.type === T.merge)) {
|
|
1806
|
+
const targets = outs.map((e) => follow(ctx.graph, node.id, e.targetNodeId));
|
|
1807
|
+
const fork = parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto, node);
|
|
1808
|
+
segs.push(fork.seg);
|
|
2514
1809
|
flagDropped(node, ctx, segs);
|
|
2515
|
-
|
|
1810
|
+
if (fork.next.brk || fork.next.cont) {
|
|
1811
|
+
segs.push(fork.next.brk ? '.break()' : '.continue()');
|
|
1812
|
+
return segs;
|
|
1813
|
+
}
|
|
1814
|
+
cursor = fork.next.cursor;
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
// A merge node with one edge in and one out is a pass-through the
|
|
1818
|
+
// designer left in place (a join whose arms were later removed). The
|
|
1819
|
+
// layout names it, so it is kept: it falls through to the regular step
|
|
1820
|
+
// path below and comes back as the raw node of its own definition
|
|
1821
|
+
// (#1310). Skipping it dropped the node, and compile then refused the
|
|
1822
|
+
// `.layout()` key. A bare merge with several edges in (a join whose fork
|
|
1823
|
+
// was not detected) is still skipped — best effort.
|
|
1824
|
+
const ins = ctx.graph.inEdges(node.id).filter((e) => scope.has(e.sourceNodeId));
|
|
1825
|
+
if (outs.length !== 1 || ins.length !== 1) {
|
|
1826
|
+
const next = continueOn(node, 'output', ctx, refInto);
|
|
1827
|
+
if (next.brk || next.cont) {
|
|
1828
|
+
segs.push(next.brk ? '.break()' : '.continue()');
|
|
1829
|
+
flagDropped(node, ctx, segs);
|
|
1830
|
+
return segs;
|
|
1831
|
+
}
|
|
1832
|
+
cursor = next.cursor;
|
|
1833
|
+
flagDropped(node, ctx, segs);
|
|
1834
|
+
continue;
|
|
2516
1835
|
}
|
|
2517
|
-
cursor = next.cursor;
|
|
2518
|
-
flagDropped(node, ctx, segs);
|
|
2519
|
-
continue;
|
|
2520
1836
|
}
|
|
2521
1837
|
if (node.type === T.decision) {
|
|
2522
1838
|
const r = ipdom.get(node.id) ?? stop;
|
|
2523
|
-
const cond = emitBareExpr(
|
|
1839
|
+
const cond = emitBareExpr(conditionText((node.inputs ?? {}).expression), ctx.imp, ctx.inputNames);
|
|
2524
1840
|
const thenSegs = forkRegion(portEdges(ctx.graph, node.id, 'true'), r, scope, ipdom, ctx, refInto);
|
|
2525
1841
|
const elseSegs = forkRegion(portEdges(ctx.graph, node.id, 'false'), r, scope, ipdom, ctx, refInto);
|
|
2526
1842
|
const labels = armLabels(node);
|
|
@@ -2603,7 +1919,14 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
|
|
|
2603
1919
|
}
|
|
2604
1920
|
if (node.type === T.subflow) {
|
|
2605
1921
|
segs.push(`.step(${tsLiteral(node.id)}, ${emitSubflow(node, ctx)}${optsArg(node, ctx)})`);
|
|
2606
|
-
|
|
1922
|
+
// A subflow step guards its body like any step: `core.subflow` declares
|
|
1923
|
+
// the `error` handle, and `.onError()` after the step authors it. Without
|
|
1924
|
+
// this the handler's edge had no spelling and a recompile dropped it.
|
|
1925
|
+
const subflowSucc = scopeSucc(ctx.graph, node.id, scope);
|
|
1926
|
+
const subflowHandler = errorHandlerSeg(node.id, subflowSucc, scope, ipdom, ctx);
|
|
1927
|
+
if (subflowHandler)
|
|
1928
|
+
segs.push(subflowHandler);
|
|
1929
|
+
cursor = follow(ctx.graph, node.id, subflowSucc[0]);
|
|
2607
1930
|
if (cursor === undefined)
|
|
2608
1931
|
accountImplicitTail(node, ctx, refInto);
|
|
2609
1932
|
flagDropped(node, ctx, segs);
|
|
@@ -2719,11 +2042,13 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
|
|
|
2719
2042
|
flagDropped(node, ctx, segs);
|
|
2720
2043
|
continue;
|
|
2721
2044
|
}
|
|
2722
|
-
// Parallel: the fork's ipdom is the
|
|
2045
|
+
// Parallel: the fork's ipdom is the join — the merge node of a `.parallel`,
|
|
2046
|
+
// or a plain step several steps feed (#1310); each successor is one arm.
|
|
2723
2047
|
const join = ipdom.get(node.id);
|
|
2724
2048
|
const joinNode = join !== undefined ? ctx.graph.byId.get(join) : undefined;
|
|
2725
|
-
|
|
2726
|
-
|
|
2049
|
+
// A plain-step join may be the region's own `stop`: the arms end in refs to it and the walk ends there.
|
|
2050
|
+
if (join && join !== EXIT && joinNode && parallelJoinable(joinNode)) {
|
|
2051
|
+
const fork = parallelSeg(join, joinNode, succ.map((t) => follow(ctx.graph, node.id, t)), scope, ipdom, ctx, refInto, undefined, node.id);
|
|
2727
2052
|
segs.push(fork.seg);
|
|
2728
2053
|
flagDropped(node, ctx, segs);
|
|
2729
2054
|
if (fork.next.brk || fork.next.cont) {
|
|
@@ -2963,7 +2288,7 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
|
|
|
2963
2288
|
// First pass: is every case one shared `<disc> === <literal>`? If not, every
|
|
2964
2289
|
// case is emitted as `when:` with its expression as written.
|
|
2965
2290
|
const parsed = cases.map((c) => {
|
|
2966
|
-
const p = parseSwitchCase(
|
|
2291
|
+
const p = parseSwitchCase(conditionText(c.expression));
|
|
2967
2292
|
if (p) {
|
|
2968
2293
|
if (disc === undefined)
|
|
2969
2294
|
disc = p.lhs;
|
|
@@ -2985,13 +2310,13 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
|
|
|
2985
2310
|
// edges leave from the ORIGINAL node's handles, which merge keeps (#1130).
|
|
2986
2311
|
const idPart = typeof c.id === 'string' && c.id !== '' && c.id !== `case_${caseIndex}` ? `, id: ${tsLiteral(c.id)}` : '';
|
|
2987
2312
|
const head = nonUniform || !p
|
|
2988
|
-
? `when: ${emitBareExpr(
|
|
2313
|
+
? `when: ${emitBareExpr(conditionText(c.expression), ctx.imp, ctx.inputNames)}`
|
|
2989
2314
|
: `value: ${p.value}`;
|
|
2990
2315
|
return `{ ${head}${labelPart}${idPart}, body: ${armCb('b', armSegs)} }`;
|
|
2991
2316
|
});
|
|
2992
2317
|
// A case with no condition has no `when:` that compiles: emitting `js\`\``
|
|
2993
2318
|
// would fail at compile ([EXPR_SYNTAX]) with no warning here.
|
|
2994
|
-
const empty = cases.filter((c) =>
|
|
2319
|
+
const empty = cases.filter((c) => conditionText(c.expression) === '');
|
|
2995
2320
|
if (empty.length > 0) {
|
|
2996
2321
|
const listing = empty.map((c) => tsLiteral(c.label ?? c.id)).join(', ');
|
|
2997
2322
|
const message = `switch ${tsLiteral(node.id)} has a case with no condition (${listing}). `
|
|
@@ -3084,13 +2409,24 @@ function layoutSeg(flow, nodes, edges) {
|
|
|
3084
2409
|
// compile refuses the whole `.layout()`. So its entry, and any edge route that
|
|
3085
2410
|
// ends at it, is left out and compile places the node itself.
|
|
3086
2411
|
const renamed = (id) => runAbsorbed.has(id) && runAbsorbed.get(id) !== id;
|
|
2412
|
+
// A resource attached to a node whose spec cannot carry it (a tool on a voice
|
|
2413
|
+
// agent) is reached only over artifact edges, and no spec folded it in: the
|
|
2414
|
+
// source has no spelling for it, so the recompiled flow has no such node, and
|
|
2415
|
+
// a layout entry for it would make compile refuse the whole `.layout()`.
|
|
2416
|
+
const incoming = new Map();
|
|
2417
|
+
for (const e of edges)
|
|
2418
|
+
(incoming.get(e.targetNodeId) ?? incoming.set(e.targetNodeId, []).get(e.targetNodeId)).push(e);
|
|
2419
|
+
const unspelled = (id) => {
|
|
2420
|
+
const ins = incoming.get(id) ?? [];
|
|
2421
|
+
return ins.length > 0 && !runAbsorbed.has(id) && ins.every((e) => ARTIFACT_PORTS.has(e.sourcePort));
|
|
2422
|
+
};
|
|
3087
2423
|
const nodeEntries = [];
|
|
3088
2424
|
for (const n of nodes) {
|
|
3089
2425
|
// A sticky note carries its own geometry in `.stickyNotes()`; a layout entry
|
|
3090
2426
|
// for it would be a second, competing source for the same coordinates.
|
|
3091
2427
|
if (n.type === STICKY_NOTE_TYPE)
|
|
3092
2428
|
continue;
|
|
3093
|
-
if (renamed(n.id))
|
|
2429
|
+
if (renamed(n.id) || unspelled(n.id))
|
|
3094
2430
|
continue;
|
|
3095
2431
|
const ui = { ...(n.ui ?? {}), ...(hoisted[n.id] ?? {}) };
|
|
3096
2432
|
const pos = ui.position;
|
|
@@ -3128,7 +2464,7 @@ function layoutSeg(flow, nodes, edges) {
|
|
|
3128
2464
|
}
|
|
3129
2465
|
if (!parts.length)
|
|
3130
2466
|
continue;
|
|
3131
|
-
if (renamed(e.sourceNodeId) || renamed(e.targetNodeId))
|
|
2467
|
+
if (renamed(e.sourceNodeId) || renamed(e.targetNodeId) || unspelled(e.targetNodeId))
|
|
3132
2468
|
continue;
|
|
3133
2469
|
const pair = `${e.sourceNodeId}->${e.targetNodeId}`;
|
|
3134
2470
|
if ((pairCount.get(pair) ?? 0) !== 1)
|
|
@@ -3153,7 +2489,10 @@ function layoutSeg(flow, nodes, edges) {
|
|
|
3153
2489
|
*/
|
|
3154
2490
|
function emitSubflow(node, ctx) {
|
|
3155
2491
|
const child = ctx.flow.subflows?.[node.id];
|
|
3156
|
-
|
|
2492
|
+
// `errorHandlingEnabled` is the flag serialize sets for `.onError()`, not a
|
|
2493
|
+
// child input; `.onError(...)` after the step puts it back.
|
|
2494
|
+
const { errorHandlingEnabled: _handled, ...inputs } = (node.inputs ?? {});
|
|
2495
|
+
const inputsSrc = emitValMap(inputs, ctx.imp, ctx.inputNames) || '{}';
|
|
3157
2496
|
if (!child) {
|
|
3158
2497
|
if (ctx.o.bestEffort === false)
|
|
3159
2498
|
throw new Error(`decompile: subflow "${node.id}" has no body in subflows[].`);
|
|
@@ -3169,7 +2508,52 @@ function emitSubflow(node, ctx) {
|
|
|
3169
2508
|
* own scope; the child's trigger is its `<caller>Start` node, written as
|
|
3170
2509
|
* `.trigger(manual(), { label })` only when its label is not `SUBFLOW_START_LABEL`.
|
|
3171
2510
|
*/
|
|
3172
|
-
function emitChildFlow(name,
|
|
2511
|
+
function emitChildFlow(name, childFile, ctx) {
|
|
2512
|
+
// Body: the child's trigger is its own manual Start; walk from its successor.
|
|
2513
|
+
const trigger = (childFile.nodes ?? []).find((n) => isTrigger(n.type) && !(childFile.edges ?? []).some((e) => e.targetNodeId === n.id));
|
|
2514
|
+
// The child's nodes read its inputs through that Start (`$vars.<caller>Start.
|
|
2515
|
+
// output.<input>`); read it back as `start`, the one name both `input()` and
|
|
2516
|
+
// check know a child's trigger by (#1313). Not when a child node is itself
|
|
2517
|
+
// named `start`: the rename would then point the reads at that node.
|
|
2518
|
+
const readAsStart = trigger !== undefined && trigger.id !== DEFAULT_TRIGGER_ID
|
|
2519
|
+
&& !(childFile.nodes ?? []).some((n) => n.id === DEFAULT_TRIGGER_ID);
|
|
2520
|
+
const child = readAsStart ? readChildStartAsStart(childFile, trigger.id) : childFile;
|
|
2521
|
+
const outerTriggerId = currentTriggerId;
|
|
2522
|
+
currentTriggerId = readAsStart ? DEFAULT_TRIGGER_ID : (trigger?.id ?? DEFAULT_TRIGGER_ID);
|
|
2523
|
+
try {
|
|
2524
|
+
return emitChildFlowIn(name, child, trigger, ctx);
|
|
2525
|
+
}
|
|
2526
|
+
finally {
|
|
2527
|
+
currentTriggerId = outerTriggerId;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* A child flow's reads of its own Start node, `$vars.<caller>Start…`, read as
|
|
2532
|
+
* `$vars.start…`. serialize names a child's trigger `<caller>Start` and renames
|
|
2533
|
+
* `start` to it in the child scope, so `input('x')` — which lowers to
|
|
2534
|
+
* `$vars.start.output.x` — reaches it; this is the inverse. Rewritten in code
|
|
2535
|
+
* spans (`renameVarRoot`: `=js:` values, bare `$vars.` paths, the raw-JS
|
|
2536
|
+
* fields) and in the BINDING slots that hold a variable path instead of an
|
|
2537
|
+
* expression: a form row's `binding` (`vars.<caller>Start.output.x`), an agent
|
|
2538
|
+
* input's `binding`, and an action app's `appInputBindings` (`=vars.…`). Only
|
|
2539
|
+
* those: a literal string elsewhere that happens to read `vars.<caller>Start…`
|
|
2540
|
+
* (a variable's default, a label) is data and stays as written.
|
|
2541
|
+
*/
|
|
2542
|
+
function readChildStartAsStart(child, startId) {
|
|
2543
|
+
const renamed = renameVarRoot(child, startId, DEFAULT_TRIGGER_ID);
|
|
2544
|
+
const path = new RegExp(`^(=?vars\\.)${startId.replace(/[$]/g, '\\$&')}(?![A-Za-z0-9_$])`);
|
|
2545
|
+
const walk = (x, key, parentKey) => {
|
|
2546
|
+
if (typeof x === 'string')
|
|
2547
|
+
return key === 'binding' || parentKey === 'appInputBindings' ? x.replace(path, `$1${DEFAULT_TRIGGER_ID}`) : x;
|
|
2548
|
+
if (Array.isArray(x))
|
|
2549
|
+
return x.map((y) => walk(y, key, parentKey));
|
|
2550
|
+
if (x && typeof x === 'object')
|
|
2551
|
+
return Object.fromEntries(Object.entries(x).map(([k, y]) => [k, walk(y, k, key)]));
|
|
2552
|
+
return x;
|
|
2553
|
+
};
|
|
2554
|
+
return walk(renamed);
|
|
2555
|
+
}
|
|
2556
|
+
function emitChildFlowIn(name, child, trigger, ctx) {
|
|
3173
2557
|
const globals = readFlowGlobals(child.variables, runStrict);
|
|
3174
2558
|
const childCtx = {
|
|
3175
2559
|
graph: new Graph(child.nodes ?? [], child.edges ?? []),
|
|
@@ -3193,8 +2577,6 @@ function emitChildFlow(name, child, ctx) {
|
|
|
3193
2577
|
const def = g.defaultValue !== undefined ? `, ${tsLiteral(g.defaultValue)}` : '';
|
|
3194
2578
|
parts.push(`.var(${tsLiteral(g.name ?? g.id)}, ${declExpr(g, false, ctx.imp)}${def})`);
|
|
3195
2579
|
}
|
|
3196
|
-
// Body: the child's trigger is its own manual Start; walk from its successor.
|
|
3197
|
-
const trigger = (child.nodes ?? []).find((n) => isTrigger(n.type) && !(child.edges ?? []).some((e) => e.targetNodeId === n.id));
|
|
3198
2580
|
if (trigger?.type === T.triggerManual && hasRenamedLabel(trigger, SUBFLOW_START_LABEL)) {
|
|
3199
2581
|
parts.push(`.trigger(${ctx.imp.use('manual')}(), { ${triggerOptParts(trigger, SUBFLOW_START_LABEL).join(', ')} })`);
|
|
3200
2582
|
}
|