@uipath/maestro-builder-sdk 6.16.14 → 6.17.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.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/api-index.md +115 -114
  3. package/dist/api-members.md +295 -291
  4. package/dist/bindings-v2.d.ts +39 -1
  5. package/dist/bindings-v2.js +289 -9
  6. package/dist/bpmn/compile-cli.js +11 -2
  7. package/dist/bpmn/decompile.js +2 -1
  8. package/dist/bpmn/registry-fallback.js +9 -7
  9. package/dist/case/compile-cli.js +16 -6
  10. package/dist/case/decompile-cli.js +2 -1
  11. package/dist/case/decompile.js +3 -2
  12. package/dist/case/merge-cli.js +3 -2
  13. package/dist/check-cli.js +2 -30
  14. package/dist/check.js +278 -55
  15. package/dist/cli-library.d.ts +18 -4
  16. package/dist/cli-library.js +24 -6
  17. package/dist/cli-result.d.ts +9 -0
  18. package/dist/cli-result.js +12 -0
  19. package/dist/compile-cli.js +82 -86
  20. package/dist/core/actions.d.ts +71 -17
  21. package/dist/core/agentic-process-forms.d.ts +51 -0
  22. package/dist/core/agentic-process-forms.js +49 -0
  23. package/dist/core/binding-messages.d.ts +5 -0
  24. package/dist/core/cli-library.d.ts +16 -7
  25. package/dist/core/cli-library.js +53 -19
  26. package/dist/core/comment-text.d.ts +29 -0
  27. package/dist/core/comment-text.js +40 -0
  28. package/dist/core/connector-checks.d.ts +18 -2
  29. package/dist/core/connector-checks.js +15 -5
  30. package/dist/core/contribution-bindings.d.ts +73 -0
  31. package/dist/core/contribution-bindings.js +105 -0
  32. package/dist/core/error-port.d.ts +25 -0
  33. package/dist/core/error-port.js +36 -0
  34. package/dist/core/expr-check.js +7 -2
  35. package/dist/core/expr.d.ts +11 -5
  36. package/dist/core/expr.js +9 -3
  37. package/dist/core/json-file.d.ts +1 -0
  38. package/dist/core/json-file.js +12 -0
  39. package/dist/core/library.d.ts +19 -0
  40. package/dist/core/library.js +22 -6
  41. package/dist/core/script-return.js +4 -1
  42. package/dist/core/typed-raw-node.js +1 -0
  43. package/dist/core/typescript-module.d.ts +2 -0
  44. package/dist/core/typescript-module.js +18 -0
  45. package/dist/core-definitions.json +210 -0
  46. package/dist/decompile-cli.js +2 -1
  47. package/dist/decompile.js +64 -24
  48. package/dist/emit.js +4 -3
  49. package/dist/flow-sdk.d.ts +18 -12
  50. package/dist/flow-sdk.js +19 -8
  51. package/dist/flow-step-tree.d.ts +2 -3
  52. package/dist/flow-step-tree.js +2 -3
  53. package/dist/load.js +1 -1
  54. package/dist/merge-cli.js +3 -2
  55. package/dist/merge.d.ts +2 -1
  56. package/dist/merge.js +0 -0
  57. package/dist/path-within.d.ts +33 -0
  58. package/dist/path-within.js +41 -0
  59. package/dist/registry/archive/extract.js +2 -5
  60. package/dist/registry/curated-overlay.js +3 -2
  61. package/dist/registry/generators/descriptors.js +20 -18
  62. package/dist/registry/generators/triggers.js +4 -4
  63. package/dist/registry/prepare/messages.js +2 -1
  64. package/dist/registry/prepare/source-labels.js +60 -1
  65. package/dist/registry/prepare-cli.js +14 -4
  66. package/dist/registry/pull-cli.js +3 -2
  67. package/dist/registry/uip/client.d.ts +28 -3
  68. package/dist/registry/uip/client.js +95 -34
  69. package/dist/registry/uip/host.d.ts +41 -0
  70. package/dist/registry/uip/host.js +312 -0
  71. package/dist/serialize.d.ts +7 -0
  72. package/dist/serialize.js +414 -277
  73. package/dist/workdir.js +1 -1
  74. package/package.json +3 -3
