@uipath/maestro-builder-sdk 6.16.8 → 6.16.10

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 CHANGED
@@ -166,7 +166,7 @@ could hand you spans for a version you do not have.
166
166
  | `ixpExtract` | function | `dist/core/actions.d.ts:2670-2724` | Actions | Extract fields from a document with a published IxP project — the platform's **Extract** node (`uipath.ixp.*`, service… |
167
167
  | `IxpExtractInputs` | interface | `dist/core/actions.d.ts:879-953` | Option shapes | 9 field(s) |
168
168
  | `IxpToolRef` | interface | `dist/core/actions.d.ts:1891-1929` | Supporting types | A published IxP (Intelligent eXtraction Platform) project as a tool — the agent decides when to extract a document and… |
169
- | `js` | function | `dist/core/expr.d.ts:241-252` | Expressions and types | Build a raw JS *expression* (a condition or computation). |
169
+ | `js` | function | `dist/core/expr.d.ts:241-276` | Expressions and types | Build a raw JS *expression* (a condition or computation). |
170
170
  | `lit` | function | `dist/core/expr.d.ts:22-28` | Expressions and types | A constant value baked directly into a node input. |
171
171
  | `lookup` | function | `dist/core/lookups.d.ts:95-102` | Generated descriptors | Begin resolving a lookup field on a generated descriptor. |
172
172
  | `lookup` | function | `dist/core/lookups.d.ts:103-122` | Generated descriptors | Begin resolving a lookup field addressed by connector key and action. |
@@ -232,7 +232,7 @@ could hand you spans for a version you do not have.
232
232
  | `summarize` | function | `dist/core/actions.d.ts:2594-2634` | Actions | Summarize a document with citations — the platform's **Summarize** node. |
233
233
  | `SummarizeInputs` | interface | `dist/core/actions.d.ts:806-838` | Option shapes | 3 field(s) |
