@uipath/maestro-builder-sdk 6.8.6 → 6.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +67 -67
- package/dist/api-members.md +161 -161
- package/dist/check.d.ts +26 -5
- package/dist/check.js +186 -10
- package/dist/compile-cli.js +21 -4
- package/dist/core/actions.d.ts +27 -11
- package/dist/core/actions.js +2 -2
- package/dist/core/binding-messages.d.ts +60 -1
- package/dist/core/binding-messages.js +82 -7
- package/dist/serialize.js +68 -33
- package/package.json +2 -2
package/dist/check.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type LookupResolutions, type LookupSpec } from './core/lookups.js';
|
|
2
2
|
import type { SchemaReadiness } from './core/library.js';
|
|
3
3
|
import type { BuiltFlow } from './flow-sdk.js';
|
|
4
|
+
import { type BindingsView } from './core/binding-messages.js';
|
|
4
5
|
export interface Diagnostic {
|
|
5
6
|
level: 'error' | 'warning';
|
|
6
7
|
code: string;
|
|
@@ -140,12 +141,13 @@ export interface CheckOpts {
|
|
|
140
141
|
object?: string;
|
|
141
142
|
}, inputs: Record<string, unknown>) => string | undefined;
|
|
142
143
|
/**
|
|
143
|
-
* The project's `bindings.json`, when there is one — enough of it to detect
|
|
144
|
-
* entry that answers with its own name
|
|
144
|
+
* The project's `bindings.json`, when there is one — enough of it to detect the
|
|
145
|
+
* two labels `compile` refuses: an entry that answers with its own name
|
|
146
|
+
* (BINDING_SELF_NAME), and a label a populated file does not declare
|
|
147
|
+
* (BINDING_UNDECLARED). `declared` and `entry` are optional so a caller holding
|
|
148
|
+
* only `resolve` still gets the first rule; the `Bindings` class has all three.
|
|
145
149
|
*/
|
|
146
|
-
bindings?:
|
|
147
|
-
resolve(name?: string): string | undefined;
|
|
148
|
-
};
|
|
150
|
+
bindings?: BindingsView;
|
|
149
151
|
}
|
|
150
152
|
export declare function check(built: BuiltFlow, opts?: CheckOpts): Diagnostic[];
|
|
151
153
|
/**
|
|
@@ -164,3 +166,22 @@ export declare const SCHEDULE_PATTERN: string;
|
|
|
164
166
|
* and day names, and the Quartz specials `L W LW ? * #`.
|
|
165
167
|
*/
|
|
166
168
|
export declare const SCHEDULE_CRON_PATTERN: string;
|
|
169
|
+
/**
|
|
170
|
+
* The phrase in `text` that tells a model to answer in JSON TEXT, or `undefined`.
|
|
171
|
+
*
|
|
172
|
+
* `check` uses it for `INLINE_AGENT_PROMPT_JSON_TEXT`, passing the step's `returns`
|
|
173
|
+
* keys so the brace form only counts a brace that spells one of them. Without
|
|
174
|
+
* `returnKeys` (the example and doc sweeps) any quoted identifier key counts.
|
|
175
|
+
* `{{input.*}}` placeholders are removed first, so an input named `json` never
|
|
176
|
+
* matches.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* jsonTextDirective('Return ONLY a JSON object with keys "subject" and "body".'); // 'Return ONLY a JSON'
|
|
181
|
+
* jsonTextDirective('Return ONLY {"category","priority"}.', ['category']); // '{"category",'
|
|
182
|
+
* jsonTextDirective('Return a result conforming to the output schema.'); // undefined
|
|
183
|
+
* jsonTextDirective('Do not return JSON.'); // undefined
|
|
184
|
+
* jsonTextDirective('Answer the question using the JSON data below.'); // undefined
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export declare function jsonTextDirective(text: string, returnKeys?: readonly string[]): string | undefined;
|
package/dist/check.js
CHANGED
|
@@ -10,7 +10,7 @@ import { FlowNode } from './core/node-classes.js';
|
|
|
10
10
|
import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './core/lookups.js';
|
|
11
11
|
import { EVENT_FILTER_OPERATORS, readEventFilter, eventFilterProblem } from './event-filters.js';
|
|
12
12
|
import { extractRefs } from './core/expr-check.js';
|
|
13
|
-
import { bindingSelfNameMessage } from './core/binding-messages.js';
|
|
13
|
+
import { bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
14
14
|
import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
|
|
15
15
|
import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
16
16
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
@@ -357,6 +357,14 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
357
357
|
checkTransform(s.name, s.spec.inputs, diags, typeOf, triggerId);
|
|
358
358
|
if (s.spec.kind === 'http') {
|
|
359
359
|
checkHttp(s.name, s.spec.inputs, diags);
|
|
360
|
+
// Only a complete pair: an empty half is HTTP_MANAGED_BINDING_INCOMPLETE, which
|
|
361
|
+
// serialize refuses before it resolves either name.
|
|
362
|
+
const pair = s.spec.inputs;
|
|
363
|
+
const filled = (v) => typeof v === 'string' && v.trim() !== '';
|
|
364
|
+
if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
|
|
365
|
+
const httpStep = s.name;
|
|
366
|
+
checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }));
|
|
367
|
+
}
|
|
360
368
|
const seen = new Set();
|
|
361
369
|
for (const b of s.spec.inputs.branches ?? []) {
|
|
362
370
|
addExpr(b.condition.js, s.name);
|
|
@@ -530,6 +538,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
530
538
|
});
|
|
531
539
|
checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
532
540
|
checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
541
|
+
checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription));
|
|
533
542
|
}
|
|
534
543
|
if (s.spec.kind === 'subflow') {
|
|
535
544
|
// Only a real child goes in the map: a missing one is reported by
|
|
@@ -1470,14 +1479,41 @@ function checkConnectorContract(step, spec, opts, diags) {
|
|
|
1470
1479
|
if (inputErr) {
|
|
1471
1480
|
diags.push({ level: 'error', code: 'CONNECTOR_INPUT', step, message: `Connector step "${step}": ${inputErr}` });
|
|
1472
1481
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1482
|
+
checkBindingLabels(`Connector step "${step}"`, spec, opts, (code, message) => diags.push({ level: 'error', code, step, message }), { key: spec.key, action: spec.action });
|
|
1483
|
+
}
|
|
1484
|
+
/** What `registry prepare` takes for an event subscription: its connector key and curated event name. */
|
|
1485
|
+
function eventTarget(sub) {
|
|
1486
|
+
const { connector, event } = (sub ?? {});
|
|
1487
|
+
return typeof connector === 'string' && connector !== ''
|
|
1488
|
+
? { key: connector, ...(typeof event === 'string' && event !== '' ? { action: event } : {}) }
|
|
1489
|
+
: undefined;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* The binding-label rules `serialize`'s `resolveBinding` enforces, for one
|
|
1493
|
+
* `{ connection, folder }` pair — reported at every site that resolves one
|
|
1494
|
+
* (connector step, event trigger and `.entryPoint` onEvent, waitForEvent, managed
|
|
1495
|
+
* HTTP), with the sentence compile throws. Without this at the event and HTTP
|
|
1496
|
+
* sites `check` printed `✓ no issues` for a source `compile` then refused.
|
|
1497
|
+
*
|
|
1498
|
+
* Silent without a bindings.json, and for a label that resolves to anything
|
|
1499
|
+
* other than its own name (stub values are compile's CONNECTION_STUB warning).
|
|
1500
|
+
*/
|
|
1501
|
+
function checkBindingLabels(where, labels, opts, report, target) {
|
|
1502
|
+
const bindings = opts.bindings;
|
|
1503
|
+
if (!bindings || !labels)
|
|
1504
|
+
return;
|
|
1505
|
+
for (const attribute of ['connection', 'folder']) {
|
|
1506
|
+
const name = labels[attribute];
|
|
1507
|
+
if (typeof name !== 'string')
|
|
1508
|
+
continue;
|
|
1509
|
+
const resolved = bindings.resolve(name);
|
|
1510
|
+
if (resolved === name) {
|
|
1511
|
+
report('BINDING_SELF_NAME', bindingSelfNameMessage(where, name, attribute, target));
|
|
1512
|
+
}
|
|
1513
|
+
else if (resolved === undefined) {
|
|
1514
|
+
const declared = undeclaredBinding(bindings, name);
|
|
1515
|
+
if (declared) {
|
|
1516
|
+
report('BINDING_UNDECLARED', bindingUndeclaredMessage(where, name, attribute, declared, target));
|
|
1481
1517
|
}
|
|
1482
1518
|
}
|
|
1483
1519
|
}
|
|
@@ -2278,6 +2314,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
2278
2314
|
const sub = spec.subscription;
|
|
2279
2315
|
checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
|
|
2280
2316
|
checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
|
|
2317
|
+
checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub));
|
|
2281
2318
|
return;
|
|
2282
2319
|
}
|
|
2283
2320
|
// manual() is the explicit spelling of the default — nothing to validate.
|
|
@@ -3154,6 +3191,106 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
|
|
|
3154
3191
|
...(nearest ? { suggestion: `out('${name}', '${nearest}')` } : {}),
|
|
3155
3192
|
});
|
|
3156
3193
|
}
|
|
3194
|
+
// ─── an inline agent's prompt asking for JSON text ───────────────────────────────
|
|
3195
|
+
//
|
|
3196
|
+
// The phrase table behind `INLINE_AGENT_PROMPT_JSON_TEXT`. It is kept narrow on
|
|
3197
|
+
// purpose, because the rule is an error. Three shapes count, and each skips a
|
|
3198
|
+
// negation anywhere earlier in the same clause ("Do not return JSON", "Do not wrap
|
|
3199
|
+
// the answer in JSON", "Return the items, not as JSON"):
|
|
3200
|
+
//
|
|
3201
|
+
// - DIRECT: an output verb, then only words from a closed qualifier list, then
|
|
3202
|
+
// `json` — "Return ONLY a JSON object", "Return only the typed JSON contract".
|
|
3203
|
+
// - VIA: an output verb, up to two words, then `as` / `in` / `with`, qualifiers,
|
|
3204
|
+
// `json` — "Format your answer as JSON", "Respond with valid JSON".
|
|
3205
|
+
// - BRACE: a quoted-key brace whose key is one of the step's own `returns` keys —
|
|
3206
|
+
// `Return ONLY {"category","priority"}` next to `returns: { category, priority }`.
|
|
3207
|
+
//
|
|
3208
|
+
// A word outside the qualifier list ends a DIRECT match, so JSON as DATA does not
|
|
3209
|
+
// count in the DIRECT form: "Answer the question using the JSON data below", "Give
|
|
3210
|
+
// a summary of the JSON ticket", "Return the invoice number from the JSON". The VIA
|
|
3211
|
+
// form allows two free words, so it can still fire on JSON as a topic ("Answer in
|
|
3212
|
+
// Spanish with JSON examples"); the tail skips the common data nouns after `json`
|
|
3213
|
+
// (keys, data, record, examples, input) and the JSON-family FORMATS a string field
|
|
3214
|
+
// legitimately holds (JSON Schema, JSON Patch, JSON-LD, JSON Lines, …). `write` is
|
|
3215
|
+
// not a verb here: "You help users write JSON" is a topic, not an answer shape.
|
|
3216
|
+
//
|
|
3217
|
+
// The skills repo's preview-doc gate (tests/tasks/uipath-maestro-flow/_shared/
|
|
3218
|
+
// test_preview_inline_agent_prompts.py) ports this table to Python and loads a
|
|
3219
|
+
// byte-identical copy of tests/fixtures/inline-agent-prompt-vectors.json, so
|
|
3220
|
+
// change the table, the fixture and the port together.
|
|
3221
|
+
const JSON_TEXT_VERBS = 'return|respond|reply|output|answer|produce|emit|format|give|provide';
|
|
3222
|
+
/** Words that are only ever qualifiers of the answer's shape, never the thing it is about. */
|
|
3223
|
+
const JSON_TEXT_QUALIFIERS = [
|
|
3224
|
+
'only', 'just', 'exactly', 'strictly', 'solely', 'simply', 'a', 'an', 'one', 'single', 'the',
|
|
3225
|
+
'your', 'its', 'this', 'that', 'valid', 'raw', 'pure', 'plain', 'strict', 'compact', 'minified',
|
|
3226
|
+
'well-formed', 'properly', 'formatted', 'typed', 'declared', 'structured', 'following', 'same',
|
|
3227
|
+
'final', 'back', 'proper', 'correct', 'machine-readable', 'parseable', 'parsable',
|
|
3228
|
+
];
|
|
3229
|
+
/** A negation anywhere earlier in the same clause, or anywhere in the match's gap. */
|
|
3230
|
+
const JSON_TEXT_NEGATIONS = new Set(['not', 'never', 'no', 'dont', 'without', 'rather', 'instead', 'avoid']);
|
|
3231
|
+
const isJsonTextNegation = (w) => JSON_TEXT_NEGATIONS.has(w) || w.endsWith("n't") || w.endsWith('n’t');
|
|
3232
|
+
const JSON_TEXT_WORD = "[A-Za-z][\\w'’-]*";
|
|
3233
|
+
/** Words are separated by spaces, tabs or a comma — never a newline or a full stop. */
|
|
3234
|
+
const JSON_TEXT_SEP = '[ \\t]*,?[ \\t]+';
|
|
3235
|
+
const JSON_TEXT_QUAL = `(?:${[...JSON_TEXT_QUALIFIERS].sort((a, b) => b.length - a.length).join('|')})\\b`;
|
|
3236
|
+
// `(?<![-\w])` skips `--output json`; `(?!\.\w)` skips `JSON.parse` / `JSON.stringify`;
|
|
3237
|
+
// `(?!-(?:ld|rpc)\b)` skips JSON-LD / JSON-RPC (`\b` matches before the hyphen);
|
|
3238
|
+
// the last lookahead skips "the JSON below" / "the JSON provided" / "the JSON you
|
|
3239
|
+
// get", data nouns ("the JSON keys", "JSON examples") and JSON-family formats
|
|
3240
|
+
// ("JSON Schema", "JSON Patch", "the JSON path", "a JSON Web Token").
|
|
3241
|
+
const JSON_TEXT_TAIL = `json\\b(?!\\.\\w)(?!-(?:ld|rpc)\\b)(?![ \\t]+(?:below|above|provided|given|you`
|
|
3242
|
+
+ `|keys?|data|records?|examples?|inputs?`
|
|
3243
|
+
+ `|schemas?|patch|path|pointer|lines|rpc|web[ \\t]+tokens?)\\b)`;
|
|
3244
|
+
const JSON_TEXT_VERB = `(?<![-\\w])(?:${JSON_TEXT_VERBS})\\b`;
|
|
3245
|
+
const JSON_TEXT_DIRECT = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,5})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
|
|
3246
|
+
const JSON_TEXT_VIA = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_WORD}){0,2}?)${JSON_TEXT_SEP}(?:as|in|with)`
|
|
3247
|
+
+ `((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,4})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
|
|
3248
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3249
|
+
/**
|
|
3250
|
+
* The phrase in `text` that tells a model to answer in JSON TEXT, or `undefined`.
|
|
3251
|
+
*
|
|
3252
|
+
* `check` uses it for `INLINE_AGENT_PROMPT_JSON_TEXT`, passing the step's `returns`
|
|
3253
|
+
* keys so the brace form only counts a brace that spells one of them. Without
|
|
3254
|
+
* `returnKeys` (the example and doc sweeps) any quoted identifier key counts.
|
|
3255
|
+
* `{{input.*}}` placeholders are removed first, so an input named `json` never
|
|
3256
|
+
* matches.
|
|
3257
|
+
*
|
|
3258
|
+
* @example
|
|
3259
|
+
* ```ts
|
|
3260
|
+
* jsonTextDirective('Return ONLY a JSON object with keys "subject" and "body".'); // 'Return ONLY a JSON'
|
|
3261
|
+
* jsonTextDirective('Return ONLY {"category","priority"}.', ['category']); // '{"category",'
|
|
3262
|
+
* jsonTextDirective('Return a result conforming to the output schema.'); // undefined
|
|
3263
|
+
* jsonTextDirective('Do not return JSON.'); // undefined
|
|
3264
|
+
* jsonTextDirective('Answer the question using the JSON data below.'); // undefined
|
|
3265
|
+
* ```
|
|
3266
|
+
*/
|
|
3267
|
+
export function jsonTextDirective(text, returnKeys) {
|
|
3268
|
+
if (typeof text !== 'string' || text === '')
|
|
3269
|
+
return undefined;
|
|
3270
|
+
// A placeholder becomes a hard break (`·` is neither a word nor a gap), and a
|
|
3271
|
+
// quoted or back-ticked `JSON` is read as the bare word.
|
|
3272
|
+
const t = text
|
|
3273
|
+
.replace(/\{\{\s*input\.[^}]*\}\}/g, ' · ')
|
|
3274
|
+
.replace(/[`"'“”](json)[`"'“”]/gi, ' $1 ');
|
|
3275
|
+
const words = (s) => (s.match(new RegExp(JSON_TEXT_WORD, 'g')) ?? []).map((w) => w.toLowerCase());
|
|
3276
|
+
const keys = returnKeys?.length ? returnKeys.map(escapeRegExp).join('|') : '[A-Za-z_]\\w*';
|
|
3277
|
+
const brace = new RegExp(`\\{[ \\t]*["'](?:${keys})["'][ \\t]*[,:}]`, 'g');
|
|
3278
|
+
for (const rule of [JSON_TEXT_DIRECT, JSON_TEXT_VIA, brace]) {
|
|
3279
|
+
rule.lastIndex = 0;
|
|
3280
|
+
for (const m of t.matchAll(rule)) {
|
|
3281
|
+
if (m.slice(1).some((g) => words(g ?? '').some(isJsonTextNegation)))
|
|
3282
|
+
continue;
|
|
3283
|
+
// Any negation earlier in the same clause: "Do not wrap the answer in JSON",
|
|
3284
|
+
// "No need to return JSON". A comma ends a clause, so "Do not guess, return
|
|
3285
|
+
// ONLY a JSON object" still counts.
|
|
3286
|
+
const clause = t.slice(0, m.index).split(/[.;:!?\n,]/).pop() ?? '';
|
|
3287
|
+
if (words(clause).some(isJsonTextNegation))
|
|
3288
|
+
continue;
|
|
3289
|
+
return m[0].trim();
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
return undefined;
|
|
3293
|
+
}
|
|
3157
3294
|
/**
|
|
3158
3295
|
* Check an `inlineAgent(...)` call.
|
|
3159
3296
|
*
|
|
@@ -3162,7 +3299,7 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
|
|
|
3162
3299
|
* rules can be certain rather than best-effort — the definition's own `required`
|
|
3163
3300
|
* list is checkable, and so is whether the prompts and the declared variables agree.
|
|
3164
3301
|
*
|
|
3165
|
-
*
|
|
3302
|
+
* Three rules exist because the PLATFORM does something with what you wrote:
|
|
3166
3303
|
*
|
|
3167
3304
|
* - `INLINE_AGENT_SCAFFOLD_MODEL` — the model the platform's own scaffold ships is
|
|
3168
3305
|
* stale, and v1's smoke check for this node type fails a flow that keeps it. A
|
|
@@ -3174,6 +3311,11 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
|
|
|
3174
3311
|
* input the prompts never mention is pruned deployed while arriving locally: a
|
|
3175
3312
|
* disagreement between rungs and the platform, which is the one class that has to
|
|
3176
3313
|
* be said out loud rather than left to a run.
|
|
3314
|
+
* - `INLINE_AGENT_PROMPT_JSON_TEXT` — the platform returns `returns` as a typed
|
|
3315
|
+
* object: it becomes the `agent.json` `outputSchema`, and the agent runtime ends
|
|
3316
|
+
* a run with a structured call that fills exactly those fields. A prompt that
|
|
3317
|
+
* ALSO asks for JSON text makes the model pack its whole answer, serialized, into
|
|
3318
|
+
* one string field. See {@link jsonTextDirective}.
|
|
3177
3319
|
*/
|
|
3178
3320
|
function checkInlineAgent(step, inputs, diags) {
|
|
3179
3321
|
const at = (code, message, suggestion) => {
|
|
@@ -3241,6 +3383,40 @@ function checkInlineAgent(step, inputs, diags) {
|
|
|
3241
3383
|
+ `definition's default, \`{ content: 'string' }\`, which is the scaffold shape. Declare the fields the `
|
|
3242
3384
|
+ `agent must return, or drop the key and read \`content\`.`);
|
|
3243
3385
|
}
|
|
3386
|
+
// A prompt that asks for JSON TEXT while `returns` declares the fields. The
|
|
3387
|
+
// platform already returns those fields as a typed object — `returns` is the
|
|
3388
|
+
// agent.json `outputSchema`, and the runtime ends a run with a structured call
|
|
3389
|
+
// that fills exactly those keys — so the extra directive does not shape the
|
|
3390
|
+
// answer, it gets serialized INTO it: on 2026-09-23 a writer declaring
|
|
3391
|
+
// `{ subject, body }` came back with `body` = '{"subject":…,"body":…}' in 3 of 3
|
|
3392
|
+
// debug runs, and the flow mailed the JSON. `uip maestro flow validate` accepts
|
|
3393
|
+
// the prompt (it never reads prompt meaning), so this is where it is said. An
|
|
3394
|
+
// ERROR for the same reason `INLINE_AGENT_UNDECLARED_PLACEHOLDER` is one: the
|
|
3395
|
+
// platform accepts the shape and the run still delivers the wrong value, and a
|
|
3396
|
+
// warning is printed past while compile writes the .flow anyway. Only when
|
|
3397
|
+
// `returns` is declared: with the `{ content }` default, JSON text in `content`
|
|
3398
|
+
// may be exactly what the author reads back.
|
|
3399
|
+
const returnKeys = Object.keys(inputs?.returns ?? {});
|
|
3400
|
+
if (returnKeys.length) {
|
|
3401
|
+
for (const field of ['systemPrompt', 'userPrompt']) {
|
|
3402
|
+
const phrase = jsonTextDirective(String(inputs?.[field] ?? ''), returnKeys);
|
|
3403
|
+
if (phrase === undefined)
|
|
3404
|
+
continue;
|
|
3405
|
+
const rewrite = `Return a result conforming to the output schema. `
|
|
3406
|
+
+ returnKeys.map((k) => `${k}: <what it holds>.`).join(' ');
|
|
3407
|
+
at('INLINE_AGENT_PROMPT_JSON_TEXT', `Inline agent step "${step}"'s \`${field}\` tells the model to answer in JSON text ("${phrase}"), `
|
|
3408
|
+
+ `but \`returns\` already declares the answer: ${returnKeys.map((k) => `\`${k}\``).join(', ')}. `
|
|
3409
|
+
+ `The platform returns those fields as a typed object — \`returns\` becomes the agent.json `
|
|
3410
|
+
+ `\`outputSchema\`, and the agent runtime ends each run with a structured call that fills exactly `
|
|
3411
|
+
+ `those keys — so \`out('${step}', '${returnKeys[0]}')\` reads a value, not text to parse. Asking `
|
|
3412
|
+
+ `for JSON as well makes the model pack its whole answer, serialized, into one string field (for `
|
|
3413
|
+
+ `example a \`body\` that reads '{"subject":…,"body":…}'). Replace only that JSON instruction (keep the rest `
|
|
3414
|
+
+ `of the prompt) with one line per field, in the platform's own wording: "${rewrite}" Declare a field \`'object'\` or `
|
|
3415
|
+
+ `\`'array'\` if it should carry structure. \`uip maestro flow validate\` accepts this prompt, so `
|
|
3416
|
+
+ `nothing later catches it. If this is a false positive (the prompt only mentions JSON as data), `
|
|
3417
|
+
+ `compile with --no-check.`);
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3244
3420
|
// The prompts-vs-variables agreement, in both directions. Both prompts are
|
|
3245
3421
|
// searched, because flow-v1's pre-pass scans both.
|
|
3246
3422
|
const prompts = `${String(inputs?.systemPrompt ?? '')}\n${String(inputs?.userPrompt ?? '')}`;
|
package/dist/compile-cli.js
CHANGED
|
@@ -27,6 +27,7 @@ import { Bindings } from './bindings.js';
|
|
|
27
27
|
import { runWhenInvokedDirectly } from './cli-run.js';
|
|
28
28
|
import { connectionResources, mergeBindingsV2, readBindingsV2 } from './bindings-v2.js';
|
|
29
29
|
import { ensureTypeScriptRuntime } from './node-runtime.js';
|
|
30
|
+
import { BindingRefusal } from './core/binding-messages.js';
|
|
30
31
|
function opt(argv, name) {
|
|
31
32
|
const i = argv.indexOf(name);
|
|
32
33
|
return i >= 0 ? argv[i + 1] : undefined;
|
|
@@ -106,11 +107,27 @@ export async function run(argv) {
|
|
|
106
107
|
}
|
|
107
108
|
// The serializer's own observations — a connection or folder value that can
|
|
108
109
|
// never be a tenant resource key — printed in the same `⚠ [CODE]` shape as
|
|
109
|
-
// check()'s
|
|
110
|
+
// check()'s. Warnings are never fatal: the .flow is still written, exit stays 0.
|
|
111
|
+
// A binding LABEL the serializer refuses (its own name as the resourceKey, or a
|
|
112
|
+
// label a populated bindings.json does not declare) is an error, not a
|
|
113
|
+
// warning: the check pass above reports it first, and this is the same refusal
|
|
114
|
+
// in the same `✗ [CODE]` shape when that pass was skipped (`--no-check`, or an
|
|
115
|
+
// emit-only project). Nothing is written.
|
|
110
116
|
const warnings = [];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
let emitted;
|
|
118
|
+
try {
|
|
119
|
+
emitted = serializeWithSidecars(built, { library, bindings, warn: (w) => warnings.push(w) });
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (!(error instanceof BindingRefusal))
|
|
123
|
+
throw error;
|
|
124
|
+
for (const w of warnings)
|
|
125
|
+
console.error(` ⚠ [${w.code}] ${w.message}`);
|
|
126
|
+
console.error(` ✗ [${error.code}] ${error.message}`);
|
|
127
|
+
console.error('compile: 1 error(s); not emitting.');
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
const { flow: flowJson, sidecars } = emitted;
|
|
114
131
|
for (const w of warnings)
|
|
115
132
|
console.error(` ⚠ [${w.code}] ${w.message}`);
|
|
116
133
|
const out = opt(argv, '-o') ?? `${built.name || basename(file).replace(/\.flow\.ts$|\.ts$/, '')}.flow`;
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -1376,16 +1376,18 @@ export interface InlineAgentInputs {
|
|
|
1376
1376
|
*/
|
|
1377
1377
|
model: string;
|
|
1378
1378
|
/**
|
|
1379
|
-
* The agent's standing instructions — who it is and what
|
|
1380
|
-
* Required by the definition (`minLength: 1`).
|
|
1379
|
+
* The agent's standing instructions — who it is, and what each `returns` field
|
|
1380
|
+
* should hold. Required by the definition (`minLength: 1`).
|
|
1381
1381
|
*
|
|
1382
1382
|
* **Write it as plain text, and reference a value as `{{input.<name>}}`** — the
|
|
1383
1383
|
* agent's own template syntax, and the same names `inputs` declares:
|
|
1384
1384
|
*
|
|
1385
1385
|
* ```ts
|
|
1386
|
-
* systemPrompt: 'You classify support email. Return
|
|
1386
|
+
* systemPrompt: 'You classify support email. Return a result conforming to the output '
|
|
1387
|
+
* + 'schema. category: billing | technical | account. priority: low | normal | high.',
|
|
1387
1388
|
* userPrompt: 'Classify this email:\n{{input.body}}',
|
|
1388
1389
|
* inputs: { body: input('body') },
|
|
1390
|
+
* returns: { category: 'string', priority: 'string' },
|
|
1389
1391
|
* ```
|
|
1390
1392
|
*
|
|
1391
1393
|
* Why a placeholder rather than an interpolated expression: an inline agent is two
|
|
@@ -1404,11 +1406,24 @@ export interface InlineAgentInputs {
|
|
|
1404
1406
|
* literal text reaches the model. See the inline-agent node reference (`website/reference/inline-agent.md`)
|
|
1405
1407
|
* §"The three prompt DIALECTS".
|
|
1406
1408
|
*
|
|
1407
|
-
*
|
|
1408
|
-
* `
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1409
|
+
* **The answer's shape is `returns`, not the prompt.** `returns` becomes the node's
|
|
1410
|
+
* `agentOutputVariables` and the `agent.json` `outputSchema` — what Studio Web's
|
|
1411
|
+
* Outputs editor writes — and the platform's agent runtime ends each run with a
|
|
1412
|
+
* built-in structured call (`end_execution`) that fills exactly those fields. So
|
|
1413
|
+
* `out('<step>', '<field>')` reads a typed value, not text to parse. Describe what
|
|
1414
|
+
* each field holds, in the product's own wording, as the example above does:
|
|
1415
|
+
* `Return a result conforming to the output schema. <field>: <how to fill it>.`
|
|
1416
|
+
*
|
|
1417
|
+
* Never ask for JSON TEXT as well ("Return ONLY a JSON object with keys …"). The
|
|
1418
|
+
* model then packs its whole answer, serialized, into ONE string field: a writer
|
|
1419
|
+
* declaring `{ subject, body }` came back on 2026-09-23 with
|
|
1420
|
+
* `body: '{"subject":"…","body":"Dear …"}'` in 3 of 3 debug runs, and the flow sent
|
|
1421
|
+
* that text on. A field that should carry structure is declared `'object'` or
|
|
1422
|
+
* `'array'` instead.
|
|
1423
|
+
*
|
|
1424
|
+
* @enforcedBy INLINE_AGENT_PROMPT_JSON_TEXT A prompt that asks for JSON text while
|
|
1425
|
+
* `returns` declares fields is refused; the platform accepts it and the run returns
|
|
1426
|
+
* the packed value.
|
|
1412
1427
|
*/
|
|
1413
1428
|
systemPrompt: string;
|
|
1414
1429
|
/**
|
|
@@ -1516,7 +1531,8 @@ export interface InlineAgentInputs {
|
|
|
1516
1531
|
* ```ts
|
|
1517
1532
|
* inlineAgent({
|
|
1518
1533
|
* model: 'gpt-5.4',
|
|
1519
|
-
* systemPrompt: 'You decide billing disputes from the SOPs. Return
|
|
1534
|
+
* systemPrompt: 'You decide billing disputes from the SOPs. Return a result conforming to the output '
|
|
1535
|
+
* + 'schema. determination: valid | invalid | needs_review. rationale: the SOP rule that decided it.',
|
|
1520
1536
|
* userPrompt: 'Dispute: {{input.description}}',
|
|
1521
1537
|
* inputs: { description: input('disputeDescription') },
|
|
1522
1538
|
* returns: { determination: 'string', rationale: 'string' },
|
|
@@ -3069,8 +3085,8 @@ export declare function agent(inputs: AgentInputs): ActionSpec;
|
|
|
3069
3085
|
* model: 'gpt-5.4',
|
|
3070
3086
|
* systemPrompt:
|
|
3071
3087
|
* 'You are a support triage assistant. Classify the email into exactly one ' +
|
|
3072
|
-
* 'category
|
|
3073
|
-
* '
|
|
3088
|
+
* 'category and one priority. Return a result conforming to the output schema. ' +
|
|
3089
|
+
* 'category: billing | technical | account. priority: low | normal | high.',
|
|
3074
3090
|
* userPrompt: 'Classify this email:\n{{input.body}}',
|
|
3075
3091
|
* inputs: { body: input('body') },
|
|
3076
3092
|
* returns: { category: 'string', priority: 'string' },
|
package/dist/core/actions.js
CHANGED
|
@@ -850,8 +850,8 @@ export function agent(inputs) {
|
|
|
850
850
|
* model: 'gpt-5.4',
|
|
851
851
|
* systemPrompt:
|
|
852
852
|
* 'You are a support triage assistant. Classify the email into exactly one ' +
|
|
853
|
-
* 'category
|
|
854
|
-
* '
|
|
853
|
+
* 'category and one priority. Return a result conforming to the output schema. ' +
|
|
854
|
+
* 'category: billing | technical | account. priority: low | normal | high.',
|
|
855
855
|
* userPrompt: 'Classify this email:\n{{input.body}}',
|
|
856
856
|
* inputs: { body: input('body') },
|
|
857
857
|
* returns: { category: 'string', priority: 'string' },
|
|
@@ -1 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
/** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
|
|
2
|
+
export declare const GUID_RE: RegExp;
|
|
3
|
+
/** The diagnostic codes a label refusal carries, in `check` and on the error `serialize` throws. */
|
|
4
|
+
export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED';
|
|
5
|
+
/**
|
|
6
|
+
* What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
|
|
7
|
+
* message is the sentence `check` prints; the `code` lets `compile --no-check`
|
|
8
|
+
* print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BindingRefusal extends Error {
|
|
11
|
+
readonly code: BindingRefusalCode;
|
|
12
|
+
constructor(code: BindingRefusalCode, message: string);
|
|
13
|
+
}
|
|
14
|
+
/** The part of a bindings.json the label rules read — `core/bindings.ts` `Bindings` has all three. */
|
|
15
|
+
export interface BindingsView {
|
|
16
|
+
resolve(name?: string): string | undefined;
|
|
17
|
+
declared?(): string[];
|
|
18
|
+
entry?(name?: string): unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare function bindingSelfNameMessage(where: string, name: string, attribute: 'connection' | 'folder', target?: PrepareTarget): string;
|
|
21
|
+
/**
|
|
22
|
+
* The names a populated bindings.json declares when it does NOT declare `name`,
|
|
23
|
+
* else `undefined`.
|
|
24
|
+
*
|
|
25
|
+
* `undefined` — nothing to refuse — when no bindings.json is loaded, when it
|
|
26
|
+
* declares no entries (offline authoring: the symbolic name is the only honest
|
|
27
|
+
* placeholder there), when `name` is itself a GUID (an author may write the
|
|
28
|
+
* resource key directly), or when any entry answers to `name`. "Answers" is the
|
|
29
|
+
* union of `entry()`, `resolve()` and `declared()`, so an entry that is declared
|
|
30
|
+
* but carries no `resourceKey` is NOT refused here — it stays the
|
|
31
|
+
* `CONNECTION_STUB` warning, which is the route a simulated or validate-only task
|
|
32
|
+
* with nothing real to bind is meant to take.
|
|
33
|
+
*/
|
|
34
|
+
export declare function undeclaredBinding(bindings: BindingsView | undefined, name: string): string[] | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The connector and operation a label belongs to, when the call site knows them:
|
|
37
|
+
* a connector step's `key` + `action`, an event's `connector` + `event`. With it
|
|
38
|
+
* the remedy is a command the author can paste; without it (managed HTTP) the
|
|
39
|
+
* command keeps its `<connector-key> <action>` placeholders.
|
|
40
|
+
*/
|
|
41
|
+
export interface PrepareTarget {
|
|
42
|
+
key: string;
|
|
43
|
+
action?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Why compile refuses `name`, and how to bind it.
|
|
47
|
+
*
|
|
48
|
+
* It names NO declared label as the one to use. bindings.json rows carry a label,
|
|
49
|
+
* a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
|
|
50
|
+
* they were bound for, so compile cannot tell which declared label — if any — is
|
|
51
|
+
* this connector's. Suggesting one sent a Data Service step to `connection:
|
|
52
|
+
* 'slack'` (the only `ConnectionId` row), and following that compiled the Slack
|
|
53
|
+
* connection id into the Data Service node with no warning at all: a real GUID of
|
|
54
|
+
* the wrong connector, which nothing downstream detects. The partial-prepare shape
|
|
55
|
+
* (ipe-drive-to-slack 09-23: `slack` bound, `drive` not yet) is exactly where
|
|
56
|
+
* that advice was on offer. So the message lists what is declared, says that a
|
|
57
|
+
* row is only reusable if it was bound for THIS connector, and gives the
|
|
58
|
+
* `prepare` command for this step's own connector and operation.
|
|
59
|
+
*/
|
|
60
|
+
export declare function bindingUndeclaredMessage(where: string, name: string, attribute: 'connection' | 'folder', declared: readonly string[], target?: PrepareTarget): string;
|
|
@@ -1,16 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* `serialize` (which throws
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 2026-09-01, turn 22 → 23).
|
|
2
|
+
* The refusals for a connection/folder label `compile` cannot turn into a tenant
|
|
3
|
+
* resource key — shared by `serialize` (which throws them) and `check`
|
|
4
|
+
* (BINDING_SELF_NAME, BINDING_UNDECLARED), so the author reads the same sentence
|
|
5
|
+
* from the inner loop as from compile. Before this, `check` printed `0 error(s)`
|
|
6
|
+
* for a source `compile` then refused (teams-decision, 2026-09-01, turn 22 → 23).
|
|
7
7
|
*/
|
|
8
8
|
import { prepareCommand } from './cli-spelling.js';
|
|
9
|
-
|
|
9
|
+
/** A tenant resource key — an Integration Service connection id or an Orchestrator folder key — is a GUID. */
|
|
10
|
+
export const GUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
/**
|
|
12
|
+
* What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
|
|
13
|
+
* message is the sentence `check` prints; the `code` lets `compile --no-check`
|
|
14
|
+
* print it in the same `✗ [CODE]` shape as the check pass instead of a bare line.
|
|
15
|
+
*/
|
|
16
|
+
export class BindingRefusal extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'BindingRefusal';
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function bindingSelfNameMessage(where, name, attribute, target) {
|
|
10
25
|
return `${where}: binding "${name}" resolves to its own name, so the ${attribute} would compile to the `
|
|
11
26
|
+ `literal string "${name}" and the run would fault with "'Connection' has an invalid GUID value: `
|
|
12
|
-
+ `'${name}'". Set its "resourceKey" to the real id: \`${
|
|
27
|
+
+ `'${name}'". Set its "resourceKey" to the real id: \`${prepareFor(target)}\` `
|
|
13
28
|
+ `discovers the connection and writes both the connection and the folder entry into bindings.json; by hand, `
|
|
14
29
|
+ `read Id and FolderKey from \`uip is connections list --all-folders --output json\` (the default listing `
|
|
15
30
|
+ `shows only your own folder, so a connection shared from another folder is invisible without --all-folders).`;
|
|
16
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The names a populated bindings.json declares when it does NOT declare `name`,
|
|
34
|
+
* else `undefined`.
|
|
35
|
+
*
|
|
36
|
+
* `undefined` — nothing to refuse — when no bindings.json is loaded, when it
|
|
37
|
+
* declares no entries (offline authoring: the symbolic name is the only honest
|
|
38
|
+
* placeholder there), when `name` is itself a GUID (an author may write the
|
|
39
|
+
* resource key directly), or when any entry answers to `name`. "Answers" is the
|
|
40
|
+
* union of `entry()`, `resolve()` and `declared()`, so an entry that is declared
|
|
41
|
+
* but carries no `resourceKey` is NOT refused here — it stays the
|
|
42
|
+
* `CONNECTION_STUB` warning, which is the route a simulated or validate-only task
|
|
43
|
+
* with nothing real to bind is meant to take.
|
|
44
|
+
*/
|
|
45
|
+
export function undeclaredBinding(bindings, name) {
|
|
46
|
+
if (!bindings || typeof bindings.declared !== 'function')
|
|
47
|
+
return undefined;
|
|
48
|
+
const declared = bindings.declared();
|
|
49
|
+
if (declared.length === 0)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (GUID_RE.test(name))
|
|
52
|
+
return undefined;
|
|
53
|
+
if (declared.includes(name))
|
|
54
|
+
return undefined;
|
|
55
|
+
if (typeof bindings.entry === 'function' && bindings.entry(name) !== undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (bindings.resolve(name) !== undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
return declared;
|
|
60
|
+
}
|
|
61
|
+
function prepareFor(target) {
|
|
62
|
+
return prepareCommand(target?.key ?? '<connector-key>', target?.action ?? '<action>');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Why compile refuses `name`, and how to bind it.
|
|
66
|
+
*
|
|
67
|
+
* It names NO declared label as the one to use. bindings.json rows carry a label,
|
|
68
|
+
* a resource key and a kind (`ConnectionId` / `FolderKey`), never the connector
|
|
69
|
+
* they were bound for, so compile cannot tell which declared label — if any — is
|
|
70
|
+
* this connector's. Suggesting one sent a Data Service step to `connection:
|
|
71
|
+
* 'slack'` (the only `ConnectionId` row), and following that compiled the Slack
|
|
72
|
+
* connection id into the Data Service node with no warning at all: a real GUID of
|
|
73
|
+
* the wrong connector, which nothing downstream detects. The partial-prepare shape
|
|
74
|
+
* (ipe-drive-to-slack 09-23: `slack` bound, `drive` not yet) is exactly where
|
|
75
|
+
* that advice was on offer. So the message lists what is declared, says that a
|
|
76
|
+
* row is only reusable if it was bound for THIS connector, and gives the
|
|
77
|
+
* `prepare` command for this step's own connector and operation.
|
|
78
|
+
*/
|
|
79
|
+
export function bindingUndeclaredMessage(where, name, attribute, declared, target) {
|
|
80
|
+
const key = attribute === 'connection' ? 'connection id' : 'folder key';
|
|
81
|
+
const flag = attribute === 'connection' ? '--bind-connection' : '--bind-folder';
|
|
82
|
+
return `${where}: ${attribute} "${name}" is not declared in bindings.json (it declares: ${declared.join(', ')}), `
|
|
83
|
+
+ `so the .flow would carry "${name}" as its ${key} and the run would fault with `
|
|
84
|
+
+ `"'Connection' has an invalid GUID value: '${name}'". Bind this one: `
|
|
85
|
+
+ `\`${prepareFor(target)} ${flag} ${name}\` (several candidates: add \`--connection-id <id>\` from the list it prints). `
|
|
86
|
+
+ `Switch to a declared label only if that row was bound for this same connector: bindings.json does not record `
|
|
87
|
+
+ `which connector a row belongs to, and another connector's ${key} compiles without any warning and faults only at run time. `
|
|
88
|
+
+ `A connector with nothing real to bind yet (a simulated or validate-only task, or one not prepared yet) `
|
|
89
|
+
+ `declares the label with a placeholder resourceKey instead, which compiles with a CONNECTION_STUB warning — `
|
|
90
|
+
+ `never an invented GUID.`;
|
|
91
|
+
}
|