@@ -373,7 +373,8 @@ export interface NodeOptions {
373
373
  updates?: Record<string, Expr | unknown>;
374
374
  /**
375
375
  * What the canvas shows on this node (`display.label`). Defaults to the step
376
- * name.
376
+ * name; on a trigger (`.trigger()`, `.entryPoint()`) to the trigger
377
+ * definition's label, e.g. `Manual trigger`.
377
378
  *
378
379
  * @remarks
379
380
  * Purely cosmetic, and deliberately separate from the step name: the NAME is
@@ -386,8 +387,10 @@ export interface NodeOptions {
386
387
  * .step('fetchInv', http({ url, managed: true }), { label: 'Fetch invoice' })
387
388
  * ```
388
389
  *
389
- * `.branch()`, `.switch()`, `.loop()`, `.doWhile()`, `.parallel()` and
390
- * `.terminate()` take their label as a positional argument instead.
390
+ * `.branch()`, `.switch()`, `.loop()`, `.doWhile()`, `.parallel()`,
391
+ * `.trigger()` and `.entryPoint()` take it here too, in their options
392
+ * argument. Only `.terminate()` also has a positional label
393
+ * (`.terminate('stop', 'Stop here')`); when both are given, this option wins.
391
394
  */
392
395
  label?: string;
393
396
  }
