@uipath/maestro-builder-sdk 6.41.2 → 6.43.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 +99 -99
- package/dist/api-members.md +307 -307
- package/dist/check.d.ts +0 -19
- package/dist/check.js +40 -1036
- package/dist/cli/commands/registry.js +15 -0
- package/dist/compile-cli.js +2 -1
- package/dist/core/actions.d.ts +1 -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/core/cli-spelling.d.ts +9 -0
- package/dist/core/cli-spelling.js +9 -0
- package/dist/{config.js → core/connector-configuration.js} +2 -2
- package/dist/core/library-cache.d.ts +0 -9
- package/dist/core/library-cache.js +0 -9
- package/dist/core/library.d.ts +47 -0
- package/dist/core/library.js +135 -34
- package/dist/core/list-envelope.d.ts +36 -1
- package/dist/core/list-envelope.js +24 -19
- package/dist/core/lookup-store.d.ts +4 -3
- package/dist/core/lookup-store.js +46 -38
- package/dist/core/node-id-placeholder.d.ts +8 -0
- package/dist/core/node-id-placeholder.js +10 -0
- package/dist/core/read-text.d.ts +26 -0
- package/dist/core/read-text.js +43 -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 +34 -822
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +130 -6
- package/dist/flow-sdk.d.ts +1 -0
- package/dist/flow-sdk.js +1 -0
- 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/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 +181 -10
- package/dist/nodes/process/flow/spec.d.ts +112 -5
- package/dist/registry/describe-cli.d.ts +10 -0
- package/dist/registry/describe-cli.js +41 -0
- package/dist/registry/library-reader.d.ts +12 -2
- package/dist/registry/library-reader.js +17 -16
- package/dist/registry/lookup-specs.d.ts +17 -1
- package/dist/registry/lookup-specs.js +3 -7
- package/dist/registry/path-cli.js +2 -1
- package/dist/registry/search-cli.js +2 -1
- package/dist/serialize.d.ts +9 -54
- package/dist/serialize.js +47 -2763
- 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
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
import { Expr } from '../../../core/expr.js';
|
|
2
|
+
import { derivedResourceNodeId } from '../../../core/agent-resource-identity.js';
|
|
3
|
+
import { GUID_RE } from '../../../core/binding-messages.js';
|
|
4
|
+
import { prepareCommand } from '../../../core/cli-spelling.js';
|
|
5
|
+
import { nearestName } from '../../../core/edit-distance.js';
|
|
6
|
+
import { FLAT_SEP, flatNameOf, flatNameSegmentsOf } from '../../../core/expr.js';
|
|
7
|
+
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText, } from '../../../core/form-field-type.js';
|
|
8
|
+
import { ownEntry } from '../../../core/own-entry.js';
|
|
9
|
+
export const AGENT_DIAGNOSTICS = {
|
|
10
|
+
AGENT_CONTEXT_NO_FOLDER_PATH: { severity: 'warning', summary: 'a context index with no folderPath' },
|
|
11
|
+
AGENT_CONTEXT_NO_ID: { severity: 'error', summary: 'a context index whose id is not a GUID' },
|
|
12
|
+
AGENT_CONTEXT_QUERY_NOT_EXPRESSION: { severity: 'error', summary: 'a context index query that is neither a prompt nor an expression' },
|
|
13
|
+
AGENT_MEMORY_NOT_AUTHORABLE: { severity: 'error', summary: 'an inline agent that attaches memory, which the platform does not serve' },
|
|
14
|
+
CALL_NO_NUMBER: { severity: 'error', summary: 'a createOutgoingCall step with no from or to number' },
|
|
15
|
+
CALL_NUMBER_NOT_E164: { severity: 'warning', summary: 'a createOutgoingCall number that is not E.164' },
|
|
16
|
+
CONV_AGENT_CUSTOM_NO_ID: { severity: 'error', summary: "a conversational agent in 'custom' mode with no conversationId" },
|
|
17
|
+
CONV_AGENT_NO_MODEL: { severity: 'error', summary: 'a conversational agent with no model' },
|
|
18
|
+
CONV_AGENT_NO_PROMPT: { severity: 'error', summary: 'a conversational agent with no systemPrompt' },
|
|
19
|
+
CONV_AGENT_NO_TURN: { severity: 'error', summary: 'a conversational agent whose settings bind nothing' },
|
|
20
|
+
CONV_AGENT_SETTINGS_MODE: { severity: 'error', summary: "a conversational agent whose settings mode is neither 'simple' nor 'custom'" },
|
|
21
|
+
GUARDRAIL_ACTION_UNKNOWN: { severity: 'error', summary: 'a guardrail whose action type is unknown' },
|
|
22
|
+
GUARDRAIL_DUP_ID: { severity: 'error', summary: 'two guardrails with one id' },
|
|
23
|
+
GUARDRAIL_INCOMPLETE: { severity: 'error', summary: 'a guardrail missing a required field' },
|
|
24
|
+
GUARDRAIL_NO_RULES: { severity: 'error', summary: 'a custom guardrail with no rules' },
|
|
25
|
+
GUARDRAIL_NO_SCOPE: { severity: 'error', summary: 'a guardrail whose selector names no scope' },
|
|
26
|
+
GUARDRAIL_NO_VALIDATOR: { severity: 'error', summary: 'a built-in validator guardrail with no validatorType' },
|
|
27
|
+
GUARDRAIL_TYPE_UNKNOWN: { severity: 'error', summary: 'a guardrail whose type is unknown' },
|
|
28
|
+
INLINE_AGENT_COMPOSITE_INPUT: { severity: 'error', summary: 'an inline agent input bound to a computed expression' },
|
|
29
|
+
INLINE_AGENT_EMPTY_RETURNS: { severity: 'warning', summary: 'an inline agent with returns: {}' },
|
|
30
|
+
INLINE_AGENT_ESCALATION_APP_KEY_NOT_GUID: { severity: 'error', summary: 'an app escalation whose app key is not a GUID' },
|
|
31
|
+
INLINE_AGENT_ESCALATION_APP_WITH_FIELDS: { severity: 'error', summary: 'an app escalation that also lists quick-form fields' },
|
|
32
|
+
INLINE_AGENT_ESCALATION_FIELD_OPTIONS: { severity: 'error', summary: 'a quick-form escalation field whose options are invalid' },
|
|
33
|
+
INLINE_AGENT_ESCALATION_FIELD_OPTIONS_IGNORED: { severity: 'warning', summary: 'a quick-form escalation field with options its type ignores' },
|
|
34
|
+
INLINE_AGENT_ESCALATION_FIELD_SETTING_INVALID: { severity: 'error', summary: 'a quick-form escalation field with an invalid setting' },
|
|
35
|
+
INLINE_AGENT_ESCALATION_FIELD_TYPE_UNKNOWN: { severity: 'error', summary: 'a quick-form escalation field of an unknown type' },
|
|
36
|
+
INLINE_AGENT_ESCALATION_NO_APP: { severity: 'error', summary: 'an app escalation with no app' },
|
|
37
|
+
INLINE_AGENT_ESCALATION_NO_FIELDS: { severity: 'error', summary: 'a quick-form escalation with no fields' },
|
|
38
|
+
INLINE_AGENT_ESCALATION_NO_NAME: { severity: 'error', summary: 'an escalation with no name' },
|
|
39
|
+
INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP: { severity: 'error', summary: 'a quick-form escalation that also names an app' },
|
|
40
|
+
INLINE_AGENT_ESCALATION_VARIANT: { severity: 'error', summary: 'an escalation whose variant is unknown' },
|
|
41
|
+
INLINE_AGENT_INPUT_NAME_AMBIGUOUS: { severity: 'warning', summary: 'two inline agent inputs that reach the platform under one name' },
|
|
42
|
+
INLINE_AGENT_MEMORY_ID_NOT_GUID: { severity: 'error', summary: 'an agent memory whose id is not a GUID' },
|
|
43
|
+
INLINE_AGENT_MEMORY_INCOMPLETE: { severity: 'error', summary: 'an agent memory missing its name or id' },
|
|
44
|
+
INLINE_AGENT_NO_MODEL: { severity: 'error', summary: 'an inline agent with no model' },
|
|
45
|
+
INLINE_AGENT_NO_SYSTEM_PROMPT: { severity: 'error', summary: 'an inline agent with no systemPrompt' },
|
|
46
|
+
INLINE_AGENT_NO_USER_PROMPT: { severity: 'error', summary: 'an inline agent with no userPrompt' },
|
|
47
|
+
INLINE_AGENT_PROMPT_JSON_TEXT: { severity: 'error', summary: 'an inline agent prompt that asks for JSON text' },
|
|
48
|
+
INLINE_AGENT_READ_NOT_OUTPUT: { severity: 'error', summary: 'an inline agent read outside output' },
|
|
49
|
+
INLINE_AGENT_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'an inline agent read of a field returns does not declare' },
|
|
50
|
+
INLINE_AGENT_RESOURCE_NODE_ID: { severity: 'error', summary: "an agent resource nodeId that is blank or another node's" },
|
|
51
|
+
INLINE_AGENT_RESOURCE_SOURCE_DUP: { severity: 'error', summary: 'two agent resources, or two agents, with one source' },
|
|
52
|
+
INLINE_AGENT_RESOURCE_SOURCE_NOT_GUID: { severity: 'error', summary: 'an agent resource source that is not a uuid' },
|
|
53
|
+
INLINE_AGENT_SCAFFOLD_MODEL: { severity: 'warning', summary: "an inline agent on the platform scaffold's model" },
|
|
54
|
+
INLINE_AGENT_SOURCE_NOT_GUID: { severity: 'error', summary: 'an inline agent source that is not a uuid' },
|
|
55
|
+
INLINE_AGENT_TOOL_A2A_INCOMPLETE: { severity: 'error', summary: 'an a2a tool missing its name, slug or key' },
|
|
56
|
+
INLINE_AGENT_TOOL_CLIENTSIDE_NO_NAME: { severity: 'error', summary: 'a client-side tool with no name' },
|
|
57
|
+
INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION: { severity: 'error', summary: 'a connector tool with no connection or folder' },
|
|
58
|
+
INLINE_AGENT_TOOL_HTTP_METHOD: { severity: 'error', summary: 'an HTTP-request tool with an unknown method' },
|
|
59
|
+
INLINE_AGENT_TOOL_IXP_NO_NAME: { severity: 'error', summary: 'an IxP tool with no name' },
|
|
60
|
+
INLINE_AGENT_TOOL_IXP_PROJECT_NOT_GUID: { severity: 'error', summary: 'an IxP tool whose projectId is not a uuid' },
|
|
61
|
+
INLINE_AGENT_TOOL_KEY_NOT_GUID: { severity: 'error', summary: 'a process tool whose key is not a GUID' },
|
|
62
|
+
INLINE_AGENT_TOOL_MCP_INCOMPLETE: { severity: 'error', summary: 'an MCP tool missing its name or key' },
|
|
63
|
+
INLINE_AGENT_TOOL_NO_OPERATION: { severity: 'error', summary: 'a connector tool with no connector or operation' },
|
|
64
|
+
INLINE_AGENT_TOOL_NO_RESOURCE_ID: { severity: 'error', summary: 'a process tool with no name or folderPath' },
|
|
65
|
+
INLINE_AGENT_TOOL_UNKNOWN_BUILTIN: { severity: 'error', summary: 'a builtin tool that is not one of the three' },
|
|
66
|
+
INLINE_AGENT_TOOL_UNKNOWN_KIND: { severity: 'error', summary: 'a tool of an unknown kind' },
|
|
67
|
+
INLINE_AGENT_UNDECLARED_PLACEHOLDER: { severity: 'error', summary: 'a prompt placeholder naming no declared input' },
|
|
68
|
+
INLINE_AGENT_UNREFERENCED_INPUT: { severity: 'warning', summary: 'an inline agent input no prompt references' },
|
|
69
|
+
VOICE_AGENT_ITERATIONS: { severity: 'error', summary: 'a voice agent whose maxIterations is outside 1 to 8' },
|
|
70
|
+
VOICE_AGENT_NO_PROMPT: { severity: 'error', summary: 'a voice agent with no systemPrompt' },
|
|
71
|
+
VOICE_CALL_CONTEXT_NOT_OBJECT: { severity: 'error', summary: 'a voice step whose callContext reads a field inside the context' },
|
|
72
|
+
VOICE_MODEL_UNKNOWN: { severity: 'error', summary: "a voice agent naming a voice model outside the platform's catalog" },
|
|
73
|
+
VOICE_NO_CALL_CONTEXT: { severity: 'error', summary: 'an endCall or voice agent step with no callContext' },
|
|
74
|
+
VOICE_PERSONA_UNKNOWN: { severity: 'error', summary: "a voice agent persona that is not one of its model's voices" },
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* A conversational AGENT step (Phase 5).
|
|
78
|
+
*
|
|
79
|
+
* The turn wiring is the whole risk: `'simple'` mode reads everything out of
|
|
80
|
+
* ONE conversation-context binding, so a flow that names neither the context
|
|
81
|
+
* nor the individual fields deploys an agent with no transcript to answer.
|
|
82
|
+
*/
|
|
83
|
+
export function checkConversationalAgent(inputs, ctx) {
|
|
84
|
+
const step = ctx.step;
|
|
85
|
+
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
86
|
+
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
87
|
+
if (blank(inputs?.model)) {
|
|
88
|
+
at('CONV_AGENT_NO_MODEL', `Conversational agent step "${step}" has no \`model\`, which the node's definition requires. `
|
|
89
|
+
+ `\`uip agent model list\` shows what the tenant offers.`, `model: 'gpt-5.4'`);
|
|
90
|
+
}
|
|
91
|
+
if (blank(inputs?.systemPrompt)) {
|
|
92
|
+
at('CONV_AGENT_NO_PROMPT', `Conversational agent step "${step}" has no \`systemPrompt\` — the agent has no instructions for how `
|
|
93
|
+
+ `to answer the person.`, `systemPrompt: 'You are a support agent. Be brief.'`);
|
|
94
|
+
}
|
|
95
|
+
const st = inputs?.settings;
|
|
96
|
+
const mode = st?.mode ?? 'simple';
|
|
97
|
+
if (mode !== 'simple' && mode !== 'custom') {
|
|
98
|
+
at('CONV_AGENT_SETTINGS_MODE', `Conversational agent step "${step}"'s settings mode is ${JSON.stringify(mode)}. Valid: 'simple' (one `
|
|
99
|
+
+ `conversation-context binding) or 'custom' (bind each field yourself).`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const named = ['context', 'conversationId', 'exchangeId', 'messages', 'userSettings']
|
|
103
|
+
.filter((k) => st !== undefined && !blank(st[k]));
|
|
104
|
+
if (named.length === 0) {
|
|
105
|
+
at('CONV_AGENT_NO_TURN', `Conversational agent step "${step}" binds nothing in \`settings\` — it would run with no transcript and `
|
|
106
|
+
+ `no conversation to answer into. In 'simple' mode bind the whole context from a wait or context step.`, `settings: { context: out('<waitStep>', 'conversationContext') }`);
|
|
107
|
+
}
|
|
108
|
+
else if (mode === 'custom' && blank(st?.conversationId)) {
|
|
109
|
+
at('CONV_AGENT_CUSTOM_NO_ID', `Conversational agent step "${step}" binds its turn field-by-field ('custom' mode) but names no `
|
|
110
|
+
+ `\`conversationId\` — the one field the platform's own editor marks required, because the reply has `
|
|
111
|
+
+ `nowhere to go without it.`, `settings: { mode: 'custom', conversationId: out('start', 'conversationId'), … }`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A VOICE step (Phase 5D): create-outgoing-call, end-call, or the voice agent.
|
|
116
|
+
*
|
|
117
|
+
* Two things are decidable offline and expensive later: a call needs both
|
|
118
|
+
* numbers in a form the telephony provider accepts, and every voice step needs
|
|
119
|
+
* the `callContext` OBJECT — binding a bare conversation id instead is the
|
|
120
|
+
* classic mistake, because the field is typed `object` and a string passes the
|
|
121
|
+
* file's schema while failing at dispatch.
|
|
122
|
+
*/
|
|
123
|
+
export function checkVoice(kind, inputs, ctx) {
|
|
124
|
+
const step = ctx.step;
|
|
125
|
+
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
126
|
+
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
127
|
+
if (kind === 'createOutgoingCall') {
|
|
128
|
+
for (const field of ['from', 'to']) {
|
|
129
|
+
const raw = inputs?.[field];
|
|
130
|
+
if (blank(raw)) {
|
|
131
|
+
at('CALL_NO_NUMBER', `createOutgoingCall step "${step}" has no \`${field}\`, which the node's schema requires — `
|
|
132
|
+
+ `${field === 'from' ? 'the provisioned number the call comes from' : 'the number to dial'}.`, `${field}: '+15551234567'`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// A literal number is checkable; an expression is the caller's business.
|
|
136
|
+
if (typeof raw === 'string' && !/^\+[1-9]\d{6,14}$/.test(raw.trim())) {
|
|
137
|
+
at('CALL_NUMBER_NOT_E164', `createOutgoingCall step "${step}"'s \`${field}\` is ${JSON.stringify(raw)}, which is not `
|
|
138
|
+
+ `E.164 (a leading '+' and 7–15 digits). Telephony providers reject anything else at dispatch, `
|
|
139
|
+
+ `so a run fails after the flow is deployed.`, `${field}: '+15551234567'`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// end-call and the voice agent are both keyed by the live call.
|
|
145
|
+
if (blank(inputs?.callContext)) {
|
|
146
|
+
at('VOICE_NO_CALL_CONTEXT', `${kind === 'endCall' ? 'endCall' : 'voiceAgent'} step "${step}" has no \`callContext\`, which the node's `
|
|
147
|
+
+ `schema requires — it identifies the live call. The incoming-call trigger publishes it as `
|
|
148
|
+
+ `out('start', 'callContext'), and createOutgoingCall as out('<dialStep>', 'callContext').`, `callContext: out('start', 'callContext')`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const cc = inputs.callContext;
|
|
152
|
+
const js = cc instanceof Expr && !cc.literal ? cc.js : undefined;
|
|
153
|
+
// The field is the call-context OBJECT. A read that ends at a scalar inside
|
|
154
|
+
// it (`…callContext.conversationId`, or a bare conversation id) passes the
|
|
155
|
+
// file's schema and then fails at dispatch.
|
|
156
|
+
if (js !== undefined && /\.(conversationId|id)\s*$/.test(js)) {
|
|
157
|
+
at('VOICE_CALL_CONTEXT_NOT_OBJECT', `${kind === 'endCall' ? 'endCall' : 'voiceAgent'} step "${step}"'s \`callContext\` reads `
|
|
158
|
+
+ `"${js.trim()}", which is a field INSIDE the call context rather than the context object itself. `
|
|
159
|
+
+ `The node's schema types it \`object\`; a scalar passes the file's schema and fails at dispatch.`, `callContext: out('start', 'callContext')`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (kind === 'voiceAgent') {
|
|
163
|
+
if (blank(inputs?.systemPrompt)) {
|
|
164
|
+
at('VOICE_AGENT_NO_PROMPT', `voiceAgent step "${step}" has no \`systemPrompt\` — a caller is waiting, and the agent has no `
|
|
165
|
+
+ `instructions for what to say.`, `systemPrompt: 'Greet the caller and find out why they called.'`);
|
|
166
|
+
}
|
|
167
|
+
const mi = inputs?.maxIterations;
|
|
168
|
+
if (mi !== undefined && !(mi instanceof Expr)) {
|
|
169
|
+
const n = Number(mi);
|
|
170
|
+
if (!Number.isInteger(n) || n < 1 || n > 8) {
|
|
171
|
+
at('VOICE_AGENT_ITERATIONS', `voiceAgent step "${step}"'s \`maxIterations\` is ${JSON.stringify(mi)}. The node's schema allows `
|
|
172
|
+
+ `1 to 8 — a tighter ceiling than a text agent's, because the caller is waiting on the turn.`, `maxIterations: 4`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const v = inputs?.voice;
|
|
176
|
+
if (v !== undefined) {
|
|
177
|
+
const PERSONAS = {
|
|
178
|
+
'gemini-3.1-flash-live-preview': ['Aoede', 'Charon', 'Fenrir', 'Kore', 'Leda', 'Orus', 'Puck', 'Zephyr'],
|
|
179
|
+
'gpt-realtime-2': ['alloy', 'ash', 'ballad', 'coral', 'echo', 'sage', 'shimmer', 'verse'],
|
|
180
|
+
};
|
|
181
|
+
const model = typeof v.model === 'string' ? v.model : undefined;
|
|
182
|
+
// An own-key read: `in` also matched 'toString', and `PERSONAS[model].includes` then threw.
|
|
183
|
+
const personas = model !== undefined ? ownEntry(PERSONAS, model) : undefined;
|
|
184
|
+
if (model !== undefined && personas === undefined) {
|
|
185
|
+
at('VOICE_MODEL_UNKNOWN', `voiceAgent step "${step}" names the voice model ${JSON.stringify(model)}. The platform's catalog `
|
|
186
|
+
+ `today is ${Object.keys(PERSONAS).map((m) => `'${m}'`).join(' and ')} — a voice model, not one of `
|
|
187
|
+
+ `the text models an inline agent takes.`);
|
|
188
|
+
}
|
|
189
|
+
else if (typeof v.persona === 'string' && personas !== undefined && !personas.includes(v.persona)) {
|
|
190
|
+
at('VOICE_PERSONA_UNKNOWN', `voiceAgent step "${step}"'s persona ${JSON.stringify(v.persona)} is not one of `
|
|
191
|
+
+ `${JSON.stringify(model)}'s voices (${personas.join(', ')}). A persona belongs to its model.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ─── an inline agent's prompt asking for JSON text ───────────────────────────────
|
|
197
|
+
//
|
|
198
|
+
// The phrase table behind `INLINE_AGENT_PROMPT_JSON_TEXT`. It is kept narrow on
|
|
199
|
+
// purpose, because the rule is an error. Three shapes count, and each skips a
|
|
200
|
+
// negation anywhere earlier in the same clause ("Do not return JSON", "Do not wrap
|
|
201
|
+
// the answer in JSON", "Return the items, not as JSON"):
|
|
202
|
+
//
|
|
203
|
+
// - DIRECT: an output verb, then only words from a closed qualifier list, then
|
|
204
|
+
// `json` — "Return ONLY a JSON object", "Return only the typed JSON contract".
|
|
205
|
+
// - VIA: an output verb, up to two words, then `as` / `in` / `with`, qualifiers,
|
|
206
|
+
// `json` — "Format your answer as JSON", "Respond with valid JSON".
|
|
207
|
+
// - BRACE: a quoted-key brace whose key is one of the step's own `returns` keys —
|
|
208
|
+
// `Return ONLY {"category","priority"}` next to `returns: { category, priority }`.
|
|
209
|
+
//
|
|
210
|
+
// A word outside the qualifier list ends a DIRECT match, so JSON as DATA does not
|
|
211
|
+
// count in the DIRECT form: "Answer the question using the JSON data below", "Give
|
|
212
|
+
// a summary of the JSON ticket", "Return the invoice number from the JSON". The VIA
|
|
213
|
+
// form allows two free words, so it can still fire on JSON as a topic ("Answer in
|
|
214
|
+
// Spanish with JSON examples"); the tail skips the common data nouns after `json`
|
|
215
|
+
// (keys, data, record, examples, input) and the JSON-family FORMATS a string field
|
|
216
|
+
// legitimately holds (JSON Schema, JSON Patch, JSON-LD, JSON Lines, …). `write` is
|
|
217
|
+
// not a verb here: "You help users write JSON" is a topic, not an answer shape.
|
|
218
|
+
//
|
|
219
|
+
// The skills repo's preview-doc gate (tests/tasks/uipath-maestro-flow/_shared/
|
|
220
|
+
// test_preview_inline_agent_prompts.py) ports this table to Python and loads a
|
|
221
|
+
// byte-identical copy of tests/fixtures/inline-agent-prompt-vectors.json, so
|
|
222
|
+
// change the table, the fixture and the port together.
|
|
223
|
+
const JSON_TEXT_VERBS = 'return|respond|reply|output|answer|produce|emit|format|give|provide';
|
|
224
|
+
/** Words that are only ever qualifiers of the answer's shape, never the thing it is about. */
|
|
225
|
+
const JSON_TEXT_QUALIFIERS = [
|
|
226
|
+
'only', 'just', 'exactly', 'strictly', 'solely', 'simply', 'a', 'an', 'one', 'single', 'the',
|
|
227
|
+
'your', 'its', 'this', 'that', 'valid', 'raw', 'pure', 'plain', 'strict', 'compact', 'minified',
|
|
228
|
+
'well-formed', 'properly', 'formatted', 'typed', 'declared', 'structured', 'following', 'same',
|
|
229
|
+
'final', 'back', 'proper', 'correct', 'machine-readable', 'parseable', 'parsable',
|
|
230
|
+
];
|
|
231
|
+
/** A negation anywhere earlier in the same clause, or anywhere in the match's gap. */
|
|
232
|
+
const JSON_TEXT_NEGATIONS = new Set(['not', 'never', 'no', 'dont', 'without', 'rather', 'instead', 'avoid']);
|
|
233
|
+
const isJsonTextNegation = (w) => JSON_TEXT_NEGATIONS.has(w) || w.endsWith("n't") || w.endsWith('n’t');
|
|
234
|
+
const JSON_TEXT_WORD = "[A-Za-z][\\w'’-]*";
|
|
235
|
+
/** Words are separated by spaces, tabs or a comma — never a newline or a full stop. */
|
|
236
|
+
const JSON_TEXT_SEP = '[ \\t]*,?[ \\t]+';
|
|
237
|
+
const JSON_TEXT_QUAL = `(?:${[...JSON_TEXT_QUALIFIERS].sort((a, b) => b.length - a.length).join('|')})\\b`;
|
|
238
|
+
// `(?<![-\w])` skips `--output json`; `(?!\.\w)` skips `JSON.parse` / `JSON.stringify`;
|
|
239
|
+
// `(?!-(?:ld|rpc)\b)` skips JSON-LD / JSON-RPC (`\b` matches before the hyphen);
|
|
240
|
+
// the last lookahead skips "the JSON below" / "the JSON provided" / "the JSON you
|
|
241
|
+
// get", data nouns ("the JSON keys", "JSON examples") and JSON-family formats
|
|
242
|
+
// ("JSON Schema", "JSON Patch", "the JSON path", "a JSON Web Token").
|
|
243
|
+
const JSON_TEXT_TAIL = `json\\b(?!\\.\\w)(?!-(?:ld|rpc)\\b)(?![ \\t]+(?:below|above|provided|given|you`
|
|
244
|
+
+ `|keys?|data|records?|examples?|inputs?`
|
|
245
|
+
+ `|schemas?|patch|path|pointer|lines|rpc|web[ \\t]+tokens?)\\b)`;
|
|
246
|
+
const JSON_TEXT_VERB = `(?<![-\\w])(?:${JSON_TEXT_VERBS})\\b`;
|
|
247
|
+
const JSON_TEXT_DIRECT = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,5})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
|
|
248
|
+
const JSON_TEXT_VIA = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_WORD}){0,2}?)${JSON_TEXT_SEP}(?:as|in|with)`
|
|
249
|
+
+ `((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,4})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
|
|
250
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
251
|
+
/**
|
|
252
|
+
* The phrase in `text` that tells a model to answer in JSON TEXT, or `undefined`.
|
|
253
|
+
*
|
|
254
|
+
* `check` uses it for `INLINE_AGENT_PROMPT_JSON_TEXT`, passing the step's `returns`
|
|
255
|
+
* keys so the brace form only counts a brace that spells one of them. Without
|
|
256
|
+
* `returnKeys` (the example and doc sweeps) any quoted identifier key counts.
|
|
257
|
+
* `{{input.*}}` placeholders are removed first, so an input named `json` never
|
|
258
|
+
* matches.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* jsonTextDirective('Return ONLY a JSON object with keys "subject" and "body".'); // 'Return ONLY a JSON'
|
|
263
|
+
* jsonTextDirective('Return ONLY {"category","priority"}.', ['category']); // '{"category",'
|
|
264
|
+
* jsonTextDirective('Return a result conforming to the output schema.'); // undefined
|
|
265
|
+
* jsonTextDirective('Do not return JSON.'); // undefined
|
|
266
|
+
* jsonTextDirective('Answer the question using the JSON data below.'); // undefined
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
export function jsonTextDirective(text, returnKeys) {
|
|
270
|
+
if (typeof text !== 'string' || text === '')
|
|
271
|
+
return undefined;
|
|
272
|
+
// A placeholder becomes a hard break (`·` is neither a word nor a gap), and a
|
|
273
|
+
// quoted or back-ticked `JSON` is read as the bare word.
|
|
274
|
+
const t = text
|
|
275
|
+
.replace(/\{\{\s*input\.[^}]*\}\}/g, ' · ')
|
|
276
|
+
.replace(/[`"'“”](json)[`"'“”]/gi, ' $1 ');
|
|
277
|
+
const words = (s) => (s.match(new RegExp(JSON_TEXT_WORD, 'g')) ?? []).map((w) => w.toLowerCase());
|
|
278
|
+
const keys = returnKeys?.length ? returnKeys.map(escapeRegExp).join('|') : '[A-Za-z_]\\w*';
|
|
279
|
+
const brace = new RegExp(`\\{[ \\t]*["'](?:${keys})["'][ \\t]*[,:}]`, 'g');
|
|
280
|
+
for (const rule of [JSON_TEXT_DIRECT, JSON_TEXT_VIA, brace]) {
|
|
281
|
+
rule.lastIndex = 0;
|
|
282
|
+
for (const m of t.matchAll(rule)) {
|
|
283
|
+
if (m.slice(1).some((g) => words(g ?? '').some(isJsonTextNegation)))
|
|
284
|
+
continue;
|
|
285
|
+
// Any negation earlier in the same clause: "Do not wrap the answer in JSON",
|
|
286
|
+
// "No need to return JSON". A comma ends a clause, so "Do not guess, return
|
|
287
|
+
// ONLY a JSON object" still counts.
|
|
288
|
+
const clause = t.slice(0, m.index).split(/[.;:!?\n,]/).pop() ?? '';
|
|
289
|
+
if (words(clause).some(isJsonTextNegation))
|
|
290
|
+
continue;
|
|
291
|
+
return m[0].trim();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Check an `inlineAgent(...)` call.
|
|
298
|
+
*
|
|
299
|
+
* The interesting difference from every other node this SDK authors: **nothing here
|
|
300
|
+
* lives on a tenant.** An inline agent's whole contract is in the call, so these
|
|
301
|
+
* rules can be certain rather than best-effort — the definition's own `required`
|
|
302
|
+
* list is checkable, and so is whether the prompts and the declared variables agree.
|
|
303
|
+
*
|
|
304
|
+
* Three rules exist because the PLATFORM does something with what you wrote:
|
|
305
|
+
*
|
|
306
|
+
* - `INLINE_AGENT_SCAFFOLD_MODEL` — the model the platform's own scaffold ships is
|
|
307
|
+
* stale, and v1's smoke check for this node type fails a flow that keeps it. A
|
|
308
|
+
* warning, not an error: it is a real model id, it just should not be a default
|
|
309
|
+
* nobody chose.
|
|
310
|
+
* - `INLINE_AGENT_UNREFERENCED_INPUT` — flow-v1 derives the deployed input list by
|
|
311
|
+
* scanning the PROMPTS for `$vars.*` references and only falls back to the
|
|
312
|
+
* authored `agentInputVariables` when that pre-pass did not run. So a declared
|
|
313
|
+
* input the prompts never mention is pruned deployed while arriving locally: a
|
|
314
|
+
* disagreement between rungs and the platform, which is the one class that has to
|
|
315
|
+
* be said out loud rather than left to a run.
|
|
316
|
+
* - `INLINE_AGENT_PROMPT_JSON_TEXT` — the platform returns `returns` as a typed
|
|
317
|
+
* object: it becomes the `agent.json` `outputSchema`, and the agent runtime ends
|
|
318
|
+
* a run with a structured call that fills exactly those fields. A prompt that
|
|
319
|
+
* ALSO asks for JSON text makes the model pack its whole answer, serialized, into
|
|
320
|
+
* one string field. See {@link jsonTextDirective}.
|
|
321
|
+
*/
|
|
322
|
+
export function checkInlineAgent(inputs, ctx) {
|
|
323
|
+
const step = ctx.step;
|
|
324
|
+
const at = (code, message, suggestion) => ctx.report(code, message, suggestion);
|
|
325
|
+
const warn = (code, message) => ctx.report(code, message);
|
|
326
|
+
const blank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
327
|
+
// `memory` is authorable in this SDK and NOT authorable on the platform. The
|
|
328
|
+
// bundled `uipath.agent.autonomous@1.4` carries a `memory` handle added by
|
|
329
|
+
// #431; the tenant's own manifest for that node type declares only
|
|
330
|
+
// `escalation, context, tool, success, error`, and the registry index (4977
|
|
331
|
+
// entries, 2026-09-09) contains NO `uipath.agent.resource.memory.*` type at
|
|
332
|
+
// all — so both halves of what `memory` emits are unknown to the platform.
|
|
333
|
+
// `context` is not the alternative: those resources are knowledge INDEXES
|
|
334
|
+
// (`…context.index.<slug>.<guid>`), i.e. context grounding, not episodic memory.
|
|
335
|
+
//
|
|
336
|
+
// Compile succeeds and `uip maestro flow validate` then hard-refuses the edge:
|
|
337
|
+
//
|
|
338
|
+
// [edges[edge_0].sourcePort] Edge references source handle "memory" on node
|
|
339
|
+
// "triage", but the current manifest for "uipath.agent.autonomous" does not
|
|
340
|
+
// declare that handle … Retry: RetryWillNotFix
|
|
341
|
+
//
|
|
342
|
+
// An artifact that compiles and can never validate is the worst of the two
|
|
343
|
+
// failures, so this is said here — before the emit — rather than left to a rung.
|
|
344
|
+
// It is an ERROR because there is no spelling that works today, and `--no-check`
|
|
345
|
+
// remains the escape for a tenant whose manifest does declare the handle.
|
|
346
|
+
if (inputs?.memory !== undefined) {
|
|
347
|
+
at('AGENT_MEMORY_NOT_AUTHORABLE', `Inline agent step "${step}" attaches \`memory\`, which the platform cannot resolve: the tenant's `
|
|
348
|
+
+ `\`uipath.agent.autonomous\` manifest declares no \`memory\` handle (only escalation, context, tool, `
|
|
349
|
+
+ `success, error) and the registry serves no \`uipath.agent.resource.memory.*\` node type. The flow `
|
|
350
|
+
+ `compiles and then fails \`uip maestro flow validate\` as RetryWillNotFix. Drop \`memory\`; for `
|
|
351
|
+
+ `retrieval over a knowledge source use a context index instead (\`tools\`/context grounding). `
|
|
352
|
+
+ `Pass --no-check only on a tenant whose manifest does declare the handle.`);
|
|
353
|
+
}
|
|
354
|
+
// A context `query` is what the RUNTIME searches with, and it is not an
|
|
355
|
+
// expression slot. A plain string is the model-invoked `dynamic` variant: the
|
|
356
|
+
// text becomes the description the model reads before it writes its own query.
|
|
357
|
+
// An expression renders as the `static` variant instead, whose `value` the
|
|
358
|
+
// retriever is handed VERBATIM — there is no `$vars` scope inside the agent and
|
|
359
|
+
// nothing templates it once `uip agent refresh` has stripped the node, so the
|
|
360
|
+
// search runs on the literal text `=js:$vars…`. With the default `threshold: 0`
|
|
361
|
+
// it still returns chunks, so it reads as working while retrieving on nonsense.
|
|
362
|
+
// Refused rather than emitted until the argument-bound form is implemented
|
|
363
|
+
// (`{ variant: 'argument' }`, which the runtime resolves from agent state).
|
|
364
|
+
const contexts = inputs?.context === undefined ? []
|
|
365
|
+
: Array.isArray(inputs.context) ? inputs.context : [inputs.context];
|
|
366
|
+
for (const cx of contexts) {
|
|
367
|
+
if (cx?.query !== undefined && typeof cx.query !== 'string') {
|
|
368
|
+
at('AGENT_CONTEXT_QUERY_NOT_EXPRESSION', `Inline agent step "${step}" gives its context index "${cx.name ?? ''}" an EXPRESSION \`query\`. `
|
|
369
|
+
+ `The runtime searches with that value literally — it is not evaluated inside the agent — so `
|
|
370
|
+
+ `retrieval runs on the text of the expression and quietly returns unrelated chunks. Use a plain `
|
|
371
|
+
+ `string: it becomes the guidance the model reads before writing its own query, which is what a `
|
|
372
|
+
+ `run-dependent search needs.`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// The index id is half of the resource node's TYPE
|
|
376
|
+
// (`…context.index.<slug>.<id>`), and the platform resolves the index by it.
|
|
377
|
+
// A blank, padded or non-GUID id builds a type no tenant serves, and nothing
|
|
378
|
+
// downstream says so: the flow compiles, merges and validates, and the agent
|
|
379
|
+
// runs ungrounded. So it is an error, not a warning. The id is tested as
|
|
380
|
+
// written, not trimmed, because serialize emits it as written.
|
|
381
|
+
for (const cx of contexts) {
|
|
382
|
+
const id = typeof cx?.id === 'string' ? cx.id : '';
|
|
383
|
+
if (!GUID_RE.test(id)) {
|
|
384
|
+
at('AGENT_CONTEXT_NO_ID', `Inline agent step "${step}" grounds on context index "${cx?.name ?? ''}" with `
|
|
385
|
+
+ (id.trim() === '' ? 'no `id`'
|
|
386
|
+
: GUID_RE.test(id.trim()) ? `\`id\` ${JSON.stringify(cx?.id)}, which has whitespace around the GUID`
|
|
387
|
+
: `\`id\` ${JSON.stringify(cx?.id)}, which is not a GUID`) + '. '
|
|
388
|
+
+ `The id is the index's key: it is the tail of the resource node's type and how the runtime finds `
|
|
389
|
+
+ `the index, so without a real one the agent runs ungrounded and \`validate\` does not notice. `
|
|
390
|
+
+ `\`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// An index outside the deployment folder needs its folder named. The resource
|
|
394
|
+
// file carries no `indexId`, so `indexName` + `folderPath` is the whole locator,
|
|
395
|
+
// and `@uipath/tool-agent`'s `createIndexBinding` leaves the folder off the
|
|
396
|
+
// binding when `folderPath` is empty. A WARNING, not an error: an index in the
|
|
397
|
+
// deployment folder resolves without it.
|
|
398
|
+
for (const cx of contexts) {
|
|
399
|
+
if (cx != null && cx.folderPath === undefined) {
|
|
400
|
+
warn('AGENT_CONTEXT_NO_FOLDER_PATH', `Inline agent step "${step}" grounds on context index "${cx.name ?? ''}" without a \`folderPath\`. `
|
|
401
|
+
+ `The emitted resource carries no index id, so the folder is half of how the runtime finds the `
|
|
402
|
+
+ `index, and the binding is written without one. Give the index's folder — `
|
|
403
|
+
+ `\`uip solution resources list --kind Index --source remote --search "<name>"\` returns it.`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// The definition's own `required`: systemPrompt, userPrompt, model — each with
|
|
407
|
+
// `minLength: 1`, so a blank string is as invalid as a missing key.
|
|
408
|
+
if (blank(inputs?.model)) {
|
|
409
|
+
at('INLINE_AGENT_NO_MODEL', `Inline agent step "${step}" has no \`model\`. The node's definition requires one (\`minLength: 1\`), so `
|
|
410
|
+
+ `a flow without it does not validate. \`uip agent model list\` shows what the tenant offers.`, `model: 'gpt-5.4'`);
|
|
411
|
+
}
|
|
412
|
+
if (blank(inputs?.systemPrompt)) {
|
|
413
|
+
at('INLINE_AGENT_NO_SYSTEM_PROMPT', `Inline agent step "${step}" has no \`systemPrompt\`. The node's definition requires one `
|
|
414
|
+
+ `(\`minLength: 1\`). It is where the agent is told what it is and what shape to answer in.`);
|
|
415
|
+
}
|
|
416
|
+
if (blank(inputs?.userPrompt)) {
|
|
417
|
+
at('INLINE_AGENT_NO_USER_PROMPT', `Inline agent step "${step}" has no \`userPrompt\`. The node's definition requires one `
|
|
418
|
+
+ `(\`minLength: 1\`). It is the per-run request.`);
|
|
419
|
+
}
|
|
420
|
+
const model = typeof inputs?.model === 'string' ? inputs.model.trim() : '';
|
|
421
|
+
if (model === 'gpt-4o-2024-11-20') {
|
|
422
|
+
warn('INLINE_AGENT_SCAFFOLD_MODEL', `Inline agent step "${step}" uses \`gpt-4o-2024-11-20\` — the model the platform's own scaffold ships, `
|
|
423
|
+
+ `and the one v1's smoke check for this node type FAILS a flow for keeping (it is the definition's `
|
|
424
|
+
+ `\`inputDefaults.model\`, not a choice). Pick a current model: \`uip agent model list\`.`);
|
|
425
|
+
}
|
|
426
|
+
// Where each resource node sits (`nodeId`, `source`): decompile writes them
|
|
427
|
+
// for a node the canvas placed, and compile writes them as given. A blank or
|
|
428
|
+
// repeated id would make compile refuse the flow or overwrite another node,
|
|
429
|
+
// and a `source` that is not a uuid names a directory the platform does not
|
|
430
|
+
// expect (the agent's own `source` has the same rule).
|
|
431
|
+
const asList = (v) => (v === undefined ? [] : Array.isArray(v) ? v : [v]);
|
|
432
|
+
const placed = [
|
|
433
|
+
...asList(inputs?.context).map((r) => ({ what: `context index "${r?.name ?? ''}"`, kind: 'Context', nodeId: r?.nodeId, source: r?.source })),
|
|
434
|
+
...asList(inputs?.tools).map((r) => ({ what: `${r?.kind ?? ''} tool`, kind: 'Tool', nodeId: r?.nodeId, source: r?.source })),
|
|
435
|
+
...asList(inputs?.escalation).map((r) => ({ what: `escalation "${r?.name ?? ''}"`, kind: 'Escalation', nodeId: r?.nodeId, source: r?.source })),
|
|
436
|
+
];
|
|
437
|
+
// The ids compile derives for the resources with no `nodeId`: the n-th one of
|
|
438
|
+
// a kind gets `<step><Kind>`, then `<step><Kind>2`, ... An explicit id equal to
|
|
439
|
+
// one of them makes compile refuse the flow, or rename the derived node.
|
|
440
|
+
for (const kind of ['Context', 'Tool', 'Escalation']) {
|
|
441
|
+
const derived = placed.filter((r) => r.kind === kind && r.nodeId === undefined);
|
|
442
|
+
derived.forEach((r, n) => {
|
|
443
|
+
const id = derivedResourceNodeId(step, kind, n);
|
|
444
|
+
ctx.resources.derivedNodeId(id, `the id compile derives for the ${r.what} of inline agent step "${step}"`);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
/** Explicit `source` -> the resource on THIS agent that has it: two would write one resource.json. */
|
|
448
|
+
const sources = new Map();
|
|
449
|
+
for (const r of placed) {
|
|
450
|
+
if (r.nodeId !== undefined) {
|
|
451
|
+
const id = typeof r.nodeId === 'string' ? r.nodeId : '';
|
|
452
|
+
let other;
|
|
453
|
+
if (id.trim() === '') {
|
|
454
|
+
at('INLINE_AGENT_RESOURCE_NODE_ID', `Inline agent step "${step}" gives its ${r.what} the \`nodeId\` ${JSON.stringify(r.nodeId)}, which is `
|
|
455
|
+
+ `not a node id. Omit \`nodeId\` to derive one, or give the id the node has in the flow.`);
|
|
456
|
+
}
|
|
457
|
+
else if ((other = ctx.resources.claimNodeId(id, r.what)) !== undefined) {
|
|
458
|
+
at('INLINE_AGENT_RESOURCE_NODE_ID', `Inline agent step "${step}" gives its ${r.what} the \`nodeId\` "${id}", which the ${other.what} `
|
|
459
|
+
+ `(on step "${other.step}") already has. Node ids are unique in a flow.`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (r.source === undefined)
|
|
463
|
+
continue;
|
|
464
|
+
// Not trimmed: compile writes `source` as given, into `inputs.source` and the directory name.
|
|
465
|
+
if (!GUID_RE.test(String(r.source))) {
|
|
466
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_NOT_GUID', `Inline agent step "${step}" gives its ${r.what} the \`source\` ${JSON.stringify(r.source)}, which is not `
|
|
467
|
+
+ `a uuid${/^\s|\s$/.test(String(r.source)) ? ' (it has spaces around it)' : ''}. \`source\` is the resource's `
|
|
468
|
+
+ `directory under the agent's own (\`resources/<source>/resource.json\`) and the node's \`inputs.source\`. `
|
|
469
|
+
+ 'Omit it and one is derived from the flow id, the step and the resource.');
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const key = String(r.source).toLowerCase();
|
|
473
|
+
const first = sources.get(key);
|
|
474
|
+
if (first !== undefined) {
|
|
475
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_DUP', `Inline agent step "${step}" gives its ${first} and its ${r.what} the same \`source\` "${String(r.source)}". `
|
|
476
|
+
+ 'Both would write `resources/<source>/resource.json` under this agent, and the second file would replace '
|
|
477
|
+
+ 'the first. Give each resource its own `source`, or omit `source` to derive one.');
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
sources.set(key, r.what);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (typeof inputs?.source === 'string' && GUID_RE.test(inputs.source.trim())) {
|
|
484
|
+
const other = ctx.resources.claimFolder(inputs.source.trim());
|
|
485
|
+
if (other !== undefined) {
|
|
486
|
+
at('INLINE_AGENT_RESOURCE_SOURCE_DUP', `Inline agent steps "${other}" and "${step}" have the same \`source\` "${inputs.source}". Both would write `
|
|
487
|
+
+ '`<source>/agent.json`, and the second file would replace the first. Give each agent its own `source`, '
|
|
488
|
+
+ 'or omit it to derive one.');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (inputs?.source !== undefined && !GUID_RE.test(String(inputs.source).trim())) {
|
|
492
|
+
at('INLINE_AGENT_SOURCE_NOT_GUID', `Inline agent step "${step}"'s \`source\` is ${JSON.stringify(inputs.source)}, which is not a uuid. `
|
|
493
|
+
+ `\`source\` is the agent's own DIRECTORY name, and the platform resolves its configuration at `
|
|
494
|
+
+ `"<source>/agent.json" — a non-uuid name is not what the designer or the runtime expect. Omit it and `
|
|
495
|
+
+ `one is derived from the flow id and step name (stable across recompiles).`);
|
|
496
|
+
}
|
|
497
|
+
if (inputs?.returns && Object.keys(inputs.returns).length === 0) {
|
|
498
|
+
warn('INLINE_AGENT_EMPTY_RETURNS', `Inline agent step "${step}" declares \`returns: {}\`. That is not "no output" — it falls back to the `
|
|
499
|
+
+ `definition's default, \`{ content: 'string' }\`, which is the scaffold shape. Declare the fields the `
|
|
500
|
+
+ `agent must return, or drop the key and read \`content\`.`);
|
|
501
|
+
}
|
|
502
|
+
// A prompt that asks for JSON TEXT while `returns` declares the fields. The
|
|
503
|
+
// platform already returns those fields as a typed object — `returns` is the
|
|
504
|
+
// agent.json `outputSchema`, and the runtime ends a run with a structured call
|
|
505
|
+
// that fills exactly those keys — so the extra directive does not shape the
|
|
506
|
+
// answer, it gets serialized INTO it: on 2026-09-23 a writer declaring
|
|
507
|
+
// `{ subject, body }` came back with `body` = '{"subject":…,"body":…}' in 3 of 3
|
|
508
|
+
// debug runs, and the flow mailed the JSON. `uip maestro flow validate` accepts
|
|
509
|
+
// the prompt (it never reads prompt meaning), so this is where it is said. An
|
|
510
|
+
// ERROR for the same reason `INLINE_AGENT_UNDECLARED_PLACEHOLDER` is one: the
|
|
511
|
+
// platform accepts the shape and the run still delivers the wrong value, and a
|
|
512
|
+
// warning is printed past while compile writes the .flow anyway. Only when
|
|
513
|
+
// `returns` is declared: with the `{ content }` default, JSON text in `content`
|
|
514
|
+
// may be exactly what the author reads back.
|
|
515
|
+
const returnKeys = Object.keys(inputs?.returns ?? {});
|
|
516
|
+
if (returnKeys.length) {
|
|
517
|
+
for (const field of ['systemPrompt', 'userPrompt']) {
|
|
518
|
+
const phrase = jsonTextDirective(String(inputs?.[field] ?? ''), returnKeys);
|
|
519
|
+
if (phrase === undefined)
|
|
520
|
+
continue;
|
|
521
|
+
const rewrite = `Return a result conforming to the output schema. `
|
|
522
|
+
+ returnKeys.map((k) => `${k}: <what it holds>.`).join(' ');
|
|
523
|
+
at('INLINE_AGENT_PROMPT_JSON_TEXT', `Inline agent step "${step}"'s \`${field}\` tells the model to answer in JSON text ("${phrase}"), `
|
|
524
|
+
+ `but \`returns\` already declares the answer: ${returnKeys.map((k) => `\`${k}\``).join(', ')}. `
|
|
525
|
+
+ `The platform returns those fields as a typed object — \`returns\` becomes the agent.json `
|
|
526
|
+
+ `\`outputSchema\`, and the agent runtime ends each run with a structured call that fills exactly `
|
|
527
|
+
+ `those keys — so \`out('${step}', '${returnKeys[0]}')\` reads a value, not text to parse. Asking `
|
|
528
|
+
+ `for JSON as well makes the model pack its whole answer, serialized, into one string field (for `
|
|
529
|
+
+ `example a \`body\` that reads '{"subject":…,"body":…}'). Replace only that JSON instruction (keep the rest `
|
|
530
|
+
+ `of the prompt) with one line per field, in the platform's own wording: "${rewrite}" Declare a field \`'object'\` or `
|
|
531
|
+
+ `\`'array'\` if it should carry structure. \`uip maestro flow validate\` accepts this prompt, so `
|
|
532
|
+
+ `nothing later catches it. If this is a false positive (the prompt only mentions JSON as data), `
|
|
533
|
+
+ `compile with --no-check.`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// The prompts-vs-variables agreement, in both directions. Both prompts are
|
|
537
|
+
// searched, because flow-v1's pre-pass scans both.
|
|
538
|
+
const prompts = `${String(inputs?.systemPrompt ?? '')}\n${String(inputs?.userPrompt ?? '')}`;
|
|
539
|
+
const placeholders = [...prompts.matchAll(/\{\{\s*input\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\}/g)]
|
|
540
|
+
.map((m) => m[1]);
|
|
541
|
+
const declaredInputs = Object.keys(inputs?.inputs ?? {});
|
|
542
|
+
// Direction 1: a declared input no prompt references. The platform may prune it,
|
|
543
|
+
// in which case the agent receives nothing and the authored list sits unused.
|
|
544
|
+
const unreferenced = declaredInputs.filter((k) => !placeholders.includes(k));
|
|
545
|
+
if (declaredInputs.length && unreferenced.length === declaredInputs.length) {
|
|
546
|
+
warn('INLINE_AGENT_UNREFERENCED_INPUT', `Inline agent step "${step}" declares input(s) ${unreferenced.map((u) => `\`${u}\``).join(', ')} that `
|
|
547
|
+
+ `neither prompt references as \`{{input.<name>}}\` — so NONE of them is referenced. Deployed, the `
|
|
548
|
+
+ `authored list is not necessarily what the platform sends: flow-v1's `
|
|
549
|
+
+ `\`preDeriveAgentInputDefinitions\` pre-pass builds the input list by scanning the node's prompts for `
|
|
550
|
+
+ `variable references, merges what it finds with the authored list, and DROPS an entry no reference `
|
|
551
|
+
+ `points at when its caller asks for pruning. A referenced input survives every path; an unreferenced `
|
|
552
|
+
+ `one survives only some, and the failure mode is an agent that arrives with no arguments at all. `
|
|
553
|
+
+ `Write \`{{input.${declaredInputs[0]}}}\` in a prompt — which is also how the agent gets told what it `
|
|
554
|
+
+ `is looking at.`);
|
|
555
|
+
}
|
|
556
|
+
else if (unreferenced.length) {
|
|
557
|
+
warn('INLINE_AGENT_UNREFERENCED_INPUT', `Inline agent step "${step}" declares input(s) ${unreferenced.map((u) => `\`${u}\``).join(', ')} that `
|
|
558
|
+
+ `neither prompt references as \`{{input.<name>}}\`. The platform may prune an unreferenced input, so `
|
|
559
|
+
+ `it is not reliably sent — and an input the prompts never mention is one the agent cannot use anyway.`);
|
|
560
|
+
}
|
|
561
|
+
// Direction 2: a placeholder naming an input that does not exist. The compiler
|
|
562
|
+
// leaves it alone rather than deleting it, so it reaches the model as the literal
|
|
563
|
+
// text `{{input.x}}` — visible, but not what anyone meant.
|
|
564
|
+
const undeclared = [...new Set(placeholders.filter((p) => !declaredInputs.includes(p)))];
|
|
565
|
+
if (undeclared.length) {
|
|
566
|
+
at('INLINE_AGENT_UNDECLARED_PLACEHOLDER', `Inline agent step "${step}" references ${undeclared.map((u) => `\`{{input.${u}}}\``).join(', ')} in a `
|
|
567
|
+
+ `prompt, but \`inputs\` declares ${declaredInputs.length ? declaredInputs.map((d) => `\`${d}\``).join(', ') : 'nothing'}. `
|
|
568
|
+
+ `An unmatched placeholder is not substituted — it reaches the model as that literal text, and the `
|
|
569
|
+
+ `value it was meant to carry never arrives.`, declaredInputs.length
|
|
570
|
+
? `inputs: { ${undeclared[0]}: input('${undeclared[0]}'), … }`
|
|
571
|
+
: `inputs: { ${undeclared[0]}: input('${undeclared[0]}') }`);
|
|
572
|
+
}
|
|
573
|
+
// Direction 3: an input bound to a COMPUTED EXPRESSION rather than to one
|
|
574
|
+
// reference. The platform names each of an agent's arguments after the
|
|
575
|
+
// reference path it is bound to (the shell node's `agentInputVariables`), so
|
|
576
|
+
// an expression spanning zero or several references has no single argument —
|
|
577
|
+
// and therefore no name a `{{input.<x>}}` token could address. The compiler
|
|
578
|
+
// declares no argument for it, so its value does not reach the agent, and the
|
|
579
|
+
// placeholder reaches the model as literal text. An error rather than a
|
|
580
|
+
// warning, and for the same reason `INLINE_AGENT_UNDECLARED_PLACEHOLDER` is
|
|
581
|
+
// one: the value never arrives.
|
|
582
|
+
for (const [name, val] of Object.entries(inputs?.inputs ?? {})) {
|
|
583
|
+
if (!(val instanceof Expr) || val.literal)
|
|
584
|
+
continue;
|
|
585
|
+
if (flatNameOf(val.js) !== undefined)
|
|
586
|
+
continue;
|
|
587
|
+
if (!placeholders.includes(name))
|
|
588
|
+
continue;
|
|
589
|
+
at('INLINE_AGENT_COMPOSITE_INPUT', `Inline agent step "${step}"'s input \`${name}\` is bound to a computed expression `
|
|
590
|
+
+ `(\`${val.js.length > 60 ? `${val.js.slice(0, 60)}…` : val.js}\`), and a prompt references it as `
|
|
591
|
+
+ `\`{{input.${name}}}\`. The platform does not send the agent the names you declared: it names each `
|
|
592
|
+
+ `argument after the REFERENCE PATH the input is bound to (\`$vars.a.output.b\` → \`a__output__b\`). `
|
|
593
|
+
+ `A computed expression is not one reference, so it gets no argument: its value does not reach the `
|
|
594
|
+
+ `agent, and the placeholder reaches the model as that literal text.`, `compute it in a script step first, then bind the step's output: `
|
|
595
|
+
+ `.step('${name}Value', script({ code: 'return …;' })) … inputs: { ${name}: out('${name}Value') }`);
|
|
596
|
+
}
|
|
597
|
+
// A step id containing `__` makes the flat name AMBIGUOUS on the platform: the
|
|
598
|
+
// encoding joins path segments with `__` and v1's own inverse (`decodeFlatName`)
|
|
599
|
+
// documents that it round-trips only while ids "disallow `__`". A warning, not
|
|
600
|
+
// an error — the argument is still SENT and our token still matches it, so the
|
|
601
|
+
// agent works; what breaks is anything that decodes the name back to a path
|
|
602
|
+
// (the CLI's `hydrateInlineAgentInputs` reconcile, and the designer).
|
|
603
|
+
for (const [name, val] of Object.entries(inputs?.inputs ?? {})) {
|
|
604
|
+
if (!(val instanceof Expr) || val.literal)
|
|
605
|
+
continue;
|
|
606
|
+
const segments = flatNameSegmentsOf(val.js);
|
|
607
|
+
const offender = segments?.find((s) => s.includes(FLAT_SEP));
|
|
608
|
+
if (!offender)
|
|
609
|
+
continue;
|
|
610
|
+
warn('INLINE_AGENT_INPUT_NAME_AMBIGUOUS', `Inline agent step "${step}"'s input \`${name}\` reads \`${val.js}\`, whose path segment `
|
|
611
|
+
+ `\`${offender}\` contains \`${FLAT_SEP}\`. The platform names the argument by joining the path's `
|
|
612
|
+
+ `segments with \`${FLAT_SEP}\` (\`${segments.join(FLAT_SEP)}\`), so that name no longer says where `
|
|
613
|
+
+ `the value came from — v1's own inverse round-trips only while ids avoid \`${FLAT_SEP}\`. The `
|
|
614
|
+
+ `argument is still sent and the prompt still reads it; what breaks is anything decoding the name `
|
|
615
|
+
+ `back to a path. Rename the step or variable to drop the double underscore.`);
|
|
616
|
+
}
|
|
617
|
+
// GUARDRAILS (Phase 4F). The node's definition types the input only as
|
|
618
|
+
// `array`, so a malformed entry deploys silently and either never trips or
|
|
619
|
+
// blocks every run — both invisible until production. The shapes below are
|
|
620
|
+
// Agent Builder's own discriminators.
|
|
621
|
+
const rails = inputs?.guardrails;
|
|
622
|
+
if (rails !== undefined) {
|
|
623
|
+
const ids = new Set();
|
|
624
|
+
rails.forEach((g, i) => {
|
|
625
|
+
const label = g?.name ? `"${g.name}"` : `guardrails[${i}]`;
|
|
626
|
+
if (!g || (g.$guardrailType !== 'custom' && g.$guardrailType !== 'builtInValidator')) {
|
|
627
|
+
at('GUARDRAIL_TYPE_UNKNOWN', `Inline agent step "${step}"'s ${label} has the $guardrailType `
|
|
628
|
+
+ `${JSON.stringify(g?.$guardrailType)}. Valid: 'custom' `
|
|
629
|
+
+ `(rules you author) or 'builtInValidator' (a platform validator).`);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (blank(g.id) || blank(g.name)) {
|
|
633
|
+
at('GUARDRAIL_INCOMPLETE', `Inline agent step "${step}"'s ${label} is missing ${blank(g.id) ? 'an id' : 'a name'} — `
|
|
634
|
+
+ `both are required by the agent schema (the id keys the rail, the name is what trips show).`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (ids.has(g.id)) {
|
|
638
|
+
at('GUARDRAIL_DUP_ID', `Inline agent step "${step}" declares the guardrail id "${g.id}" twice — the second silently `
|
|
639
|
+
+ `shadows the first in Agent Builder's keyed storage.`);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
ids.add(g.id);
|
|
643
|
+
if (!Array.isArray(g.selector?.scopes) || g.selector.scopes.length === 0) {
|
|
644
|
+
at('GUARDRAIL_NO_SCOPE', `Inline agent step "${step}"'s guardrail "${g.name}" selects no scopes — it can never apply.`, `selector: { scopes: ['Agent'] }`);
|
|
645
|
+
}
|
|
646
|
+
const act = g.action?.$actionType;
|
|
647
|
+
if (act !== 'block' && act !== 'filter' && act !== 'log') {
|
|
648
|
+
at('GUARDRAIL_ACTION_UNKNOWN', `Inline agent step "${step}"'s guardrail "${g.name}" has the action ${JSON.stringify(act)}. `
|
|
649
|
+
+ `Valid: 'block', 'filter', 'log'.`);
|
|
650
|
+
}
|
|
651
|
+
if (g.$guardrailType === 'custom' && (!Array.isArray(g.rules) || g.rules.length === 0)) {
|
|
652
|
+
at('GUARDRAIL_NO_RULES', `Inline agent step "${step}"'s custom guardrail "${g.name}" has no rules — nothing can ever `
|
|
653
|
+
+ `trip it, so the action never fires.`, `rules: [{ $ruleType: 'always', applyTo: 'inputAndOutput' }]`);
|
|
654
|
+
}
|
|
655
|
+
if (g.$guardrailType === 'builtInValidator' && blank(g.validatorType)) {
|
|
656
|
+
at('GUARDRAIL_NO_VALIDATOR', `Inline agent step "${step}"'s guardrail "${g.name}" names no validatorType — the platform `
|
|
657
|
+
+ `cannot resolve which built-in validator to run.`);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// TOOLS and ESCALATION (card G16). Cheap identity checks only: a wrong KIND
|
|
662
|
+
// or a non-uuid KEY emits a node type the tenant has never heard of, which
|
|
663
|
+
// nothing downstream of the compiler can see — the resource is dropped from
|
|
664
|
+
// emitted graph, so no rung ever exercises it. Library resolution for connector
|
|
665
|
+
// tools happens at compile (where the library is), not here.
|
|
666
|
+
const toolRefs = inputs?.tools === undefined ? []
|
|
667
|
+
: Array.isArray(inputs.tools) ? inputs.tools : [inputs.tools];
|
|
668
|
+
const BUILTIN_TOOLS = ['analyzefiles', 'summarize', 'batchtransform'];
|
|
669
|
+
for (const t of toolRefs) {
|
|
670
|
+
const kind = t?.kind;
|
|
671
|
+
if (kind === 'builtin') {
|
|
672
|
+
if (!BUILTIN_TOOLS.includes(String(t.tool))) {
|
|
673
|
+
at('INLINE_AGENT_TOOL_UNKNOWN_BUILTIN', `Inline agent step "${step}" declares a builtin tool ${JSON.stringify(t.tool)}. `
|
|
674
|
+
+ `The platform ships exactly three: ${BUILTIN_TOOLS.map((b) => `'${b}'`).join(', ')} — the node type is a `
|
|
675
|
+
+ `fixed string, so any other name is a node the tenant has never heard of.`, `{ kind: 'builtin', tool: 'summarize' }`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else if (kind === 'connector') {
|
|
679
|
+
const c = t;
|
|
680
|
+
if (blank(c.connector) || blank(c.operation)) {
|
|
681
|
+
at('INLINE_AGENT_TOOL_NO_OPERATION', `Inline agent step "${step}" declares a connector tool without ${blank(c.connector) ? 'a `connector`' : 'an `operation`'}. `
|
|
682
|
+
+ `Both name the curated-library entry the tool is resolved from — the same pair \`connector(…)\` takes.`, `{ kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue' }`);
|
|
683
|
+
}
|
|
684
|
+
// A connector tool calls Integration Service on a CONNECTION, exactly as a
|
|
685
|
+
// connector step does — and before #867 there was no way to give it one, so
|
|
686
|
+
// the tool compiled with none anywhere (no binding, no resource.json row)
|
|
687
|
+
// and could only fail at run time. Both halves, as `registry prepare` writes them.
|
|
688
|
+
const labels = t;
|
|
689
|
+
const missing = ['connection', 'folder'].filter((k) => blank(labels[k]));
|
|
690
|
+
if (missing.length > 0) {
|
|
691
|
+
const key = blank(c.connector) ? '<connector-key>' : String(c.connector);
|
|
692
|
+
const op = blank(c.operation) ? '<operation>' : String(c.operation);
|
|
693
|
+
at('INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION', `Inline agent step "${step}"'s connector tool "${key}/${op}" has no ${missing.map((m) => `\`${m}\``).join(' or ')}. `
|
|
694
|
+
+ `The tool calls Integration Service on a tenant connection: without one the agent's tool has no `
|
|
695
|
+
+ `connection binding, its resource.json carries none, and the solution's Resources never list it. `
|
|
696
|
+
+ `Name the bindings.json labels the same way \`connector()\` takes them — \`${prepareCommand(key, op)}\` `
|
|
697
|
+
+ `finds the connection and writes both entries.`, `{ kind: 'connector', connector: '${key}', operation: '${op}', connection: '<connection label>', folder: '<folder label>' }`);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
ctx.bindingLabels(`Inline agent step "${step}"'s connector tool`, labels, undefined, blank(c.connector) ? undefined : { key: String(c.connector), ...(blank(c.operation) ? {} : { action: String(c.operation) }) });
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else if (kind === 'ixp') {
|
|
704
|
+
const x = t;
|
|
705
|
+
if (blank(x.projectId) || !GUID_RE.test(String(x.projectId).trim())) {
|
|
706
|
+
at('INLINE_AGENT_TOOL_IXP_PROJECT_NOT_GUID', `Inline agent step "${step}"'s IxP tool has \`projectId\` ${JSON.stringify(x.projectId)}, which is not a uuid. `
|
|
707
|
+
+ `It is the node type's tail (uipath.agent.resource.tool.ixp.<slug>.<projectId>), so a wrong one emits a `
|
|
708
|
+
+ `node type the tenant cannot resolve. Find it: uip maestro flow registry pull --force && `
|
|
709
|
+
+ `uip maestro flow registry search -f "type:startsWith=uipath.agent.resource.tool.ixp".`);
|
|
710
|
+
}
|
|
711
|
+
if (blank(x.name)) {
|
|
712
|
+
at('INLINE_AGENT_TOOL_IXP_NO_NAME', `Inline agent step "${step}"'s IxP tool needs a \`name\` — the project's display name, which the definition `
|
|
713
|
+
+ `carries as both its label and \`inputDefaults.name\`.`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (kind === 'process' || kind === 'agent' || kind === 'api' || kind === 'flow'
|
|
717
|
+
|| kind === 'maestro' || kind === 'function') {
|
|
718
|
+
const p = t;
|
|
719
|
+
if (blank(p.key) || !GUID_RE.test(String(p.key).trim())) {
|
|
720
|
+
at('INLINE_AGENT_TOOL_KEY_NOT_GUID', `Inline agent step "${step}"'s '${String(kind)}' tool has \`key\` ${JSON.stringify(p.key)}, which is not a uuid. `
|
|
721
|
+
+ `The key IS the node type's tail (uipath.agent.resource.tool.…<key>), so a wrong one emits a node type `
|
|
722
|
+
+ `the tenant cannot resolve. Find it: uip maestro flow registry search "uipath.agent.resource.tool".`);
|
|
723
|
+
}
|
|
724
|
+
if (blank(p.name) || blank(p.folderPath)) {
|
|
725
|
+
at('INLINE_AGENT_TOOL_NO_RESOURCE_ID', `Inline agent step "${step}"'s '${String(kind)}' tool needs both \`name\` and \`folderPath\` — they form the `
|
|
726
|
+
+ `binding's resourceKey ("<folderPath>.<name>"), which is how the deployed flow finds the resource.`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else if (kind === 'mcp') {
|
|
730
|
+
const m = t;
|
|
731
|
+
if (blank(m.name) || blank(m.key) || !GUID_RE.test(String(m.key).trim())) {
|
|
732
|
+
at('INLINE_AGENT_TOOL_MCP_INCOMPLETE', `Inline agent step "${step}"'s MCP tool needs a \`name\` and a uuid \`key\` — the node type is minted `
|
|
733
|
+
+ `from them (uipath.agent.resource.tool.mcp.<name>.<key>) and the key is the reference the runtime `
|
|
734
|
+
+ `resolves.`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (kind === 'a2a') {
|
|
738
|
+
const a = t;
|
|
739
|
+
if (blank(a.name) || blank(a.key) || !GUID_RE.test(String(a.key).trim()) || blank(a.slug)) {
|
|
740
|
+
at('INLINE_AGENT_TOOL_A2A_INCOMPLETE', `Inline agent step "${step}"'s remote-A2A tool needs \`name\`, a uuid \`key\`, and \`slug\` — the node `
|
|
741
|
+
+ `type is minted from name+key, and the runtime resolves the remote agent by slug (the platform's own `
|
|
742
|
+
+ `mapper refuses a blank one).`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else if (kind === 'clientside') {
|
|
746
|
+
if (blank(t.name)) {
|
|
747
|
+
at('INLINE_AGENT_TOOL_CLIENTSIDE_NO_NAME', `Inline agent step "${step}"'s client-side tool has no \`name\` — it is what the model calls, and the `
|
|
748
|
+
+ `calling application dispatches on it.`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else if (kind === 'httpRequest') {
|
|
752
|
+
const h = t;
|
|
753
|
+
const m = h.method === undefined ? undefined : String(h.method).toUpperCase();
|
|
754
|
+
if (m !== undefined && !['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(m)) {
|
|
755
|
+
at('INLINE_AGENT_TOOL_HTTP_METHOD', `Inline agent step "${step}"'s HTTP-request tool fixes the method to ${JSON.stringify(h.method)}. The `
|
|
756
|
+
+ `tool's contract allows GET, POST, PUT, PATCH, DELETE — anything else the runtime refuses at call time.`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
at('INLINE_AGENT_TOOL_UNKNOWN_KIND', `Inline agent step "${step}" declares a tool with kind ${JSON.stringify(kind)}. Valid kinds: 'builtin', `
|
|
761
|
+
+ `'connector', 'process', 'agent', 'api', 'flow', 'maestro', 'function', 'ixp', 'mcp', 'a2a', `
|
|
762
|
+
+ `'clientside', 'httpRequest'.`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const escalations = inputs?.escalation === undefined ? []
|
|
766
|
+
: Array.isArray(inputs.escalation) ? inputs.escalation : [inputs.escalation];
|
|
767
|
+
for (const e of escalations) {
|
|
768
|
+
if (blank(e?.name)) {
|
|
769
|
+
at('INLINE_AGENT_ESCALATION_NO_NAME', `Inline agent step "${step}" declares an escalation without a \`name\`. The node's definition requires one `
|
|
770
|
+
+ `(\`minLength: 1\`).`);
|
|
771
|
+
}
|
|
772
|
+
if (e?.variant !== undefined && e.variant !== 'quick-form') {
|
|
773
|
+
at('INLINE_AGENT_ESCALATION_VARIANT', `Inline agent step "${step}"'s escalation names the variant ${JSON.stringify(e.variant)}. Valid: `
|
|
774
|
+
+ `'quick-form' — or omit it for the app-backed default.`);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (e?.variant === 'quick-form') {
|
|
778
|
+
// The form lives INLINE: an app would be emitted and ignored, and a form
|
|
779
|
+
// with nothing to fill in is a notification nobody can answer.
|
|
780
|
+
if (e.app !== undefined) {
|
|
781
|
+
at('INLINE_AGENT_ESCALATION_QUICKFORM_WITH_APP', `Inline agent step "${step}"'s quick-form escalation also names an app. The form lives in the flow — `
|
|
782
|
+
+ `drop \`app\`, or drop the variant for the app-backed escalation.`);
|
|
783
|
+
}
|
|
784
|
+
if (!Array.isArray(e.fields) || e.fields.length === 0) {
|
|
785
|
+
at('INLINE_AGENT_ESCALATION_NO_FIELDS', `Inline agent step "${step}"'s quick-form escalation declares no \`fields\` — the reviewer gets an `
|
|
786
|
+
+ `empty form.`);
|
|
787
|
+
}
|
|
788
|
+
for (const f of Array.isArray(e.fields) ? e.fields : []) {
|
|
789
|
+
if (!FORM_FIELD_TYPES.includes(String(f?.type))) {
|
|
790
|
+
at('INLINE_AGENT_ESCALATION_FIELD_TYPE_UNKNOWN', `Inline agent step "${step}"'s quick-form escalation gives the field "${String(f?.id ?? '?')}" the type `
|
|
791
|
+
+ `${JSON.stringify(f?.type)}, ${formFieldTypeWhy(f?.type, `which the form does not recognize — it would `
|
|
792
|
+
+ `be published as a plain string`)}. Valid: ${validFormFieldTypesText()}.`, formFieldTypeHint(f?.type));
|
|
793
|
+
}
|
|
794
|
+
const optionsProblem = formFieldOptionsProblem(f);
|
|
795
|
+
if (optionsProblem !== undefined) {
|
|
796
|
+
const message = `Inline agent step "${step}"'s quick-form escalation field "${String(f?.id ?? '?')}" `
|
|
797
|
+
+ `${formFieldOptionsWhy(optionsProblem, f?.type)}.`;
|
|
798
|
+
if (optionsProblem === 'ignored')
|
|
799
|
+
warn('INLINE_AGENT_ESCALATION_FIELD_OPTIONS_IGNORED', message);
|
|
800
|
+
else {
|
|
801
|
+
at('INLINE_AGENT_ESCALATION_FIELD_OPTIONS', message, `options: [{ id: 'opt-1', value: 'high', label: 'High' }, { id: 'opt-2', value: 'low', label: 'Low' }]`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
for (const problem of formFieldSettingProblems(f)) {
|
|
805
|
+
at('INLINE_AGENT_ESCALATION_FIELD_SETTING_INVALID', `Inline agent step "${step}"'s quick-form escalation gives the field "${String(f?.id ?? '?')}" `
|
|
806
|
+
+ `${problem}. The platform refuses the form.`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (e?.fields !== undefined) {
|
|
812
|
+
at('INLINE_AGENT_ESCALATION_APP_WITH_FIELDS', `Inline agent step "${step}"'s escalation declares inline \`fields\`, but its form belongs to the deployed `
|
|
813
|
+
+ `app. Use variant: 'quick-form' for an inline form.`);
|
|
814
|
+
}
|
|
815
|
+
if (blank(e?.app?.key) || blank(e?.app?.name)) {
|
|
816
|
+
at('INLINE_AGENT_ESCALATION_NO_APP', `Inline agent step "${step}"'s escalation needs \`app.key\` and \`app.name\` — the deployed Action Center app `
|
|
817
|
+
+ `that carries the review task. An escalation with no app is a task nobody can open.`);
|
|
818
|
+
}
|
|
819
|
+
else if (!GUID_RE.test(String(e.app.key).trim())) {
|
|
820
|
+
at('INLINE_AGENT_ESCALATION_APP_KEY_NOT_GUID', `Inline agent step "${step}"'s escalation \`app.key\` is ${JSON.stringify(e.app.key)}, which is not a uuid. `
|
|
821
|
+
+ `It is the app's own key on the tenant, not its name.`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// MEMORY (Phase 5A): identity is required — the node type is minted from it.
|
|
825
|
+
const mem = inputs?.memory;
|
|
826
|
+
if (mem !== undefined) {
|
|
827
|
+
if (blank(mem.name) || blank(mem.id)) {
|
|
828
|
+
at('INLINE_AGENT_MEMORY_INCOMPLETE', `Inline agent step "${step}"'s memory needs both \`name\` and \`id\` — the node type is minted from them `
|
|
829
|
+
+ `(uipath.agent.resource.memory.<name>.<id>) and the runtime resolves the memory by id.`);
|
|
830
|
+
}
|
|
831
|
+
else if (!GUID_RE.test(String(mem.id).trim())) {
|
|
832
|
+
at('INLINE_AGENT_MEMORY_ID_NOT_GUID', `Inline agent step "${step}"'s memory \`id\` is ${JSON.stringify(mem.id)}, which is not a uuid. It is the `
|
|
833
|
+
+ `memory's own id on the tenant, not its name.`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* A read of an inline agent's output: `out('<step>', '<field>')`.
|
|
839
|
+
*
|
|
840
|
+
* Stricter than the published families' equivalent, and it can afford to be: the
|
|
841
|
+
* declared `returns` IS the contract (there is no tenant to disagree with), and it
|
|
842
|
+
* is what any runner has to coerce the model's completion to: build
|
|
843
|
+
* `{ <declared keys> }` and put the raw completion in the first one if the model
|
|
844
|
+
* ignored the shape. So a read outside the set is undefined by construction, not
|
|
845
|
+
* merely probably.
|
|
846
|
+
*/
|
|
847
|
+
export function checkInlineAgentRead({ node: name, field, rest, reader: step }, spec, report) {
|
|
848
|
+
if (field === undefined)
|
|
849
|
+
return;
|
|
850
|
+
if (field === 'error')
|
|
851
|
+
return;
|
|
852
|
+
if (field !== 'output') {
|
|
853
|
+
report('INLINE_AGENT_READ_NOT_OUTPUT', `Inline agent "${name}" publishes its answer under "output", but "$vars.${name}.${field}" (read in `
|
|
854
|
+
+ `"${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', '<field>')`);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
// The default when `returns` is omitted is the definition's own.
|
|
858
|
+
const declared = spec.returns && Object.keys(spec.returns).length
|
|
859
|
+
? Object.keys(spec.returns)
|
|
860
|
+
: ['content'];
|
|
861
|
+
const wanted = (rest ?? '').replace(/^\./, '').split('.')[0];
|
|
862
|
+
if (!wanted)
|
|
863
|
+
return;
|
|
864
|
+
if (declared.includes(wanted))
|
|
865
|
+
return;
|
|
866
|
+
const nearest = nearestName(wanted, declared);
|
|
867
|
+
report('INLINE_AGENT_READ_UNKNOWN_FIELD', `Inline agent "${name}" declares no output "${wanted}" (read as "$vars.${name}.output.${wanted}" in `
|
|
868
|
+
+ `"${step}"). Its \`returns\` names: ${declared.join(', ')}`
|
|
869
|
+
+ `${spec.returns && Object.keys(spec.returns).length ? '' : ' (the definition\'s default, because the step declares no `returns`)'}`
|
|
870
|
+
+ `. Unlike a published agent's, this contract is yours — the node's \`agentOutputVariables\` are these `
|
|
871
|
+
+ `names, and the engine coerces the model's completion to exactly them, so a read outside the set is `
|
|
872
|
+
+ `undefined by construction.`, nearest ? `out('${name}', '${nearest}')` : undefined);
|
|
873
|
+
}
|