@uipath/maestro-builder-sdk 6.16.14 → 6.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/api-index.md +108 -107
  2. package/dist/api-members.md +295 -291
  3. package/dist/bindings-v2.d.ts +39 -1
  4. package/dist/bindings-v2.js +289 -9
  5. package/dist/bpmn/compile-cli.js +11 -2
  6. package/dist/bpmn/decompile.js +2 -1
  7. package/dist/bpmn/registry-fallback.js +9 -7
  8. package/dist/case/compile-cli.js +16 -6
  9. package/dist/case/decompile-cli.js +2 -1
  10. package/dist/case/decompile.js +3 -2
  11. package/dist/case/merge-cli.js +3 -2
  12. package/dist/check.js +219 -37
  13. package/dist/cli-result.d.ts +9 -0
  14. package/dist/cli-result.js +12 -0
  15. package/dist/compile-cli.js +78 -53
  16. package/dist/core/actions.d.ts +70 -16
  17. package/dist/core/agentic-process-forms.d.ts +51 -0
  18. package/dist/core/agentic-process-forms.js +49 -0
  19. package/dist/core/binding-messages.d.ts +5 -0
  20. package/dist/core/comment-text.d.ts +29 -0
  21. package/dist/core/comment-text.js +40 -0
  22. package/dist/core/contribution-bindings.d.ts +73 -0
  23. package/dist/core/contribution-bindings.js +105 -0
  24. package/dist/core/expr-check.js +7 -2
  25. package/dist/core/json-file.d.ts +1 -0
  26. package/dist/core/json-file.js +12 -0
  27. package/dist/core/script-return.js +4 -1
  28. package/dist/core/typed-raw-node.js +1 -0
  29. package/dist/core/typescript-module.d.ts +2 -0
  30. package/dist/core/typescript-module.js +18 -0
  31. package/dist/core-definitions.json +210 -0
  32. package/dist/decompile-cli.js +2 -1
  33. package/dist/decompile.js +64 -24
  34. package/dist/emit.js +4 -3
  35. package/dist/flow-sdk.d.ts +14 -9
  36. package/dist/flow-sdk.js +14 -4
  37. package/dist/flow-step-tree.d.ts +2 -3
  38. package/dist/flow-step-tree.js +2 -3
  39. package/dist/load.js +1 -1
  40. package/dist/merge-cli.js +3 -2
  41. package/dist/merge.d.ts +2 -1
  42. package/dist/merge.js +0 -0
  43. package/dist/path-within.d.ts +33 -0
  44. package/dist/path-within.js +41 -0
  45. package/dist/registry/archive/extract.js +2 -5
  46. package/dist/registry/curated-overlay.js +3 -2
  47. package/dist/registry/generators/descriptors.js +20 -18
  48. package/dist/registry/generators/triggers.js +4 -4
  49. package/dist/registry/prepare/messages.js +2 -1
  50. package/dist/registry/prepare/source-labels.js +60 -1
  51. package/dist/registry/prepare-cli.js +14 -4
  52. package/dist/registry/pull-cli.js +3 -2
  53. package/dist/registry/uip/client.d.ts +28 -3
  54. package/dist/registry/uip/client.js +95 -34
  55. package/dist/registry/uip/host.d.ts +41 -0
  56. package/dist/registry/uip/host.js +312 -0
  57. package/dist/serialize.d.ts +7 -0
  58. package/dist/serialize.js +390 -270
  59. package/dist/workdir.js +1 -1
  60. package/package.json +3 -3