@@ -663,7 +666,7 @@ export interface BuiltEntryPoint {
663
666
  id: string;
664
667
  /** What fires this root: any built-in trigger factory, or a custom trigger. */
665
668
  trigger: TriggerSpec;
666
- /** Node options for the trigger node — exact `version` selection. */
669
+ /** Node options for the trigger node — exact `version` selection and canvas `label`. */
667
670
  options?: NodeOptions;
668
671
  /** Inputs scoped to this root (globals bound to it via `triggerNodeId`). */
669
672
  inputs: VarDecl[];
@@ -821,7 +824,7 @@ declare class StepList {
821
824
  * even a legal call: `out(step, path?)` takes two arguments.
822
825
  *
823
826
  * **Three ways the handler can end**: `.return(...)` answers the flow,
824
- * `.terminate(...)` stops the run, and `h.rejoin('<step>')` compensates and
827
+ * `.terminate(...)` stops the run, and `h.stepToRef('<step>')` compensates and
825
828
  * hands control back to a step the success path also reaches — the shape most
826
829
  * deployed flows use. A handler that just runs out of steps still gets its own
827
830
  * End: rejoining is something the author SAYS, never something wiring infers.
@@ -835,8 +838,9 @@ declare class StepList {
835
838
  *
836
839
  * **A `.loop()` container takes one too.** `core.logic.loop` declares the
837
840
  * handle, and a body step's failure routes to it: the container's envelope
838
- * carries the body's message in `detail` and the failing body step's id in
839
- * `element`, and the instance completes rather than faulting. `.doWhile()` does
841
+ * carries the body's message in `detail` and the failing body step's label
842
+ * (`display.label`, which defaults to the step name) in `element`, and the
843
+ * instance completes rather than faulting. `.doWhile()` does
840
844
  * NOT — it declares an error variable and no error handle — so that one is
841
845
  * refused by name, and its failures belong to the body step that can fail.
842
846
  *
@@ -1235,7 +1239,8 @@ declare class FlowBuilder extends StepList {
1235
1239
  *
1236
1240
  * @param spec - What starts the flow, from `scheduled(...)` or `onEvent(...)`.
1237
1241
  * @param options - Node options; `version` selects the exact definition
1238
- * version (see {@link NodeOptions}).
1242
+ * version and `label` sets what the canvas shows on the trigger node
1243
+ * (default: the trigger definition's label, e.g. `Scheduled trigger`). See {@link NodeOptions}.
1239
1244
  * @returns This builder, so calls chain.
1240
1245
  * @defaultValue the manual trigger
1241
1246
  */
@@ -1281,13 +1286,14 @@ declare class FlowBuilder extends StepList {
1281
1286
  * @param trigger - What fires this root: `manual()`, `scheduled(...)`,
1282
1287
  * `onEvent(...)`, `formTrigger()` (one field per scoped input),
1283
1288
  * `conversationTrigger()`, or `voiceTrigger()`.
1284
- * @param options - `version` (exact trigger definition version) and `inputs`
1285
- * — this root's scoped inputs, declared like `.input()`.
1289
+ * @param options - `version` (exact trigger definition version), `label`
1290
+ * (what the canvas shows on the trigger node; default: the trigger
1291
+ * definition's label, e.g. `Manual trigger`), and `inputs` — this root's scoped inputs, declared like
1292
+ * `.input()`.
1286
1293
  * @param prefixFn - Optional sub-builder for the root's prefix steps.
1287
1294
  * @returns This builder, so calls chain.
1288
1295
  */
1289
- entryPoint(id: string, trigger: TriggerSpec | FlowTrigger, options?: {
1290
- version?: string;
1296
+ entryPoint(id: string, trigger: TriggerSpec | FlowTrigger, options?: Pick<NodeOptions, 'version' | 'label'> & {
1291
1297
  inputs?: Record<string, TypeDesc | VarSpec>;
1292
1298
  }, prefixFn?: (b: StepList) => void): this;
1293
1299
  /**
package/dist/flow-sdk.js CHANGED
@@ -402,7 +402,7 @@ class StepList {
402
402
  * even a legal call: `out(step, path?)` takes two arguments.
403
403
  *
404
404
  * **Three ways the handler can end**: `.return(...)` answers the flow,
405
- * `.terminate(...)` stops the run, and `h.rejoin('<step>')` compensates and
405
+ * `.terminate(...)` stops the run, and `h.stepToRef('<step>')` compensates and
406
406
  * hands control back to a step the success path also reaches — the shape most
407
407
  * deployed flows use. A handler that just runs out of steps still gets its own
408
408
  * End: rejoining is something the author SAYS, never something wiring infers.
@@ -416,8 +416,9 @@ class StepList {
416
416
  *
417
417
  * **A `.loop()` container takes one too.** `core.logic.loop` declares the
418
418
  * handle, and a body step's failure routes to it: the container's envelope
419
- * carries the body's message in `detail` and the failing body step's id in
420
- * `element`, and the instance completes rather than faulting. `.doWhile()` does
419
+ * carries the body's message in `detail` and the failing body step's label
420
+ * (`display.label`, which defaults to the step name) in `element`, and the
421
+ * instance completes rather than faulting. `.doWhile()` does
421
422
  * NOT — it declares an error variable and no error handle — so that one is
422
423
  * refused by name, and its failures belong to the body step that can fail.
423
424
  *
@@ -464,7 +465,7 @@ class StepList {
464
465
  // `@2.4`, gated on `errorHandlingEnabled` like every other error handle), and
465
466
  // the platform routes it: a body step throwing inside a measured run left the
466
467
  // instance Completed with the container's envelope published — `detail` the
467
- // body's own message, `element` the id of the body step that failed.
468
+ // body's own message, `element` the label of the body step that failed.
468
469
  //
469
470
  // `.doWhile()` is NOT: `core.logic.dowhile@1.0` declares an error VARIABLE
470
471
  // and no error HANDLE, so an edge would leave a handle the node does not
@@ -942,7 +943,8 @@ class FlowBuilder extends StepList {
942
943
  *
943
944
  * @param spec - What starts the flow, from `scheduled(...)` or `onEvent(...)`.
944
945
  * @param options - Node options; `version` selects the exact definition
945
- * version (see {@link NodeOptions}).
946
+ * version and `label` sets what the canvas shows on the trigger node
947
+ * (default: the trigger definition's label, e.g. `Scheduled trigger`). See {@link NodeOptions}.
946
948
  * @returns This builder, so calls chain.
947
949
  * @defaultValue the manual trigger
948
950
  */
@@ -996,8 +998,10 @@ class FlowBuilder extends StepList {
996
998
  * @param trigger - What fires this root: `manual()`, `scheduled(...)`,
997
999
  * `onEvent(...)`, `formTrigger()` (one field per scoped input),
998
1000
  * `conversationTrigger()`, or `voiceTrigger()`.
999
- * @param options - `version` (exact trigger definition version) and `inputs`
1000
- * — this root's scoped inputs, declared like `.input()`.
1001
+ * @param options - `version` (exact trigger definition version), `label`
1002
+ * (what the canvas shows on the trigger node; default: the trigger
1003
+ * definition's label, e.g. `Manual trigger`), and `inputs` — this root's scoped inputs, declared like
1004
+ * `.input()`.
1001
1005
  * @param prefixFn - Optional sub-builder for the root's prefix steps.
1002
1006
  * @returns This builder, so calls chain.
1003
1007
  */
@@ -1011,7 +1015,14 @@ class FlowBuilder extends StepList {
1011
1015
  this._entryPoints.push({
1012
1016
  id,
1013
1017
  trigger: trigger,
1014
- ...(options?.version !== undefined ? { options: { version: options.version } } : {}),
1018
+ ...(options?.version !== undefined || options?.label !== undefined
1019
+ ? {
1020
+ options: {
1021
+ ...(options.version !== undefined ? { version: options.version } : {}),
1022
+ ...(options.label !== undefined ? { label: options.label } : {}),
1023
+ },
1024
+ }
1025
+ : {}),
1015
1026
  inputs,
1016
1027
  steps: b.steps,
1017
1028
  });
@@ -15,9 +15,8 @@ export declare function stopsAtEnd(steps: readonly Step[]): boolean;
15
15
  * The step lists nested DIRECTLY inside `step` — the bodies a walk over this
16
16
  * flow's own tree must descend into. Exhaustive over `Step['kind']`, so a new
17
17
  * container kind fails to compile here instead of being silently skipped by
18
- * every walker. That is how `check`'s return scan came to miss `.stepSwitch`
19
- * arms and `.stepToList` / `.onError` bodies, and warned NO_RETURN on flows
20
- * whose every path ends in `.return()` (UV-16855).
18
+ * every walker. That is how an earlier `check` return scan came to miss
19
+ * `.stepSwitch` arms and `.stepToList` / `.onError` bodies (UV-16855).
21
20
  *
22
21
  * A subflow's child flow is deliberately NOT a child here: it is another
23
22
  * flow's tree, and its returns say nothing about whether THIS flow ends.
@@ -15,9 +15,8 @@ export function stopsAtEnd(steps) {
15
15
  * The step lists nested DIRECTLY inside `step` — the bodies a walk over this
16
16
  * flow's own tree must descend into. Exhaustive over `Step['kind']`, so a new
17
17
  * container kind fails to compile here instead of being silently skipped by
18
- * every walker. That is how `check`'s return scan came to miss `.stepSwitch`
19
- * arms and `.stepToList` / `.onError` bodies, and warned NO_RETURN on flows
20
- * whose every path ends in `.return()` (UV-16855).
18
+ * every walker. That is how an earlier `check` return scan came to miss
19
+ * `.stepSwitch` arms and `.stepToList` / `.onError` bodies (UV-16855).
21
20
  *
22
21
  * A subflow's child flow is deliberately NOT a child here: it is another
23
22
  * flow's tree, and its returns say nothing about whether THIS flow ends.
package/dist/load.js CHANGED
@@ -13,7 +13,7 @@ export function resolveFlowFile(arg) {
13
13
  if (!statSync(resolvedCandidate, { throwIfNoEntry: false })?.isFile())
14
14
  continue;
15
15
  if (candidate !== arg && argStat?.isDirectory()) {
16
- console.error(`flow source: "${resolvedArg}" is a directory; using "${resolvedCandidate}"`);
16
+ process.stderr.write(`flow source: "${resolvedArg}" is a directory; using "${resolvedCandidate}"\n`);
17
17
  }
18
18
  return resolvedCandidate;
19
19
  }
package/dist/merge-cli.js CHANGED
@@ -12,7 +12,8 @@
12
12
  * See merge.ts.
13
13
  */
14
14
  import { reportResult } from './cli-result.js';
15
- import { readFileSync, writeFileSync } from 'node:fs';
15
+ import { writeJsonFile } from './core/json-file.js';
16
+ import { readFileSync } from 'node:fs';
16
17
  import { basename, resolve } from 'node:path';
17
18
  import { mergeFlow } from './merge.js';
18
19
  import { flagValue, runWhenInvokedDirectly } from './cli-run.js';
@@ -37,7 +38,7 @@ export function run(argv) {
37
38
  warn: (m) => console.error(`flow-merge: warning: ${m}`),
38
39
  });
39
40
  const out = flagValue(argv, '-o') ?? 'merged.flow';
40
- writeFileSync(out, JSON.stringify(merged, null, 2) + '\n');
41
+ writeJsonFile(out, merged);
41
42
  console.log(`flow-merge: wrote ${basename(out)} (${(merged.nodes ?? []).length} nodes, ${(merged.edges ?? []).length} edges)`);
42
43
  reportResult({ path: resolve(out), nodes: (merged.nodes ?? []).length, edges: (merged.edges ?? []).length });
43
44
  return 0;
package/dist/merge.d.ts CHANGED
@@ -25,7 +25,8 @@
25
25
  * as decompile was faithful for every node (the common case).
26
26
  *
27
27
  * Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
28
- * preserved from the original; `definitions` and `bindings` are unioned
28
+ * preserved from the original; `subflows` and edges (with their ids) come from
29
+ * `edited`; `definitions` and `bindings` are unioned
29
30
  * (original wins on conflict, since the original's are the tenant-resolved
30
31
  * authority). Node ids are stable by construction (decompile names every step by
31
32
  * its node id), which is what lets every re-attach key on id.
package/dist/merge.js CHANGED
Binary file
@@ -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.