234
234
  | `SwitchArm` | interface | `dist/flow-sdk.d.ts:329-334` | Supporting types | One arm of a built `.switch` (the serializer's view: body already collected). |
235
- | `tmpl` | function | `dist/core/expr.d.ts:253-263` | Expressions and types | Build a JS *string template* (for URLs, messages, etc.). |
235
+ | `tmpl` | function | `dist/core/expr.d.ts:277-287` | Expressions and types | Build a JS *string template* (for URLs, messages, etc.). |
236
236
  | `ToolRef` | type | `dist/core/actions.d.ts:1657-1666` | Supporting types | One tool on an inline agent — a discriminated union over the kinds the tenant's registry actually serves… |
237
237
  | `transform` | function | `dist/core/actions.d.ts:2496-2541` | Actions | Declare a Transform action — a chain of declarative operations over a collection. |
238
238
  | `Transformation` | type | `dist/core/actions.d.ts:248-249` | Supporting types | What a map does to a field's value. |
@@ -244,8 +244,8 @@ could hand you spans for a version you do not have.
244
244
  | `TriggerMeta` | interface | `dist/core/connectors.d.ts:120-129` | Generated descriptors | Runtime metadata a generated trigger descriptor carries. |
245
245
  | `TriggerOptions` | interface | `dist/core/actions.d.ts:3410-3457` | Option shapes | The typed `onEvent`/`waitForEvent` options — everything an EventSubscription carries except `connector`/`event`, which… |
246
246
  | `TriggerSpec` | type | `dist/flow-sdk.d.ts:150-168` | Supporting types | What starts the flow. |
247
- | `TypeDesc` | type | `dist/core/expr.d.ts:328` | Supporting types | |
248
- | `types` | const | `dist/core/expr.d.ts:312-327` | Expressions and types | Variable type descriptors (map to Flow variable `type` values). |
247
+ | `TypeDesc` | type | `dist/core/expr.d.ts:379` | Supporting types | |
248
+ | `types` | const | `dist/core/expr.d.ts:363-378` | Expressions and types | Variable type descriptors (map to Flow variable `type` values). |
249
249
  | `unresolvedLookupMessage` | function | `dist/core/lookups.d.ts:180-189` | Generated descriptors | The message for a lookup nothing has resolved — the command that fixes it. |
250
250
  | `v` | function | `dist/core/expr.d.ts:29-41` | Expressions and types | Reference a flow variable or output by name → `$vars.<name>`. |
251
251
  | `VarDecl` | interface | `dist/flow-sdk.d.ts:47-59` | Supporting types | 7 field(s) |
@@ -337,7 +337,7 @@ could hand you spans for a version you do not have.
337
337
  | `TriggerDescriptor` | type | `dist/core/connectors.d.ts:130-138` | Supporting types | A generated, typed connector-trigger descriptor: TriggerMeta branded with phantom `where`/output types. |
338
338
  | `TriggerMeta` | interface | `dist/core/connectors.d.ts:120-129` | Supporting types | Runtime metadata a generated trigger descriptor carries. |
339
339
  | `TriggerOptions` | interface | `dist/core/actions.d.ts:3410-3457` | Option shapes | The typed `onEvent`/`waitForEvent` options — everything an EventSubscription carries except `connector`/`event`, which… |
340
- | `TypeDesc` | type | `dist/core/expr.d.ts:328` | Supporting types | |
340
+ | `TypeDesc` | type | `dist/core/expr.d.ts:379` | Supporting types | |
341
341
  | `UnresolvedReferenceTaskKind` | type | `dist/case/case-sdk.d.ts:358-359` | Supporting types | Published-resource task kinds that the Case schema permits as unresolved skeletons. |
342
342
  | `WaitConnectorPlaceholderSpec` | interface | `dist/case/case-sdk.d.ts:360-370` | Supporting types | A `wait-for-connector` subscription: suspend on an Integration Service event. |
343
343
  | `WaitConnectorSpec` | type | `dist/case/case-sdk.d.ts:371-375` | Supporting types | A placeholder connector/operation pair, or a library-resolved event subscription using the same symbolic shape as Flow… |
@@ -436,7 +436,7 @@ could hand you spans for a version you do not have.
436
436
  | `TimerLike` | type | `dist/bpmn/bpmn-sdk.d.ts:40-41` | Supporting types | A timer as an ISO-8601 duration string (shorthand for `{ duration }`) or a full spec. |
437
437
  | `TimerSpec` | interface | `dist/bpmn/bpmn-sdk.d.ts:31-39` | Supporting types | ISO-8601 timer specification (one of duration / date / cycle). |
438
438
  | `TypedContextRow` | interface | `dist/bpmn/typed-node.d.ts:155-186` | Supporting types | One `uipath:context` input row, spelled out. |
439
- | `TypeDesc` | type | `dist/core/expr.d.ts:328` | Supporting types | |
439
+ | `TypeDesc` | type | `dist/core/expr.d.ts:379` | Supporting types | |
440
440
  | `TypedInputRow` | interface | `dist/bpmn/typed-node.d.ts:187-209` | Supporting types | One `uipath:input` payload row spelled out — the `separateInputs` counterpart of TypedContextRow. |
441
441
  | `TypedOutputRow` | interface | `dist/bpmn/typed-node.d.ts:246-285` | Supporting types | 9 field(s) |
442
442
  | `VarDirection` | type | `dist/bpmn/bpmn-sdk.d.ts:361-365` | Supporting types | How a variable is exposed: `'input'` (read-only entry input), `'output'` (a return value), or `'inputOutput'` (a mutable… |
package/dist/check.js CHANGED
@@ -9,6 +9,7 @@ import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProje
9
9
  import { FlowNode } from './core/node-classes.js';
10
10
  import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
11
11
  import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
12
+ import { checkCookedEscapes } from './flow-expr-check.js';
12
13
  import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
13
14
  import { prepareCommand } from './core/cli-spelling.js';
14
15
  import { editDistance } from './core/edit-distance.js';
@@ -25,6 +26,15 @@ export function check(built, opts = {}) {
25
26
  for (const { id, message } of connectionConnectorConflicts(uses, opts)) {
26
27
  diags.push({ level: 'error', code: 'CONNECTION_CONNECTOR_CONFLICT', step: id, message });
27
28
  }
29
+ // Once, from the top: the walk reaches child flows itself, so running it per
30
+ // `checkFlow` would report a child's escape twice.
31
+ for (const d of checkCookedEscapes(built)) {
32
+ diags.push({
33
+ level: d.level, code: d.code, message: `In ${d.where}: ${d.message}`,
34
+ ...(d.step !== undefined ? { step: d.step } : {}),
35
+ ...(d.suggestion ? { suggestion: d.suggestion } : {}),
36
+ });
37
+ }
28
38
  return diags;
29
39
  }
30
40
  /**
@@ -21,7 +21,7 @@ import { reportDiagnostics, reportResult } from './cli-result.js';
21
21
  import { writeFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
22
22
  import { basename, dirname, join, resolve } from 'node:path';
23
23
  import { resolveFlowFile, loadBuiltFlow } from './load.js';
24
- import { serializeWithSidecars, connectorInputErrors } from './serialize.js';
24
+ import { serializeWithSidecars, connectorInputErrors, compileOwnsResource } from './serialize.js';
25
25
  import { check } from './check.js';
26
26
  import { byoaCheckOpts, eventResolveErrorFor, openCliLibrary } from './cli-library.js';
27
27
  import { Bindings } from './bindings.js';
@@ -31,32 +31,6 @@ import { ensureTypeScriptRuntime } from './node-runtime.js';
31
31
  import { defaultBindingsFile } from './workdir.js';
32
32
  import { BindingRefusal } from './core/binding-messages.js';
33
33
  import { ByoaRefusal } from './core/byoa.js';
34
- /**
35
- * The tool `type` values `compile` writes. NOT every `$resourceType: 'tool'`:
36
- * the designer writes kinds the SDK cannot derive into the same directory, so
37
- * pruning by `$resourceType` alone would delete theirs.
38
- *
39
- * `integration` (connector tools) is compile's since #867: compile writes the
40
- * file under the tool node's own `source` id, the same id and content `uip agent
41
- * refresh --inline-in-flow` derives from that node, so a file under any other id
42
- * belongs to a tool node the flow no longer has.
43
- */
44
- const COMPILE_OWNED_TOOL_TYPES = new Set([
45
- 'process', 'api', 'processOrchestration', 'flow', 'agent', 'function', 'integration',
46
- ]);
47
- /**
48
- * Does `compile` own this resource file — i.e. would this run have written it,
49
- * so an unwritten one is stale rather than someone else's?
50
- *
51
- * Everything it says no to belongs to the designer (escalation, mcp, and the
52
- * kinds the SDK cannot derive).
53
- */
54
- function compileOwnsResource(body) {
55
- const kind = body?.$resourceType;
56
- if (kind === 'context')
57
- return true;
58
- return kind === 'tool' && COMPILE_OWNED_TOOL_TYPES.has(String(body?.type));
59
- }
60
34
  export async function run(argv) {
61
35
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
62
36
  console.error('usage: compile <flow.ts | BaseName> [-o out.flow] [--library <dir>] [--bindings <file>]');
@@ -201,10 +175,11 @@ export async function run(argv) {
201
175
  // gone. Measured 2026-09-25 — compile with a context, remove it, recompile:
202
176
  // the file survives and refresh still reports `Resources: 1`.
203
177
  //
204
- // Only the types THIS compile emits are pruned. `uip agent refresh` generates
205
- // connector-tool resources into the same directory, and the designer writes
206
- // the families the SDK cannot, so anything else found there is someone else's
207
- // and is left alone.
178
+ // Only the types THIS compile emits are pruned — `compileOwnsResource`, which
179
+ // lives beside the writers and reads their tables. The designer writes the
180
+ // families the SDK cannot into the same directory (including `internal`
181
+ // built-ins such as `load-attachments`), so anything else found there is
182
+ // someone else's and is left alone.
208
183
  const staleBindings = [];
209
184
  const owned = new Set(sidecars
210
185
  .filter((s) => /\/resources\/[^/]+\/resource\.json$/.test(s.path))
@@ -238,7 +213,9 @@ export async function run(argv) {
238
213
  const key = typeof named === 'string' && named !== '' ? named : undefined;
239
214
  // A connector tool's binding is a `connection` row, and `mergeBindingsV2`
240
215
  // re-derives every connection row from the flow below — nothing to retract.
241
- if (key !== undefined && body?.type !== 'integration') {
216
+ // A built-in (`internal`) tool binds nothing at all: retracting a `process`
217
+ // row under its label could only remove a real process tool's binding.
218
+ if (key !== undefined && body?.type !== 'integration' && body?.type !== 'internal') {
242
219
  // Keyed the way refresh keys it: an index by its name, a deployed-resource
243
220
  // tool under `process` whatever its own type says.
244
221
  staleBindings.push({ resource: resourceType === 'context' ? 'index' : 'process', key });
@@ -29,6 +29,37 @@
29
29
  * and gets back diagnostics with no location. The caller stamps the location
30
30
  * (which node / task / field) it came from.
31
31
  */
32
+ import type { CookedEscape } from './expr.js';
33
+ /** What one cooked escape did to an expression. */
34
+ export interface CookedEscapeEffect {
35
+ /** The escape as written, e.g. `\d`. */
36
+ escape: string;
37
+ /** What TypeScript turned it into. */
38
+ cooked: string;
39
+ /** `meaning`: the expression parses but means something else. `syntax`: it no longer parses. */
40
+ effect: 'meaning' | 'syntax';
41
+ /** The escape was invalid, so `js` wrote `undefined` for its whole part. */
42
+ invalid?: true;
43
+ }
44
+ /**
45
+ * Which of an expression's cooked escapes changed it, and how. Harmless ones are
46
+ * left out. See the block comment above for the rule.
47
+ */
48
+ export declare function cookedEscapeEffects(expr: string, escapes: readonly CookedEscape[]): CookedEscapeEffect[];
49
+ /** The escape spelled so it survives: one more backslash. */
50
+ export declare function keptEscape(escape: string): string;
51
+ /** The sentence that explains one cooked escape, shared by the warning and EXPR_SYNTAX. */
52
+ export declare function cookedEscapeSentence(e: CookedEscapeEffect): string;
53
+ /**
54
+ * A diagnostic per escape that changed what an expression means. Location is
55
+ * stamped by the caller.
56
+ *
57
+ * * JS_ESCAPE_COOKED (warning) — a valid escape cooked to another character:
58
+ * `/\d+/` became `/d+/`. The expression may still be what the author wants.
59
+ * * JS_ESCAPE_INVALID (error) — an invalid escape (`\1`, `\x4`): the whole part
60
+ * of the template became the word `undefined`, which is never what was meant.
61
+ */
62
+ export declare function cookedEscapeDiagnostics(expr: string, escapes: readonly CookedEscape[]): ExprDiagnostic[];
32
63
  /** A `$vars.*` reference found in an expression. */
33
64
  export interface ExprRef {
34
65
  /** The first segment after `$vars.` — the name the reference is rooted at. */
@@ -90,4 +121,4 @@ export declare function extractRefs(expr: string, namespace?: string): ExprRef[]
90
121
  * message). An empty result means "nothing this check is sure is wrong" — never
91
122
  * "fully valid"; the `validate` CLI is what proves the rest.
92
123
  */
93
- export declare function checkExpression(expr: string, scope: ExprScope): ExprDiagnostic[];
124
+ export declare function checkExpression(expr: string, scope: ExprScope, escapes?: readonly CookedEscape[]): ExprDiagnostic[];
@@ -50,16 +50,129 @@ const JSONPATH_WILDCARD = /\[\s*(?:\*|\?|'[^']*'|"[^"]*")\s*\]|\.\.[A-Za-z_$]/;
50
50
  * this adds no dependency.
51
51
  */
52
52
  function syntaxError(expr) {
53
+ const problems = parseExpression(expr).problems;
54
+ if (problems.length === 0)
55
+ return undefined;
56
+ return ts.flattenDiagnosticMessageText(problems[0].messageText, ' ');
57
+ }
58
+ function parseExpression(expr) {
53
59
  const source = ts.createSourceFile('expression.ts', `(${expr}\n)`, ts.ScriptTarget.ES2020,
54
60
  /* setParentNodes */ false);
55
61
  // `parseDiagnostics` is where the scanner records syntax problems. It is not on
56
62
  // the public `SourceFile` type, hence the cast; there is no public API that
57
63
  // parses a fragment and hands back its syntax diagnostics without also running
58
64
  // a full program's type-check.
59
- const problems = source.parseDiagnostics;
60
- if (!problems || problems.length === 0)
61
- return undefined;
62
- return ts.flattenDiagnosticMessageText(problems[0].messageText, ' ');
65
+ const problems = source.parseDiagnostics ?? [];
66
+ return { source, problems };
67
+ }
68
+ /** Replace each chosen escape's cooked text with what was typed. Escapes are disjoint and in order. */
69
+ function retype(expr, escapes, keepCooked) {
70
+ let out = '';
71
+ let pos = 0;
72
+ for (const e of escapes) {
73
+ out += expr.slice(pos, e.at) + (e === keepCooked ? e.cooked : e.typed);
74
+ pos = e.at + e.cooked.length;
75
+ }
76
+ return out + expr.slice(pos);
77
+ }
78
+ /** The value a leaf denotes — a name, or a literal's value (a regular expression's is its source). */
79
+ function leafValue(node) {
80
+ if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node))
81
+ return node.text;
82
+ // NumericLiteral … TemplateTail: every literal and template piece carries `.text`.
83
+ if (node.kind >= ts.SyntaxKind.FirstLiteralToken && node.kind <= ts.SyntaxKind.LastTemplateToken) {
84
+ return node.text;
85
+ }
86
+ return undefined;
87
+ }
88
+ function children(node) {
89
+ const out = [];
90
+ ts.forEachChild(node, (c) => { out.push(c); });
91
+ return out;
92
+ }
93
+ function sameTree(a, b) {
94
+ if (a.kind !== b.kind || leafValue(a) !== leafValue(b))
95
+ return false;
96
+ const ca = children(a);
97
+ const cb = children(b);
98
+ return ca.length === cb.length && ca.every((c, i) => sameTree(c, cb[i]));
99
+ }
100
+ /**
101
+ * Which of an expression's cooked escapes changed it, and how. Harmless ones are
102
+ * left out. See the block comment above for the rule.
103
+ */
104
+ export function cookedEscapeEffects(expr, escapes) {
105
+ if (escapes.length === 0)
106
+ return [];
107
+ const sorted = [...escapes].sort((a, b) => a.at - b.at);
108
+ const typed = parseExpression(retype(expr, sorted));
109
+ if (typed.problems.length)
110
+ return [];
111
+ const effects = [];
112
+ const seen = new Set();
113
+ for (const e of sorted) {
114
+ const one = parseExpression(retype(expr, sorted, e));
115
+ const effect = one.problems.length ? 'syntax' : sameTree(typed.source, one.source) ? undefined : 'meaning';
116
+ if (!effect || seen.has(`${effect} ${e.escape}`))
117
+ continue;
118
+ seen.add(`${effect} ${e.escape}`);
119
+ effects.push({ escape: e.escape, cooked: e.cooked, effect, ...(e.invalid ? { invalid: true } : {}) });
120
+ }
121
+ return effects;
122
+ }
123
+ const CHARACTER_NAMES = {
124
+ '\b': 'backspace', '\f': 'form feed', '\n': 'line feed', '\r': 'carriage return',
125
+ '\t': 'tab', '\v': 'vertical tab', '\0': 'NUL', '\u2028': 'line separator', '\u2029': 'paragraph separator',
126
+ };
127
+ /** How to say what an escape became, in a message. */
128
+ function describeCooked(cooked) {
129
+ if (cooked === '')
130
+ return 'nothing (a line continuation)';
131
+ const name = CHARACTER_NAMES[cooked];
132
+ if (name)
133
+ return `U+${cooked.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')} (${name})`;
134
+ return `\`${cooked}\``;
135
+ }
136
+ /** The escape spelled so it survives: one more backslash. */
137
+ export function keptEscape(escape) {
138
+ return '\\' + escape;
139
+ }
140
+ /** The sentence that explains one cooked escape, shared by the warning and EXPR_SYNTAX. */
141
+ export function cookedEscapeSentence(e) {
142
+ const fix = `write \`${keptEscape(e.escape)}\` to keep the backslash.`;
143
+ if (e.invalid) {
144
+ return `\`${e.escape}\` is not a valid escape in a js\`\` template, so TypeScript drops that whole part of `
145
+ + `the template and the expression holds the word \`undefined\` in its place; ${fix}`;
146
+ }
147
+ return `\`${e.escape}\` in a js\`\` template is read by TypeScript as ${describeCooked(e.cooked)} before the `
148
+ + `SDK sees it; ${fix}`;
149
+ }
150
+ /**
151
+ * A diagnostic per escape that changed what an expression means. Location is
152
+ * stamped by the caller.
153
+ *
154
+ * * JS_ESCAPE_COOKED (warning) — a valid escape cooked to another character:
155
+ * `/\d+/` became `/d+/`. The expression may still be what the author wants.
156
+ * * JS_ESCAPE_INVALID (error) — an invalid escape (`\1`, `\x4`): the whole part
157
+ * of the template became the word `undefined`, which is never what was meant.
158
+ */
159
+ export function cookedEscapeDiagnostics(expr, escapes) {
160
+ return cookedEscapeEffects(expr, escapes)
161
+ .filter((e) => e.effect === 'meaning')
162
+ .map((e) => e.invalid
163
+ ? {
164
+ level: 'error',
165
+ code: 'JS_ESCAPE_INVALID',
166
+ message: cookedEscapeSentence(e),
167
+ suggestion: keptEscape(e.escape),
168
+ }
169
+ : {
170
+ level: 'warning',
171
+ code: 'JS_ESCAPE_COOKED',
172
+ message: `${cookedEscapeSentence(e)} The expression parses, so nothing else reports it, but it no longer `
173
+ + `means what was typed.`,
174
+ suggestion: keptEscape(e.escape),
175
+ });
63
176
  }
64
177
  const DEFAULT_NAMESPACE = '$vars';
65
178
  /**
@@ -149,7 +262,7 @@ function nearMiss(name, roots) {
149
262
  * message). An empty result means "nothing this check is sure is wrong" — never
150
263
  * "fully valid"; the `validate` CLI is what proves the rest.
151
264
  */
152
- export function checkExpression(expr, scope) {
265
+ export function checkExpression(expr, scope, escapes = []) {
153
266
  const namespace = scope.namespace ?? DEFAULT_NAMESPACE;
154
267
  const noun = scope.noun ?? 'input, variable, or step';
155
268
  const diags = [];
@@ -158,11 +271,16 @@ export function checkExpression(expr, scope) {
158
271
  // references, and reporting both would just be noise on one root cause.
159
272
  const syntax = scope.syntax === 'javascript' ? syntaxError(expr) : undefined;
160
273
  if (syntax) {
274
+ // A `js` template whose escape broke the parse: say which one, or the author
275
+ // goes looking for an unterminated string they never wrote.
276
+ const cause = cookedEscapeEffects(expr, escapes).filter((e) => e.effect === 'syntax');
161
277
  return [{
162
278
  level: 'error',
163
279
  code: 'EXPR_SYNTAX',
164
- message: `Expression is not valid JavaScript: ${syntax}`,
165
- ...(JSONPATH_WILDCARD.test(expr)
280
+ message: `Expression is not valid JavaScript: ${syntax}`
281
+ + cause.map((e) => ` ${cookedEscapeSentence(e)}`).join(''),
282
+ ...(cause.length ? { suggestion: keptEscape(cause[0].escape) } : {}),
283
+ ...(!cause.length && JSONPATH_WILDCARD.test(expr)
166
284
  ? {
167
285
  suggestion: 'This looks like JSONPath. The runtime evaluates JavaScript, so index or map '
168
286
  + 'explicitly — `…issues` for the whole array, `…issues.map(i => i.key)` for one field.',
@@ -243,11 +243,35 @@ export declare function err(step: string, field?: ErrorEnvelopeField): Expr;
243
243
  * Interpolated `Expr`s contribute their reference; other values are JSON-encoded.
244
244
  * e.g. js`${input('from')} !== ${input('to')}` → `$vars.from !== $vars.to`
245
245
  *
246
+ * @remarks
247
+ * **Backslashes are read by TypeScript first.** The expression is the template's
248
+ * COOKED text: TypeScript applies every backslash escape before `js` sees it, the
249
+ * same as in any template literal. So a regular expression typed with one
250
+ * backslash loses it — `` js`/\d+/.test(${x})` `` emits `/d+/.test(…)`, which
251
+ * matches the letter `d`, and `\b` becomes the backspace character, not a word
252
+ * boundary. Write the backslash twice to keep it: `` js`/\\d+/.test(${x})` ``
253
+ * emits `/\d+/.test(…)`. Likewise `` \` `` is a backtick, and `\${` keeps `${` from
254
+ * starting an interpolation.
255
+ * This is the spelling `decompile` writes.
256
+ *
257
+ * `check` reports an escape that changed what the expression means
258
+ * (JS_ESCAPE_COOKED), and an `EXPR_SYNTAX` error names the escape when one broke
259
+ * the parse (`'\n'` in a string literal becomes a real line break). An escape
260
+ * that is not valid in a template at all (`\1`, `\x4`) leaves TypeScript no text
261
+ * for that part, so the expression would hold the word `undefined`: `check`
262
+ * refuses it (JS_ESCAPE_INVALID).
263
+ *
246
264
  * @param strings - The template's literal parts, supplied by the tag call.
247
265
  * @param vals - Interpolated values. An {@link Expr} contributes its reference;
248
266
  * anything else is JSON-encoded.
249
267
  * @returns An {@link Expr} carrying the composed JavaScript expression.
250
268
  * @see tmpl
269
+ * @example
270
+ * ```ts
271
+ * .return({ hasDigit: js`/\\d+/.test(${input('text')})` })
272
+ * ```
273
+ * @enforcedBy JS_ESCAPE_COOKED Write a backslash twice inside `js`: TypeScript
274
+ * reads `\d` as `d` before the SDK sees it.
251
275
  */
252
276
  export declare function js(strings: TemplateStringsArray, ...vals: unknown[]): Expr;
253
277
  /**
@@ -261,6 +285,33 @@ export declare function js(strings: TemplateStringsArray, ...vals: unknown[]): E
261
285
  * @see js
262
286
  */
263
287
  export declare function tmpl(strings: TemplateStringsArray, ...vals: unknown[]): Expr;
288
+ /**
289
+ * One backslash escape TypeScript applied inside a `js` template.
290
+ *
291
+ * @internal Recorded by `js`, read by `check`; not an authoring value.
292
+ */
293
+ export interface CookedEscape {
294
+ /** Offset in `Expr.js` where the cooked text starts. */
295
+ readonly at: number;
296
+ /** The text TypeScript produced, as it sits in `Expr.js` (`d`, U+0008, `undefined`). */
297
+ readonly cooked: string;
298
+ /** What the author typed in its place, read as JavaScript (`\d`). */
299
+ readonly typed: string;
300
+ /** The escape as written, for messages (`\d`). */
301
+ readonly escape: string;
302
+ /**
303
+ * An INVALID template escape (`\1`, `\x4`): TypeScript gives the whole part no
304
+ * cooked value, and `js` writes the word `undefined` in its place.
305
+ */
306
+ readonly invalid?: true;
307
+ }
308
+ /**
309
+ * The backslash escapes TypeScript cooked away inside this expression's `js`
310
+ * templates, nested ones included. Empty for anything else.
311
+ *
312
+ * @internal Read by `check`; not an authoring factory.
313
+ */
314
+ export declare function cookedEscapesOf(val: unknown): readonly CookedEscape[];
264
315
  /**
265
316
  * Coerce a raw value or Expr into an Expr.
266
317
  *
package/dist/core/expr.js CHANGED
@@ -260,20 +260,60 @@ export function err(step, field) {
260
260
  * Interpolated `Expr`s contribute their reference; other values are JSON-encoded.
261
261
  * e.g. js`${input('from')} !== ${input('to')}` → `$vars.from !== $vars.to`
262
262
  *
263
+ * @remarks
264
+ * **Backslashes are read by TypeScript first.** The expression is the template's
265
+ * COOKED text: TypeScript applies every backslash escape before `js` sees it, the
266
+ * same as in any template literal. So a regular expression typed with one
267
+ * backslash loses it — `` js`/\d+/.test(${x})` `` emits `/d+/.test(…)`, which
268
+ * matches the letter `d`, and `\b` becomes the backspace character, not a word
269
+ * boundary. Write the backslash twice to keep it: `` js`/\\d+/.test(${x})` ``
270
+ * emits `/\d+/.test(…)`. Likewise `` \` `` is a backtick, and `\${` keeps `${` from
271
+ * starting an interpolation.
272
+ * This is the spelling `decompile` writes.
273
+ *
274
+ * `check` reports an escape that changed what the expression means
275
+ * (JS_ESCAPE_COOKED), and an `EXPR_SYNTAX` error names the escape when one broke
276
+ * the parse (`'\n'` in a string literal becomes a real line break). An escape
277
+ * that is not valid in a template at all (`\1`, `\x4`) leaves TypeScript no text
278
+ * for that part, so the expression would hold the word `undefined`: `check`
279
+ * refuses it (JS_ESCAPE_INVALID).
280
+ *
263
281
  * @param strings - The template's literal parts, supplied by the tag call.
264
282
  * @param vals - Interpolated values. An {@link Expr} contributes its reference;
265
283
  * anything else is JSON-encoded.
266
284
  * @returns An {@link Expr} carrying the composed JavaScript expression.
267
285
  * @see tmpl
286
+ * @example
287
+ * ```ts
288
+ * .return({ hasDigit: js`/\\d+/.test(${input('text')})` })
289
+ * ```
290
+ * @enforcedBy JS_ESCAPE_COOKED Write a backslash twice inside `js`: TypeScript
291
+ * reads `\d` as `d` before the SDK sees it.
268
292
  */
269
293
  export function js(strings, ...vals) {
270
294
  let out = '';
295
+ const escapes = [];
271
296
  strings.forEach((s, i) => {
297
+ // Recorded BEFORE `out` grows, so each offset is where the cooked text lands.
298
+ const raw = strings.raw?.[i];
299
+ if (raw !== undefined)
300
+ for (const e of segmentEscapes(raw, s))
301
+ escapes.push({ ...e, at: out.length + e.at });
272
302
  out += s;
273
- if (i < vals.length)
303
+ if (i < vals.length) {
304
+ const at = out.length;
274
305
  out += exprText(vals[i]);
306
+ for (const e of cookedEscapesOf(vals[i]))
307
+ escapes.push({ ...e, at: at + e.at });
308
+ }
275
309
  });
276
- return new Expr(out.trim());
310
+ const lead = out.length - out.trimStart().length;
311
+ const text = out.trim();
312
+ // An escape cooked into whitespace the trim removed is gone from the expression.
313
+ const kept = escapes
314
+ .filter((e) => e.at >= lead && e.at + e.cooked.length <= lead + text.length)
315
+ .map((e) => ({ ...e, at: e.at - lead }));
316
+ return withCookedEscapes(new Expr(text), kept);
277
317
  }
278
318
  /**
279
319
  * Build a JS *string template* (for URLs, messages, etc.).
@@ -287,16 +327,129 @@ export function js(strings, ...vals) {
287
327
  */
288
328
  export function tmpl(strings, ...vals) {
289
329
  let body = '';
330
+ const escapes = [];
290
331
  strings.forEach((s, i) => {
291
332
  body += s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
292
- if (i < vals.length)
333
+ if (i < vals.length) {
334
+ // tmpl's OWN escapes are cooked on purpose (`tmpl`a\nb`` is a line break,
335
+ // as in a template literal). Only an interpolated js`…`'s lost escapes
336
+ // travel on, so `check` still sees them once the Expr is nested.
337
+ const at = 1 + body.length + 2; // past the opening backtick and `${`
293
338
  body += '${' + exprText(vals[i]) + '}';
339
+ for (const e of cookedEscapesOf(vals[i]))
340
+ escapes.push({ ...e, at: at + e.at });
341
+ }
294
342
  });
295
- return new Expr('`' + body + '`');
343
+ return withCookedEscapes(new Expr('`' + body + '`'), escapes);
296
344
  }
297
345
  function exprText(val) {
298
346
  return val instanceof Expr ? val.js : JSON.stringify(val);
299
347
  }
348
+ const COOKED_ESCAPES = Symbol.for('@uipath/maestro-builder-sdk/cookedEscapes');
349
+ /**
350
+ * The backslash escapes TypeScript cooked away inside this expression's `js`
351
+ * templates, nested ones included. Empty for anything else.
352
+ *
353
+ * @internal Read by `check`; not an authoring factory.
354
+ */
355
+ export function cookedEscapesOf(val) {
356
+ if (val === null || typeof val !== 'object')
357
+ return [];
358
+ return val[COOKED_ESCAPES] ?? [];
359
+ }
360
+ function withCookedEscapes(e, escapes) {
361
+ if (escapes.length)
362
+ Object.defineProperty(e, COOKED_ESCAPES, { value: Object.freeze(escapes), enumerable: false });
363
+ return e;
364
+ }
365
+ /**
366
+ * The three escapes a `js` template needs to say what it means — and exactly the
367
+ * three `decompile` writes: `\\` (one backslash), `` \` `` (a backtick) and `\${`
368
+ * (so `${` is not an interpolation). A `\$` NOT before `{` is not one of them: a
369
+ * template needs no escape there, and `` js`/\$/` `` cooks a literal-dollar regex
370
+ * into `/$/`, an end anchor. Every other escape is TypeScript's, not the author's
371
+ * JavaScript.
372
+ */
373
+ function isTemplateEscape(raw, i) {
374
+ const next = raw[i + 1];
375
+ return next === '\\' || next === '`' || (next === '$' && raw[i + 2] === '{');
376
+ }
377
+ const SINGLE_ESCAPES = { b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v' };
378
+ /**
379
+ * The escapes in one template part, at offsets in its cooked text. `raw` is the
380
+ * part as typed (`strings.raw[i]`), `cooked` what TypeScript made of it — or
381
+ * `undefined` when an escape is not valid in a template (`\1`, `\x4`): the
382
+ * whole part is then lost, and `js` appends the word `undefined` in its place.
383
+ *
384
+ * The cooking rules are ECMAScript's TemplateCharacter grammar. The rebuilt text
385
+ * must equal `cooked`; if it ever does not, nothing is recorded rather than
386
+ * something wrong.
387
+ */
388
+ function segmentEscapes(raw, cooked) {
389
+ const found = [];
390
+ let built = '';
391
+ let typed = '';
392
+ let invalid;
393
+ for (let i = 0; i < raw.length; i++) {
394
+ const ch = raw[i];
395
+ if (ch !== '\\' || i + 1 >= raw.length) {
396
+ built += ch;
397
+ typed += ch;
398
+ continue;
399
+ }
400
+ const next = raw[i + 1];
401
+ if (isTemplateEscape(raw, i)) {
402
+ built += next;
403
+ typed += next;
404
+ i++;
405
+ continue;
406
+ }
407
+ let len = 2;
408
+ let value;
409
+ if (next === '\n' || next === '\u2028' || next === '\u2029')
410
+ value = ''; // line continuation
411
+ else if (next in SINGLE_ESCAPES)
412
+ value = SINGLE_ESCAPES[next];
413
+ else if (next === '0' && !/[0-9]/.test(raw[i + 2] ?? ''))
414
+ value = '\0';
415
+ else if (/[0-9]/.test(next))
416
+ value = undefined;
417
+ else if (next === 'x') {
418
+ const hex = /^[0-9a-fA-F]{2}/.exec(raw.slice(i + 2));
419
+ if (hex) {
420
+ value = String.fromCharCode(parseInt(hex[0], 16));
421
+ len = 4;
422
+ }
423
+ }
424
+ else if (next === 'u') {
425
+ const m = /^(?:\{([0-9a-fA-F]+)\}|([0-9a-fA-F]{4}))/.exec(raw.slice(i + 2));
426
+ const cp = m ? parseInt(m[1] ?? m[2], 16) : NaN;
427
+ if (m && cp <= 0x10ffff) {
428
+ value = String.fromCodePoint(cp);
429
+ len = 2 + m[0].length;
430
+ }
431
+ }
432
+ else {
433
+ // Any other character escapes to itself: `\d` → `d`, `\.` → `.`.
434
+ value = String.fromCodePoint(raw.codePointAt(i + 1));
435
+ len = 1 + value.length;
436
+ }
437
+ const escape = raw.slice(i, i + len);
438
+ if (value === undefined)
439
+ invalid ??= escape;
440
+ else {
441
+ found.push({ at: built.length, cooked: value, typed: escape, escape });
442
+ built += value;
443
+ }
444
+ typed += escape;
445
+ i += len - 1;
446
+ }
447
+ if (cooked === undefined) {
448
+ // `out += undefined` writes the word itself — that is what the expression holds.
449
+ return invalid === undefined ? [] : [{ at: 0, cooked: 'undefined', typed, escape: invalid, invalid: true }];
450
+ }
451
+ return built === cooked ? found : [];
452
+ }
300
453
  /**
301
454
  * Coerce a raw value or Expr into an Expr.
302
455
  *
@@ -6,6 +6,23 @@ export interface LocatedDiagnostic extends ExprDiagnostic {
6
6
  }
7
7
  /** Every first-level expression problem in a built flow, each stamped with its location. */
8
8
  export declare function checkFlowExpressions(built: BuiltFlow): LocatedDiagnostic[];
9
+ /**
10
+ * Every `js` template escape that changed what its expression means
11
+ * (JS_ESCAPE_COOKED), each stamped with where it sits.
12
+ *
13
+ * WHY A GENERIC WALK. The two per-site walks (`walk` above and `check.ts`'s
14
+ * expression collection) each enumerate the positions an expression can sit in,
15
+ * and neither covers all of them — HTTP branch conditions, trigger filters and
16
+ * error-handler bodies each live in only one, or none. This warning is about the
17
+ * `Expr` itself, not about the field it fills, so it visits every `Expr` reachable
18
+ * from the built flow, child flows included: a new position cannot be missed.
19
+ *
20
+ * Only `meaning` effects are reported here. An escape that breaks the parse is
21
+ * named by EXPR_SYNTAX, which already stops `build()`.
22
+ */
23
+ export declare function checkCookedEscapes(built: BuiltFlow): (LocatedDiagnostic & {
24
+ step?: string;
25
+ })[];
9
26
  /**
10
27
  * Thrown by `build()` when the first-level expression check finds errors. Its
11
28
  * `message` lists every problem (so a runner that prints `err.message` shows them
@@ -27,8 +27,8 @@
27
27
  * body is a plain string carrying its own `$vars.*` references; checking those is
28
28
  * the same mechanism and a natural next step, deliberately out of this first cut.
29
29
  */
30
- import { Expr } from './core/expr.js';
31
- import { checkExpression } from './core/expr-check.js';
30
+ import { Expr, cookedEscapesOf } from './core/expr.js';
31
+ import { checkExpression, cookedEscapeDiagnostics } from './core/expr-check.js';
32
32
  const NOUN = 'input, variable, or step';
33
33
  /**
34
34
  * Every `$vars` root a reference may legitimately resolve to, across the WHOLE
@@ -108,16 +108,18 @@ function addStepNames(steps, roots) {
108
108
  }
109
109
  }
110
110
  }
111
- function pushExpr(js, literal, where, roots, out) {
112
- if (literal)
111
+ function pushExpr(e, where, roots, out) {
112
+ if (e.literal)
113
113
  return; // a literal carries no reference to resolve
114
- for (const d of checkExpression(js, { roots, noun: NOUN, syntax: 'javascript' }))
114
+ // The cooked escapes ride along so an EXPR_SYNTAX error can name the one that broke the parse.
115
+ for (const d of checkExpression(e.js, { roots, noun: NOUN, syntax: 'javascript' }, cookedEscapesOf(e))) {
115
116
  out.push({ ...d, where });
117
+ }
116
118
  }
117
119
  /** Reach `Expr`s nested anywhere in an action's inputs (connector/http/… inputs can be structured). */
118
120
  function deepExprs(val, where, roots, out) {
119
121
  if (val instanceof Expr)
120
- return pushExpr(val.js, val.literal, where, roots, out);
122
+ return pushExpr(val, where, roots, out);
121
123
  if (Array.isArray(val)) {
122
124
  for (const v of val)
123
125
  deepExprs(v, where, roots, out);
@@ -140,12 +142,12 @@ function walk(steps, roots, out) {
140
142
  walk(s.body, roots, out);
141
143
  break;
142
144
  case 'branch':
143
- pushExpr(s.cond.js, s.cond.literal, s.name, roots, out);
145
+ pushExpr(s.cond, s.name, roots, out);
144
146
  walk(s.then, roots, out);
145
147
  walk(s.otherwise, roots, out);
146
148
  break;
147
149
  case 'switch': {
148
- pushExpr(s.on.js, s.on.literal, s.name, roots, out);
150
+ pushExpr(s.on, s.name, roots, out);
149
151
  // Inside a case (or default) body the discriminant is bound to
150
152
  // `$vars.value`, so it is a valid root only there.
151
153
  const caseRoots = new Set(roots).add('value');
@@ -163,14 +165,14 @@ function walk(steps, roots, out) {
163
165
  walk(c.body, roots, out);
164
166
  break;
165
167
  case 'loop':
166
- pushExpr(s.collection.js, s.collection.literal, s.name, roots, out);
168
+ pushExpr(s.collection, s.name, roots, out);
167
169
  if (s.options?.completionCondition) {
168
- pushExpr(s.options.completionCondition.js, s.options.completionCondition.literal, s.name, roots, out);
170
+ pushExpr(s.options.completionCondition, s.name, roots, out);
169
171
  }
170
172
  walk(s.body, roots, out);
171
173
  break;
172
174
  case 'doWhile':
173
- pushExpr(s.condition.js, s.condition.literal, s.name, roots, out);
175
+ pushExpr(s.condition, s.name, roots, out);
174
176
  walk(s.body, roots, out);
175
177
  break;
176
178
  case 'parallel':
@@ -179,7 +181,7 @@ function walk(steps, roots, out) {
179
181
  break;
180
182
  case 'return':
181
183
  for (const [k, v] of Object.entries(s.values))
182
- pushExpr(v.js, v.literal, `return "${k}"`, roots, out);
184
+ pushExpr(v, `return "${k}"`, roots, out);
183
185
  break;
184
186
  }
185
187
  }
@@ -193,6 +195,65 @@ export function checkFlowExpressions(built) {
193
195
  walk(ep.steps, roots, out);
194
196
  return out;
195
197
  }
198
+ /**
199
+ * Every `js` template escape that changed what its expression means
200
+ * (JS_ESCAPE_COOKED), each stamped with where it sits.
201
+ *
202
+ * WHY A GENERIC WALK. The two per-site walks (`walk` above and `check.ts`'s
203
+ * expression collection) each enumerate the positions an expression can sit in,
204
+ * and neither covers all of them — HTTP branch conditions, trigger filters and
205
+ * error-handler bodies each live in only one, or none. This warning is about the
206
+ * `Expr` itself, not about the field it fills, so it visits every `Expr` reachable
207
+ * from the built flow, child flows included: a new position cannot be missed.
208
+ *
209
+ * Only `meaning` effects are reported here. An escape that breaks the parse is
210
+ * named by EXPR_SYNTAX, which already stops `build()`.
211
+ */
212
+ export function checkCookedEscapes(built) {
213
+ const out = [];
214
+ const seen = new WeakSet();
215
+ const root = built;
216
+ const visit = (val, at) => {
217
+ if (val === null || typeof val !== 'object')
218
+ return;
219
+ const escapes = cookedEscapesOf(val);
220
+ if (escapes.length) {
221
+ for (const d of cookedEscapeDiagnostics(val.js, escapes)) {
222
+ out.push({ ...d, where: at.where, ...(at.step !== undefined ? { step: at.step } : {}) });
223
+ }
224
+ return;
225
+ }
226
+ if (val instanceof Expr || seen.has(val))
227
+ return;
228
+ seen.add(val);
229
+ if (Array.isArray(val)) {
230
+ for (const v of val)
231
+ visit(v, at);
232
+ return;
233
+ }
234
+ const o = val;
235
+ if (o === root) {
236
+ for (const [k, v] of Object.entries(o))
237
+ visit(v, { where: k, prefix: '' });
238
+ return;
239
+ }
240
+ // A child flow under a step (a subflow's `spec.child`): its steps are named in its own scope.
241
+ if (at.step !== undefined && Array.isArray(o.steps) && Array.isArray(o.inputs))
242
+ at = { ...at, prefix: `${at.where} › ` };
243
+ if (o.kind === 'return' && o.values && typeof o.values === 'object') {
244
+ for (const [k, v] of Object.entries(o.values))
245
+ visit(v, { ...at, where: `${at.prefix}return "${k}"` });
246
+ return;
247
+ }
248
+ if (typeof o.kind === 'string' && typeof o.name === 'string') {
249
+ at = { ...at, where: `${at.prefix}step "${o.name}"`, step: at.step ?? o.name };
250
+ }
251
+ for (const v of Object.values(o))
252
+ visit(v, at);
253
+ };
254
+ visit(built, { where: 'flow', prefix: '' });
255
+ return out;
256
+ }
196
257
  /**
197
258
  * Thrown by `build()` when the first-level expression check finds errors. Its
198
259
  * `message` lists every problem (so a runner that prints `err.message` shows them
@@ -11,6 +11,31 @@ import { connectorInputErrors as coreConnectorInputErrors } from './core/connect
11
11
  * dropping it would make `check` quietly accept what `compile` throws on.
12
12
  */
13
13
  export declare function connectorInputErrors(rc: Parameters<typeof coreConnectorInputErrors>[0], inputs: Record<string, unknown>, resolutions?: Parameters<typeof coreConnectorInputErrors>[2], lookups?: Parameters<typeof coreConnectorInputErrors>[3]): string | undefined;
14
+ /**
15
+ * Every tool resource identity `compile` writes, read off the tables its
16
+ * writers use — so a tool kind cannot be written without being owned, or owned
17
+ * without being written (#935: the #920 built-in files were written as
18
+ * `internal` and never pruned, because ownership was a hand-kept list in
19
+ * `compile-cli.ts` that did not have them).
20
+ *
21
+ * * `types` — the resource `type`s compile owns OUTRIGHT: every deployed-resource
22
+ * kind's, and `integration` (connector tools, since #867).
23
+ * * `internalToolTypes` — `internal` is shared with designer-only built-ins
24
+ * (`load-attachments`, `create-file`, `jev-classifier`), so for it ownership
25
+ * is the `properties.toolType`, and only the ones compile writes.
26
+ */
27
+ export declare const COMPILE_WRITTEN_TOOL_RESOURCES: {
28
+ readonly types: ReadonlySet<string>;
29
+ readonly internalToolTypes: ReadonlySet<string>;
30
+ };
31
+ /**
32
+ * Does `compile` own this `resources/<id>/resource.json` — would a compile have
33
+ * written it, so one this compile did not write is stale rather than someone
34
+ * else's? Everything it says no to belongs to the designer or to `uip agent
35
+ * refresh` (escalation, mcp, designer-only built-ins, the kinds the SDK cannot
36
+ * derive), and is never deleted.
37
+ */
38
+ export declare function compileOwnsResource(body: Record<string, unknown> | undefined): boolean;
14
39
  /**
15
40
  * A compile-time observation that does not stop emission. `check()` has its
16
41
  * diagnostics; this is the serializer's own channel for things it can only see
package/dist/serialize.js CHANGED
@@ -976,6 +976,16 @@ const TOOL_RESOURCE_TYPE = {
976
976
  process: 'process', api: 'api', maestro: 'processOrchestration',
977
977
  flow: 'flow', agent: 'agent', function: 'function',
978
978
  };
979
+ /** The resource `type` of a connector tool's file ({@link connectorToolResourceJson}). */
980
+ const CONNECTOR_TOOL_RESOURCE_TYPE = 'integration';
981
+ /**
982
+ * The resource `type` of every platform built-in tool. Shared with tools the
983
+ * DESIGNER writes and the SDK cannot (`load-attachments`, `create-file`, …), so
984
+ * on its own it says nothing about who owns a file: `properties.toolType` does.
985
+ */
986
+ const INTERNAL_TOOL_RESOURCE_TYPE = 'internal';
987
+ /** The `properties.toolType` of the built-in HTTP-request tool's file (#935). */
988
+ const HTTP_REQUEST_TOOL_TYPE = 'http-request';
979
989
  /**
980
990
  * The `resources/<id>/resource.json` a DEPLOYED-RESOURCE tool needs — the tool
981
991
  * half of what `contextResourceJson` does for an index, and inert for the same
@@ -1005,8 +1015,12 @@ const TOOL_RESOURCE_TYPE = {
1005
1015
  * card, which are tenant reads.
1006
1016
  * * `builtin` — has its own writer, {@link builtinToolResourceJson}: its
1007
1017
  * `toolType` is a fixed per-tool constant, so the file is derivable offline.
1008
- * * `httpRequest` — its `http-request` toolType carries no request config in
1009
- * the file, while the node carries all of it; not measured, so not written.
1018
+ * * `httpRequest` — has its own writer, {@link httpRequestToolResourceJson}:
1019
+ * the file carries the request config as static `argumentProperties` and the
1020
+ * model-facing `inputSchema`, both derivable from the node (#935).
1021
+ *
1022
+ * Which `type`/`toolType` values compile writes — and so owns and may prune —
1023
+ * is {@link compileOwnsResource}, read off the same tables these writers use.
1010
1024
  */
1011
1025
  function toolResourceJson(tr, spec) {
1012
1026
  const common = {
@@ -1107,7 +1121,7 @@ function builtinToolResourceJson(tool, label, inputs, spec) {
1107
1121
  // Null, not absent: what identifies a built-in as NOT an external tool.
1108
1122
  referenceKey: null,
1109
1123
  name: label,
1110
- type: 'internal',
1124
+ type: INTERNAL_TOOL_RESOURCE_TYPE,
1111
1125
  description: String(inputs.description ?? ''),
1112
1126
  isEnabled: true,
1113
1127
  settings: {},
@@ -1197,6 +1211,102 @@ function builtinToolResourceJson(tool, label, inputs, spec) {
1197
1211
  },
1198
1212
  };
1199
1213
  }
1214
+ /**
1215
+ * The six request fields of the HTTP-request tool, in the node's (and the
1216
+ * designer's file's) order, with the JSON Schema each takes in the file's
1217
+ * model-facing `inputSchema`. `headers`/`params` are name/value rows and
1218
+ * `timeout` is a number — the same normalization the node's text-builder
1219
+ * values already carry.
1220
+ */
1221
+ const HTTP_REQUEST_FIELDS = [
1222
+ ['url', { type: 'string' }],
1223
+ ['method', { type: 'string' }],
1224
+ ['headers', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
1225
+ ['params', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
1226
+ ['body', { type: 'string' }],
1227
+ ['timeout', { type: 'number' }],
1228
+ ];
1229
+ /**
1230
+ * The `resources/<id>/resource.json` of the built-in HTTP-request tool (#935) —
1231
+ * without it the deployed agent is never offered the tool (measured: one LLM
1232
+ * call, no tool span), and `uip agent refresh --inline-in-flow` strips the node
1233
+ * to `{source}`, so the request config is lost entirely.
1234
+ *
1235
+ * Derived from the node's own mode-tagged inputs, so the two halves cannot
1236
+ * disagree. The rules are the ones Studio Web's own file follows (measured by
1237
+ * saving the agent once in the designer; a file built by these rules is
1238
+ * JSON-equal to it, and a debug run with only this file showed the tool spans):
1239
+ *
1240
+ * * `inputSchema` declares all six fields, each with the node's `promptValue`
1241
+ * as its description — the text the model reads when it fills the field;
1242
+ * * `argumentProperties` pins every field NOT in `prompt` mode as
1243
+ * `$['<field>']: {variant: 'static', value: <textValue>}` — a fixed value the
1244
+ * model does not get to choose. The SDK writes only `prompt` and
1245
+ * `text-builder` fields; a `text-builder` value is the literal.
1246
+ */
1247
+ function httpRequestToolResourceJson(label, inputs, spec) {
1248
+ const properties = {};
1249
+ const argumentProperties = {};
1250
+ for (const [field, schema] of HTTP_REQUEST_FIELDS) {
1251
+ const row = (inputs[field] ?? {});
1252
+ properties[field] = { ...schema, description: String(row.promptValue ?? '') };
1253
+ if (row.mode !== undefined && row.mode !== 'prompt') {
1254
+ argumentProperties[`$['${field}']`] = { variant: 'static', value: row.textValue, isSensitive: false };
1255
+ }
1256
+ }
1257
+ return {
1258
+ $resourceType: 'tool',
1259
+ id: spec.id,
1260
+ canvasNodeId: spec.canvasNodeId,
1261
+ name: label,
1262
+ type: INTERNAL_TOOL_RESOURCE_TYPE,
1263
+ description: String(inputs.description ?? ''),
1264
+ isEnabled: true,
1265
+ inputSchema: { type: 'object', properties, required: ['url'] },
1266
+ outputSchema: { type: 'object', properties: {} },
1267
+ settings: {},
1268
+ guardrail: { policies: [] },
1269
+ argumentProperties,
1270
+ properties: { toolType: HTTP_REQUEST_TOOL_TYPE },
1271
+ };
1272
+ }
1273
+ /**
1274
+ * Every tool resource identity `compile` writes, read off the tables its
1275
+ * writers use — so a tool kind cannot be written without being owned, or owned
1276
+ * without being written (#935: the #920 built-in files were written as
1277
+ * `internal` and never pruned, because ownership was a hand-kept list in
1278
+ * `compile-cli.ts` that did not have them).
1279
+ *
1280
+ * * `types` — the resource `type`s compile owns OUTRIGHT: every deployed-resource
1281
+ * kind's, and `integration` (connector tools, since #867).
1282
+ * * `internalToolTypes` — `internal` is shared with designer-only built-ins
1283
+ * (`load-attachments`, `create-file`, `jev-classifier`), so for it ownership
1284
+ * is the `properties.toolType`, and only the ones compile writes.
1285
+ */
1286
+ export const COMPILE_WRITTEN_TOOL_RESOURCES = {
1287
+ types: new Set([...Object.values(TOOL_RESOURCE_TYPE), CONNECTOR_TOOL_RESOURCE_TYPE]),
1288
+ internalToolTypes: new Set([...Object.values(BUILTIN_TOOL_TYPE), HTTP_REQUEST_TOOL_TYPE]),
1289
+ };
1290
+ /**
1291
+ * Does `compile` own this `resources/<id>/resource.json` — would a compile have
1292
+ * written it, so one this compile did not write is stale rather than someone
1293
+ * else's? Everything it says no to belongs to the designer or to `uip agent
1294
+ * refresh` (escalation, mcp, designer-only built-ins, the kinds the SDK cannot
1295
+ * derive), and is never deleted.
1296
+ */
1297
+ export function compileOwnsResource(body) {
1298
+ const kind = body?.$resourceType;
1299
+ if (kind === 'context')
1300
+ return true;
1301
+ if (kind !== 'tool')
1302
+ return false;
1303
+ const type = String(body?.type);
1304
+ if (type === INTERNAL_TOOL_RESOURCE_TYPE) {
1305
+ const toolType = body?.properties?.toolType;
1306
+ return COMPILE_WRITTEN_TOOL_RESOURCES.internalToolTypes.has(String(toolType));
1307
+ }
1308
+ return COMPILE_WRITTEN_TOOL_RESOURCES.types.has(type);
1309
+ }
1200
1310
  /**
1201
1311
  * The .NET type an Action Center app argument of each field type carries. Studio
1202
1312
  * Web serializes the task payload through `inputSchemaDotnetTypeMapping`, which
@@ -1654,7 +1764,7 @@ function connectorToolResourceJson(ctx, id) {
1654
1764
  name: ctx.name,
1655
1765
  description: ctx.description,
1656
1766
  location: 'external',
1657
- type: 'integration',
1767
+ type: CONNECTOR_TOOL_RESOURCE_TYPE,
1658
1768
  inputSchema,
1659
1769
  outputSchema,
1660
1770
  settings: {},
@@ -5335,8 +5445,10 @@ export function serialize(built, opts = {}) {
5335
5445
  fix('body', tr.body);
5336
5446
  if (tr.timeout !== undefined)
5337
5447
  fix('timeout', tr.timeout);
5338
- if (tr.description !== undefined)
5339
- extraInputs.description = tr.description;
5448
+ // Always set, falling back to the definition's own text: the resource
5449
+ // file hands this to the MODEL as the tool's description, and the node
5450
+ // must carry the same one (#935).
5451
+ extraInputs.description = tr.description ?? String(tdef.description ?? '');
5340
5452
  }
5341
5453
  else {
5342
5454
  const meta = PROCESS_TOOL_META[tr.kind];
@@ -5390,7 +5502,9 @@ export function serialize(built, opts = {}) {
5390
5502
  ? connectorToolResourceJson(connectorTool, toolResourceId)
5391
5503
  : tr.kind === 'builtin'
5392
5504
  ? builtinToolResourceJson(tr.tool, label, tnode.inputs, { id: toolResourceId })
5393
- : toolResourceJson(tr, { id: toolResourceId });
5505
+ : tr.kind === 'httpRequest'
5506
+ ? httpRequestToolResourceJson(label, tnode.inputs, { id: toolResourceId, canvasNodeId: tid })
5507
+ : toolResourceJson(tr, { id: toolResourceId });
5394
5508
  if (toolResource !== undefined) {
5395
5509
  sidecars.push({
5396
5510
  path: `${source}/resources/${toolResourceId}/resource.json`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.16.8",
3
+ "version": "6.16.10",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "77b292a40160cde6e025bbeb534397e5e930bae1"
95
+ "gitref": "2e666ec2dd9e7baa4716a2a0d21ee86484b0de89"
96
96
  }