@@ -0,0 +1,33 @@
1
+ /**
2
+ * path-within — the one path-containment test for anything that writes files.
3
+ *
4
+ * "Is `child` inside `parent`?" looks like a string-prefix question and is not
5
+ * one. `child.startsWith(parent + '/')` is correct only where the separator is
6
+ * `/`: on Windows `path.resolve` returns `C:\proj\P\x`, the check refuses every
7
+ * path, and compile refused every inline agent's `<source>/agent.json`
8
+ * (flow-builder-sdk#946). A prefix test without a trailing separator has the
9
+ * opposite bug: it accepts the sibling `C:\proj\P2\x` as inside `C:\proj\P`.
10
+ *
11
+ * So the test is `relative(parent, child)` instead, which gets both right on
12
+ * every platform, and Windows' case-insensitive drive letters and
13
+ * cross-drive paths (`relative` returns the absolute `D:\x` there) with them.
14
+ *
15
+ * The path implementation is a parameter so the Windows rules can be tested on
16
+ * a Linux runner (`isWithin(a, b, path.win32)`). Callers leave it out.
17
+ */
18
+ import * as nodePath from 'node:path';
19
+ /** The slice of `node:path` the test needs; `path.win32` and `path.posix` both fit. */
20
+ export type PathApi = Pick<typeof nodePath, 'relative' | 'isAbsolute' | 'sep'>;
21
+ /**
22
+ * `child` is `parent` itself or somewhere beneath it.
23
+ *
24
+ * Both paths should be absolute (from `path.resolve`); relative ones are
25
+ * resolved against the working directory by `relative`.
26
+ *
27
+ * @example
28
+ * isWithin('/proj/P', '/proj/P/abc/agent.json') // true
29
+ * isWithin('/proj/P', '/proj/P2/agent.json') // false — sibling, not child
30
+ * isWithin('C:\\proj\\P', 'C:\\proj\\P\\abc\\agent.json', path.win32) // true
31
+ * isWithin('C:\\proj\\P', 'D:\\agent.json', path.win32) // false — other drive
32
+ */
33
+ export declare function isWithin(parent: string, child: string, path?: PathApi): boolean;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * path-within — the one path-containment test for anything that writes files.
3
+ *
4
+ * "Is `child` inside `parent`?" looks like a string-prefix question and is not
5
+ * one. `child.startsWith(parent + '/')` is correct only where the separator is
6
+ * `/`: on Windows `path.resolve` returns `C:\proj\P\x`, the check refuses every
7
+ * path, and compile refused every inline agent's `<source>/agent.json`
8
+ * (flow-builder-sdk#946). A prefix test without a trailing separator has the
9
+ * opposite bug: it accepts the sibling `C:\proj\P2\x` as inside `C:\proj\P`.
10
+ *
11
+ * So the test is `relative(parent, child)` instead, which gets both right on
12
+ * every platform, and Windows' case-insensitive drive letters and
13
+ * cross-drive paths (`relative` returns the absolute `D:\x` there) with them.
14
+ *
15
+ * The path implementation is a parameter so the Windows rules can be tested on
16
+ * a Linux runner (`isWithin(a, b, path.win32)`). Callers leave it out.
17
+ */
18
+ import * as nodePath from 'node:path';
19
+ /**
20
+ * `child` is `parent` itself or somewhere beneath it.
21
+ *
22
+ * Both paths should be absolute (from `path.resolve`); relative ones are
23
+ * resolved against the working directory by `relative`.
24
+ *
25
+ * @example
26
+ * isWithin('/proj/P', '/proj/P/abc/agent.json') // true
27
+ * isWithin('/proj/P', '/proj/P2/agent.json') // false — sibling, not child
28
+ * isWithin('C:\\proj\\P', 'C:\\proj\\P\\abc\\agent.json', path.win32) // true
29
+ * isWithin('C:\\proj\\P', 'D:\\agent.json', path.win32) // false — other drive
30
+ */
31
+ export function isWithin(parent, child, path = nodePath) {
32
+ const rel = path.relative(parent, child);
33
+ if (rel === '')
34
+ return true;
35
+ // `..` alone or as the first segment leaves `parent`; a segment that merely
36
+ // starts with dots (`..cache`) does not.
37
+ if (rel === '..' || rel.startsWith(`..${path.sep}`))
38
+ return false;
39
+ // Windows: a child on another drive has no relative spelling.
40
+ return !path.isAbsolute(rel);
41
+ }
@@ -17,7 +17,8 @@
17
17
  * Python printed to stderr; the caller prints it and exits 1.
18
18
  */
19
19
  import { readFileSync } from "node:fs";
20
- import { join, resolve, sep } from "node:path";
20
+ import { join, resolve } from "node:path";
21
+ import { isWithin } from "../../path-within.js";
21
22
  import { isFile, writeBytes, ensureDir } from "../files.js";
22
23
  import { UnsafeArchive, isSymlinkEntry, safeRelativePath } from "./entry-policy.js";
23
24
  import { NotAZip, UnsupportedArchive, entryData, readZipEntries } from "./zip-reader.js";
@@ -40,10 +41,6 @@ export class ExtractLibraryError extends Error {
40
41
  this.name = "ExtractLibraryError";
41
42
  }
42
43
  }
43
- /** `child` is `parent` or lives beneath it — both already `path.resolve`d. */
44
- function isWithin(parent, child) {
45
- return child === parent || child.startsWith(parent.endsWith(sep) ? parent : parent + sep);
46
- }
47
44
  export function extractArchive(options) {
48
45
  const { archivePath, destination } = options;
49
46
  const root = options.root ?? DEFAULT_ROOT;
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { cp, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { sortedStrings } from "./compat/compare.js";
6
+ import { writeJsonFile } from "../core/json-file.js";
6
7
  import { consoleReporter } from "./reporter.js";
7
8
  /** The corrections and definitions that the raw registry dump does not carry. */
8
9
  export function bundledOverlayPath() {
@@ -129,7 +130,7 @@ reporter = consoleReporter) {
129
130
  }
130
131
  if (additions.length > 0) {
131
132
  destination.entries = [...entries, ...additions];
132
- await writeFile(indexPath, `${JSON.stringify(destination, null, 2)}\n`);
133
+ writeJsonFile(indexPath, destination);
133
134
  }
134
135
  return { files: copied, entries: additions.length, skipped: sortedStrings([...skipped]) };
135
136
  }
@@ -19,6 +19,7 @@
19
19
  * node type and so would emit the same const twice.
20
20
  */
21
21
  import { prepareCommand } from "../../core/cli-spelling.js";
22
+ import { commentText, docComment } from "../../core/comment-text.js";
22
23
  import { PACKAGE_NAME } from "../../package-name.js";
23
24
  import { pyJsonString, pyJsonStringify } from "../codecs/pyjson.js";
24
25
  import { sortedByCodePoint, sortedStrings } from "../compat/compare.js";
@@ -58,15 +59,16 @@ export function lookupFieldNote(declared) {
58
59
  export function emitNestedProperty(name, node, indent, namedTypes, allOptional, isInput = false) {
59
60
  const lines = [];
60
61
  const declared = node.declared ?? {};
61
- let desc = pyStrip(strOrEmpty(declared.description)).replaceAll("\n", " ");
62
+ let desc = pyStrip(strOrEmpty(declared.description));
62
63
  if (isInput && pyTruthy(declared.__lookup)) {
63
64
  // The identity goes ON the field, not only in the LOOKUP block above the
64
65
  // descriptor: "The reporter of the issue" beside `ConnectorLookupValue<string>`
65
66
  // read as "any string", and an e-mail went into `fields.reporter.id`.
66
67
  desc = pyStrip(`${desc} ${lookupFieldNote(declared)}`);
67
68
  }
69
+ // A multi-line description keeps its breaks: `docComment` opens a block for it.
68
70
  if (desc)
69
- lines.push(`${indent}/** ${desc} */`);
71
+ lines.push(...docComment(desc, indent));
70
72
  const optional = allOptional || !subtreeRequired(node) ? "?" : "";
71
73
  let rendered = namedTypes.typeFor(name, node);
72
74
  if (isInput) {
@@ -100,11 +102,11 @@ export function emitLookupComment(specs, symbol = "<Descriptor>") {
100
102
  const lines = [];
101
103
  for (const [field, spec] of specs) {
102
104
  const target = splitOnce(pyStr(pyTruthy(spec.objectName) ? spec.objectName : spec.path), "?")[0];
103
- lines.push(` * LOOKUP: \`${field}\` -> ${target}.${spec.value}`);
105
+ lines.push(` * LOOKUP: \`${commentText(field)}\` -> ${commentText(target)}.${commentText(spec.value)}`);
104
106
  const aliases = spec.aliases;
105
107
  const offered = aliases.size > 0 ? [...aliases.values()].map((a) => `${a}()`) : [];
106
108
  const byList = spec.by.map((n) => `\`${n}\``).join(", ");
107
- lines.push(` * by: ${byList}`);
109
+ lines.push(` * by: ${commentText(byList)}`);
108
110
  // THE WHOLE CALL, not just the method names: naming `.byEmail` without the
109
111
  // `lookup(Descriptor, 'field')` wrapper says helpers exist but not how to
110
112
  // reach one.
@@ -114,16 +116,16 @@ export function emitLookupComment(specs, symbol = "<Descriptor>") {
114
116
  : spec.by.length > 0
115
117
  ? `lookup(${symbol}, '${field}').by('${spec.by[0]}', value)`
116
118
  : `lookup(${symbol}, '${field}')`;
117
- lines.push(` * use: ${call}`);
119
+ lines.push(` * use: ${commentText(call)}`);
118
120
  if (offered.length > 0) {
119
- lines.push(` * also: ${[...aliases.values()].map((a) => `.${a}`).join(", ")} · .by(name, value)`);
121
+ lines.push(` * also: ${commentText([...aliases.values()].map((a) => `.${a}`).join(", "))} · .by(name, value)`);
120
122
  }
121
123
  else {
122
124
  lines.push(" * also: .by(name, value) only — aliases collided");
123
125
  }
124
- lines.push(` * strategy: ${STRATEGY_PROSE[spec.strategy]}`);
126
+ lines.push(` * strategy: ${commentText(STRATEGY_PROSE[spec.strategy])}`);
125
127
  if (spec.dependsOn && spec.dependsOn.length > 0) {
126
- lines.push(` * resolve first: ${spec.dependsOn.map((d) => `\`${d}\``).join(", ")}`);
128
+ lines.push(` * resolve first: ${commentText(spec.dependsOn.map((d) => `\`${d}\``).join(", "))}`);
127
129
  }
128
130
  }
129
131
  return lines;
@@ -186,16 +188,16 @@ export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTyp
186
188
  // connector input's are, so the descriptor carries the same specs.
187
189
  const specs = specsFromFields(inFields, invariant);
188
190
  lines.push("/**");
189
- lines.push(` * ${pyStr(label)} — ${nodeType}`);
191
+ lines.push(` * ${commentText(pyStr(label))} — ${commentText(nodeType)}`);
190
192
  lines.push(" *");
191
193
  if (generic) {
192
194
  lines.push(" * A GENERIC event: it fires for ONE object of the connection (an entity, a");
193
- lines.push(` * table, a custom object), so name it — \`onEvent(${name}, { object: '<Object>' })\`.`);
194
- lines.push(` * \`uip is triggers objects ${pyStr(connectorKey)} <EVENT> --connection-id <id>\` lists them.`);
195
+ lines.push(` * table, a custom object), so name it — \`onEvent(${commentText(name)}, { object: '<Object>' })\`.`);
196
+ lines.push(` * \`uip is triggers objects ${commentText(pyStr(connectorKey))} <EVENT> --connection-id <id>\` lists them.`);
195
197
  }
196
198
  else {
197
- lines.push(` * Start a flow on it with \`onEvent(${name}, { where: { … } })\`, or pause for it`);
198
- lines.push(` * with \`waitForEvent(${name}, { … })\`; \`where\` carries its event parameters.`);
199
+ lines.push(` * Start a flow on it with \`onEvent(${commentText(name)}, { where: { … } })\`, or pause for it`);
200
+ lines.push(` * with \`waitForEvent(${commentText(name)}, { … })\`; \`where\` carries its event parameters.`);
199
201
  }
200
202
  // The facts the library build stamped from the event's `triggers objects`
201
203
  // row. An author holding only the descriptor must learn the requirement here:
@@ -205,7 +207,7 @@ export function emitEvent(entry, inFields, inputTree, outputTree, name, inputTyp
205
207
  lines.push(" * Requires a BYOA (bring-your-own-app) connection — on a standard one the");
206
208
  lines.push(" * trigger deploys and never fires. Bind it with");
207
209
  // Spelled for whoever ran the generator, like every `check` remedy.
208
- lines.push(` * \`${prepareCommand(pyStr(connectorKey), eventId, "--connection-id", "<id>")}\`,`);
210
+ lines.push(` * \`${commentText(prepareCommand(pyStr(connectorKey), eventId, "--connection-id", "<id>"))}\`,`);
209
211
  lines.push(" * which refuses a standard connection and records the check `compile` requires;");
210
212
  lines.push(" * `uip is connections list --byoa --all-folders` lists the BYOA connections.");
211
213
  }
@@ -253,7 +255,7 @@ export function emitOp(entry, v1def, inputTree, outputTree, name, inputTypes, ou
253
255
  const schemaAction = operation.schemaAction ?? {};
254
256
  const specs = lookupSpecs(entry, v1def, invariant);
255
257
  const lookupComment = specs.size > 0 ? emitLookupComment(specs, name) : [];
256
- const header = ` * ${pyStr(label)} — ${nodeType}`;
258
+ const header = ` * ${commentText(pyStr(label))} — ${commentText(nodeType)}`;
257
259
  if (pyTruthy(operation.objectFromDiscovery)) {
258
260
  lines.push("/**");
259
261
  lines.push(header);
@@ -272,8 +274,8 @@ export function emitOp(entry, v1def, inputTree, outputTree, name, inputTypes, ou
272
274
  lines.push("/**");
273
275
  lines.push(header);
274
276
  lines.push(" *");
275
- lines.push(` * \`Inputs\` carries only the fields that SELECT the schema (${parents}).`);
276
- lines.push(` * The real set comes from the \`${pyStr(schemaAction.name)}\` design-time`);
277
+ lines.push(` * \`Inputs\` carries only the fields that SELECT the schema (${commentText(parents)}).`);
278
+ lines.push(` * The real set comes from the \`${commentText(pyStr(schemaAction.name))}\` design-time`);
277
279
  lines.push(" * action — describe with those values supplied to see it.");
278
280
  if (lookupComment.length > 0) {
279
281
  lines.push(" *");
@@ -289,7 +291,7 @@ export function emitOp(entry, v1def, inputTree, outputTree, name, inputTypes, ou
289
291
  lines.push(" */");
290
292
  }
291
293
  else {
292
- lines.push(`/** ${pyStr(label)} — ${nodeType} */`);
294
+ lines.push(`/** ${commentText(pyStr(label))} — ${commentText(nodeType)} */`);
293
295
  }
294
296
  lines.push(`export const ${name} = descriptor<${name}Inputs, ${name}Outputs>()({`);
295
297
  lines.push(` nodeType: ${pyJsonString(nodeType)},`);
@@ -11,6 +11,7 @@
11
11
  * with it, and `tests/registry-triggers-gen.test.ts` now pins the text.
12
12
  */
13
13
  import { PACKAGE_NAME } from "../../package-name.js";
14
+ import { docComment } from "../../core/comment-text.js";
14
15
  import { pyJsonString, pyJsonStringify } from "../codecs/pyjson.js";
15
16
  import { sortedByCodePoint, sortedStrings } from "../compat/compare.js";
16
17
  import { afterLast, pyRstrip, pyStrip, upperFirst } from "../compat/pystr.js";
@@ -32,11 +33,10 @@ function emitTrigger(row, seen) {
32
33
  seen.add(name);
33
34
  const version = "Version" in row ? row.Version : "1.0.0";
34
35
  const label = row.DisplayName || eventId;
35
- const description = pyStrip(String(row.Description || "")).replaceAll("\n", " ");
36
- let doc = description ? `${label} — ${description}` : label;
37
- doc = doc.replaceAll("*/", "* /"); // never let a description close the doc comment early
36
+ const description = pyStrip(String(row.Description || ""));
37
+ const doc = description ? `${label} — ${description}` : label;
38
38
  return [
39
- `/** ${doc} */`,
39
+ ...docComment(doc),
40
40
  `export const ${name} = triggerDescriptor()({`,
41
41
  ` connector: ${pyJsonString(row.connectorKey)},`,
42
42
  ` event: ${pyJsonString(eventId)},`,
@@ -21,6 +21,7 @@
21
21
  * `core/cli-spelling.ts`) without this module reading the environment.
22
22
  */
23
23
  import { Refusal, messageOf } from "../errors.js";
24
+ import { commentText } from "../../core/comment-text.js";
24
25
  import { plausibleMatches } from "./actions.js";
25
26
  import { pyGet, pyOr, pyRepr, pyStrAny, pyTruthy } from "./support.js";
26
27
  export const PREFIX = "prepare-connector: ";
@@ -637,7 +638,7 @@ export function descriptorUsageUnparsedLines(sourcePosix, descriptorName) {
637
638
  export function descriptorUsageLines(descriptorName, importPath, requiredFields, boundConnection, boundFolder) {
638
639
  let inputs = "{}";
639
640
  if (requiredFields.length > 0) {
640
- const placeholders = requiredFields.map(([name, type]) => `${name}: /* ${type}, required */`).join(", ");
641
+ const placeholders = requiredFields.map(([name, type]) => `${name}: /* ${commentText(type)}, required */`).join(", ");
641
642
  inputs = `{ ${placeholders} }`;
642
643
  }
643
644
  return [
@@ -22,6 +22,10 @@
22
22
  * import), so a source that a first `prepare` already repointed is still read;
23
23
  * - `onEvent(...)` / `waitForEvent(...)` — a subscription object whose
24
24
  * `connector` is `'<key>'`, or a descriptor imported as above with its options.
25
+ * - `inlineAgent({ tools })` — each `{ kind: 'connector', connector: '<key>',
26
+ * connection, folder }` tool, a single tool or an array (flow-builder-sdk#903).
27
+ * A tool that is not a same-file object may be a connector tool, so it is
28
+ * unplaceable like any other call; a tool of another `kind` binds nothing.
25
29
  *
26
30
  * `opts` and the labels in it may be literals or same-file `const`s, through
27
31
  * `as const`, `satisfies`, parentheses, spreads of such consts, and property
@@ -47,7 +51,7 @@ import { relativePosix } from "../compat/paths.js";
47
51
  import { canonicalPath, flowSources, isRelativeSpecifier } from "./source-import.js";
48
52
  import { inWorkDir } from "../../workdir.js";
49
53
  const CALLEES = new Set([
50
- "connector", "onEvent", "waitForEvent", "eventTrigger", "dataService",
54
+ "connector", "onEvent", "waitForEvent", "eventTrigger", "dataService", "inlineAgent",
51
55
  ]);
52
56
  /**
53
57
  * Data Fabric's connector key, spelled here rather than imported from
@@ -313,6 +317,58 @@ function readFile(file, shown, text, connectorKey, moduleDirs, result, family) {
313
317
  result.unreadable.connection.push(line);
314
318
  result.unreadable.folder.push(line);
315
319
  };
320
+ /**
321
+ * An inline agent's `tools`: one tool or an array of them. A connector tool
322
+ * carries its labels on the tool object itself, beside its `connector` key.
323
+ */
324
+ const readAgentTools = (spec, at) => {
325
+ const obj = constObject(spec);
326
+ if (!obj) {
327
+ unplaceable(at, `takes \`${snippet(unwrap(spec))}\`, which is not a same-file object, so its tools`);
328
+ return;
329
+ }
330
+ const tools = prop(obj, "tools");
331
+ if (tools.kind === "absent")
332
+ return;
333
+ if (tools.kind === "unknown") {
334
+ unplaceable(at, `takes its tools from ${tools.why}`);
335
+ return;
336
+ }
337
+ const list = constArray(tools.expr);
338
+ if (!list && !constObject(tools.expr)) {
339
+ unplaceable(at, `takes its tools as \`${snippet(unwrap(tools.expr))}\``);
340
+ return;
341
+ }
342
+ const elements = list ? expandArgs(list.elements) : [tools.expr];
343
+ if (!Array.isArray(elements)) {
344
+ unplaceable(at, `${elements.why} in its tools`);
345
+ return;
346
+ }
347
+ for (const el of elements) {
348
+ const toolAt = `${where(el, "inlineAgent")} tool`;
349
+ const tool = constObject(el);
350
+ if (!tool) {
351
+ unplaceable(toolAt, `is \`${snippet(unwrap(el))}\`, which is not a same-file object`);
352
+ continue;
353
+ }
354
+ const kindProp = prop(tool, "kind");
355
+ if (kindProp.kind === "absent")
356
+ continue;
357
+ const kind = kindProp.kind === "found" ? constString(kindProp.expr) : undefined;
358
+ if (kind === undefined) {
359
+ unplaceable(toolAt, "computes its `kind`");
360
+ continue;
361
+ }
362
+ if (kind !== "connector")
363
+ continue;
364
+ const keyProp = prop(tool, "connector");
365
+ const key = keyProp.kind === "found" ? constString(keyProp.expr) : undefined;
366
+ if (key)
367
+ record(key, el, toolAt);
368
+ else
369
+ unplaceable(toolAt, keyProp.kind === "absent" ? "has no `connector`" : "computes its `connector`");
370
+ }
371
+ };
316
372
  const visitCalls = (node) => {
317
373
  if (ts.isCallExpression(node)) {
318
374
  const callee = ts.isIdentifier(node.expression)
@@ -343,6 +399,9 @@ function readFile(file, shown, text, connectorKey, moduleDirs, result, family) {
343
399
  record(key, args[callee === "connector" ? 4 : 3], at);
344
400
  }
345
401
  }
402
+ else if (callee === "inlineAgent") {
403
+ readAgentTools(args[0], at);
404
+ }
346
405
  else if (callee === "connector") {
347
406
  const [first] = args;
348
407
  const literalKey = constString(first);
@@ -58,10 +58,20 @@ export function pipelineOptions(options) {
58
58
  * `uip` client and reporter injected so tests replay recordings.
59
59
  */
60
60
  export async function prepareConnector(options) {
61
- return runPipeline(pipelineOptions(options), {
62
- client: defaultUipClient(),
63
- reporter: consoleReporter,
64
- });
61
+ // One `uip mcp serve` for the ten-or-so calls a prepare makes, instead of a
62
+ // process start per call (flow-builder-sdk#965). Closed when the pipeline
63
+ // is done, whichever way it ends; a host that could not be used says so
64
+ // once, as a warning, and the calls spawn as they did before.
65
+ const client = defaultUipClient({ host: { onFallback: (reason) => consoleReporter.warn(` WARN: ${reason}`) } });
66
+ try {
67
+ return await runPipeline(pipelineOptions(options), {
68
+ client,
69
+ reporter: consoleReporter,
70
+ });
71
+ }
72
+ finally {
73
+ client.close?.();
74
+ }
65
75
  }
66
76
  function value(args, name) {
67
77
  const index = args.indexOf(name);
@@ -2,6 +2,7 @@ import { reportResult } from '../cli-result.js';
2
2
  import { createHash } from "node:crypto";
3
3
  import { mkdir, readFile, rename, rm, stat, symlink, writeFile, } from "node:fs/promises";
4
4
  import { join, resolve } from "node:path";
5
+ import { writeJsonFile } from "../core/json-file.js";
5
6
  import { CURRENT_LINK, LIBRARY_JSON_DIR, LIBRARY_MARKDOWN_DIR, cacheEntry, libraryCacheRoot, referencedConnectorKeys, } from "../core/library-cache.js";
6
7
  import { extractLibrary } from "./extractor.js";
7
8
  import { materializeRegistry } from "./materializer.js";
@@ -210,14 +211,14 @@ async function isCurrentLibrary(libraryPath, sha256) {
210
211
  }
211
212
  }
212
213
  async function writeSource(libraryPath, archive, sha256, archiveSha256, overlaySha256) {
213
- await writeFile(join(libraryPath, SOURCE_FILE), `${JSON.stringify({
214
+ writeJsonFile(join(libraryPath, SOURCE_FILE), {
214
215
  sha256,
215
216
  archiveSha256,
216
217
  overlaySha256,
217
218
  url: archive.url ?? null,
218
219
  archive: archive.checksum ? undefined : archive.path,
219
220
  pulledAt: new Date().toISOString(),
220
- }, null, 2)}\n`);
221
+ });
221
222
  }
222
223
  /**
223
224
  * Point `current` at an entry, replacing whatever it named.
@@ -1,3 +1,4 @@
1
+ import { type HostUipClientOptions } from "./host.js";
1
2
  export interface UipRunOptions {
2
3
  /** Working directory for the CLI; defaults to the process's. */
3
4
  cwd?: string;
@@ -23,6 +24,12 @@ export interface UipResult {
23
24
  export interface UipClient {
24
25
  /** Run `argv[0] argv.slice(1)` — the first element is the executable, normally `uip`. */
25
26
  run(argv: readonly string[], options?: UipRunOptions): Promise<UipResult>;
27
+ /**
28
+ * Release whatever the client holds open — the one long-lived `uip` of
29
+ * {@link HostUipClient}. Optional because most clients hold nothing; a
30
+ * command that may have been given one calls `client.close?.()` when done.
31
+ */
32
+ close?(): void;
26
33
  }
27
34
  /** The real thing: spawn the CLI, capture both streams, never throw on a non-zero exit. */
28
35
  export declare class SpawnUipClient implements UipClient {
@@ -50,6 +57,7 @@ export declare class RecordingUipClient implements UipClient {
50
57
  private readonly directory;
51
58
  constructor(inner: UipClient, directory: string);
52
59
  run(argv: readonly string[], options?: UipRunOptions): Promise<UipResult>;
60
+ close(): void;
53
61
  }
54
62
  /**
55
63
  * A client that runs nothing and reports every call as refused.
@@ -93,7 +101,24 @@ export declare class OfflineUipClient implements UipClient {
93
101
  *
94
102
  * Both exist to capture a fixture from a real tenant and to replay one — that
95
103
  * is how `fixtures/library-build/` was taken — and neither is meant to be set
96
- * in a pipeline. They are read here and nowhere else, so this is the only place
97
- * an operator can be told what they do.
104
+ * in a pipeline. They are read by `uipSwitches()` here, for this and
105
+ * {@link runUipSync}, and nowhere else, so this is the only place an operator can
106
+ * be told what they do.
107
+ *
108
+ * A third, `FLOW_SDK_UIP_HOST=0`, turns off the one-process host a caller may
109
+ * ask for with `host` (see `host.ts`): every call then spawns its own `uip`,
110
+ * which is how the registry ran before flow-builder-sdk#965 and the shape to
111
+ * compare against when a prepare misbehaves.
112
+ */
113
+ export declare function defaultUipClient(options?: UipRunOptions & {
114
+ host?: HostUipClientOptions | boolean;
115
+ }): UipClient;
116
+ /**
117
+ * Run `uip` synchronously, for the one caller that cannot await:
118
+ * `bpmn/registry-fallback.ts` resolves an extension type while a builder method runs.
119
+ * Same switches as {@link defaultUipClient} (`UIP_REPLAY_DIR` answers from recordings and
120
+ * throws on an unrecorded call; `UIP_RECORD_DIR` writes each result down) and the same
121
+ * contract as {@link SpawnUipClient}: no stdin, a non-zero exit or a timeout is a result,
122
+ * a missing executable throws the install instruction.
98
123
  */
99
- export declare function defaultUipClient(options?: UipRunOptions): UipClient;
124
+ export declare function runUipSync(argv: readonly string[], options?: UipRunOptions): UipResult;