@uipath/maestro-builder-sdk 5.2.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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/dist/bindings-v2.d.ts +37 -0
  4. package/dist/bindings-v2.js +155 -0
  5. package/dist/bindings.d.ts +6 -0
  6. package/dist/bindings.js +6 -0
  7. package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
  8. package/dist/bpmn/bpmn-expr-check.js +218 -0
  9. package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
  10. package/dist/bpmn/bpmn-sdk.js +1476 -0
  11. package/dist/bpmn/check-cli.d.ts +3 -0
  12. package/dist/bpmn/check-cli.js +32 -0
  13. package/dist/bpmn/check.d.ts +25 -0
  14. package/dist/bpmn/check.js +430 -0
  15. package/dist/bpmn/compile-cli.d.ts +3 -0
  16. package/dist/bpmn/compile-cli.js +60 -0
  17. package/dist/bpmn/connector.d.ts +56 -0
  18. package/dist/bpmn/connector.js +199 -0
  19. package/dist/bpmn/decompile-cli.d.ts +8 -0
  20. package/dist/bpmn/decompile-cli.js +54 -0
  21. package/dist/bpmn/decompile.d.ts +6 -0
  22. package/dist/bpmn/decompile.js +1108 -0
  23. package/dist/bpmn/format-profile.d.ts +27 -0
  24. package/dist/bpmn/format-profile.js +11 -0
  25. package/dist/bpmn/intsvc.d.ts +77 -0
  26. package/dist/bpmn/intsvc.js +155 -0
  27. package/dist/bpmn/load.d.ts +4 -0
  28. package/dist/bpmn/load.js +22 -0
  29. package/dist/bpmn/merge-cli.d.ts +3 -0
  30. package/dist/bpmn/merge-cli.js +45 -0
  31. package/dist/bpmn/merge.d.ts +19 -0
  32. package/dist/bpmn/merge.js +293 -0
  33. package/dist/bpmn/registry-types.json +1869 -0
  34. package/dist/bpmn/serialize.d.ts +28 -0
  35. package/dist/bpmn/serialize.js +757 -0
  36. package/dist/bpmn/tidy-cli.d.ts +2 -0
  37. package/dist/bpmn/tidy-cli.js +39 -0
  38. package/dist/bpmn/typed-node.d.ts +382 -0
  39. package/dist/bpmn/typed-node.js +551 -0
  40. package/dist/bpmn/uipath-moddle.v1.json +823 -0
  41. package/dist/bpmn/xml-ranges.d.ts +45 -0
  42. package/dist/bpmn/xml-ranges.js +151 -0
  43. package/dist/case/case-expr-check.d.ts +35 -0
  44. package/dist/case/case-expr-check.js +85 -0
  45. package/dist/case/case-sdk.d.ts +1282 -0
  46. package/dist/case/case-sdk.js +923 -0
  47. package/dist/case/check-cli.d.ts +3 -0
  48. package/dist/case/check-cli.js +31 -0
  49. package/dist/case/check.d.ts +18 -0
  50. package/dist/case/check.js +456 -0
  51. package/dist/case/compile-cli.d.ts +3 -0
  52. package/dist/case/compile-cli.js +291 -0
  53. package/dist/case/decompile-cli.d.ts +9 -0
  54. package/dist/case/decompile-cli.js +117 -0
  55. package/dist/case/decompile.d.ts +187 -0
  56. package/dist/case/decompile.js +1170 -0
  57. package/dist/case/entry-points.d.ts +79 -0
  58. package/dist/case/entry-points.js +118 -0
  59. package/dist/case/format-profile.d.ts +60 -0
  60. package/dist/case/format-profile.js +79 -0
  61. package/dist/case/ids.d.ts +24 -0
  62. package/dist/case/ids.js +76 -0
  63. package/dist/case/load.d.ts +12 -0
  64. package/dist/case/load.js +32 -0
  65. package/dist/case/preserve.d.ts +133 -0
  66. package/dist/case/preserve.js +105 -0
  67. package/dist/case/serialize.d.ts +10 -0
  68. package/dist/case/serialize.js +1414 -0
  69. package/dist/check-cli.d.ts +3 -0
  70. package/dist/check-cli.js +118 -0
  71. package/dist/check.d.ts +146 -0
  72. package/dist/check.js +4898 -0
  73. package/dist/cli/commands/bpmn.d.ts +2 -0
  74. package/dist/cli/commands/bpmn.js +73 -0
  75. package/dist/cli/commands/case.d.ts +2 -0
  76. package/dist/cli/commands/case.js +60 -0
  77. package/dist/cli/commands/flow.d.ts +2 -0
  78. package/dist/cli/commands/flow.js +99 -0
  79. package/dist/cli/commands/registry.d.ts +10 -0
  80. package/dist/cli/commands/registry.js +170 -0
  81. package/dist/cli/common.d.ts +30 -0
  82. package/dist/cli/common.js +92 -0
  83. package/dist/cli/index.d.ts +2 -0
  84. package/dist/cli/index.js +18 -0
  85. package/dist/cli-run.d.ts +2 -0
  86. package/dist/cli-run.js +42 -0
  87. package/dist/compile-cli.d.ts +3 -0
  88. package/dist/compile-cli.js +160 -0
  89. package/dist/config.d.ts +47 -0
  90. package/dist/config.js +201 -0
  91. package/dist/core/actions.d.ts +3244 -0
  92. package/dist/core/actions.js +1062 -0
  93. package/dist/core/binding-messages.d.ts +1 -0
  94. package/dist/core/binding-messages.js +16 -0
  95. package/dist/core/bindings.d.ts +12 -0
  96. package/dist/core/bindings.js +67 -0
  97. package/dist/core/ceql.d.ts +73 -0
  98. package/dist/core/ceql.js +272 -0
  99. package/dist/core/cli-spelling.d.ts +39 -0
  100. package/dist/core/cli-spelling.js +62 -0
  101. package/dist/core/connector-raw-node.d.ts +21 -0
  102. package/dist/core/connector-raw-node.js +47 -0
  103. package/dist/core/connectors.d.ts +184 -0
  104. package/dist/core/connectors.js +128 -0
  105. package/dist/core/expr-check.d.ts +93 -0
  106. package/dist/core/expr-check.js +186 -0
  107. package/dist/core/expr.d.ts +323 -0
  108. package/dist/core/expr.js +400 -0
  109. package/dist/core/index.d.ts +10 -0
  110. package/dist/core/index.js +10 -0
  111. package/dist/core/library.d.ts +440 -0
  112. package/dist/core/library.js +1183 -0
  113. package/dist/core/list-envelope.d.ts +45 -0
  114. package/dist/core/list-envelope.js +97 -0
  115. package/dist/core/lookup-store.d.ts +22 -0
  116. package/dist/core/lookup-store.js +85 -0
  117. package/dist/core/lookups.d.ts +227 -0
  118. package/dist/core/lookups.js +231 -0
  119. package/dist/core/node-classes.d.ts +234 -0
  120. package/dist/core/node-classes.js +158 -0
  121. package/dist/core/script-return.d.ts +68 -0
  122. package/dist/core/script-return.js +197 -0
  123. package/dist/core/stable-id.d.ts +28 -0
  124. package/dist/core/stable-id.js +37 -0
  125. package/dist/core-definitions.json +16249 -0
  126. package/dist/decompile-cli.d.ts +3 -0
  127. package/dist/decompile-cli.js +131 -0
  128. package/dist/decompile.d.ts +138 -0
  129. package/dist/decompile.js +2482 -0
  130. package/dist/emit.d.ts +10 -0
  131. package/dist/emit.js +60 -0
  132. package/dist/event-filters.d.ts +130 -0
  133. package/dist/event-filters.js +137 -0
  134. package/dist/flow-expr-check.d.ts +17 -0
  135. package/dist/flow-expr-check.js +193 -0
  136. package/dist/flow-sdk.d.ts +1341 -0
  137. package/dist/flow-sdk.js +1164 -0
  138. package/dist/flow-variables.d.ts +13 -0
  139. package/dist/flow-variables.js +42 -0
  140. package/dist/format-profile.d.ts +72 -0
  141. package/dist/format-profile.js +97 -0
  142. package/dist/generators/_connections.py +287 -0
  143. package/dist/generators/_events.py +355 -0
  144. package/dist/generators/_lookups.py +114 -0
  145. package/dist/generators/_naming.py +185 -0
  146. package/dist/generators/_objects.py +299 -0
  147. package/dist/generators/_resolve.py +273 -0
  148. package/dist/generators/convert_library_to_md.py +1441 -0
  149. package/dist/generators/extract_library.py +125 -0
  150. package/dist/generators/generate_connectors_ts.py +936 -0
  151. package/dist/generators/generate_library_json.py +2527 -0
  152. package/dist/generators/generate_triggers_ts.py +110 -0
  153. package/dist/generators/materialize_registry.py +155 -0
  154. package/dist/generators/prepare_connector.py +1688 -0
  155. package/dist/index.d.ts +35 -0
  156. package/dist/index.js +35 -0
  157. package/dist/ixp-definition.json +1982 -0
  158. package/dist/library.d.ts +6 -0
  159. package/dist/library.js +6 -0
  160. package/dist/load.d.ts +16 -0
  161. package/dist/load.js +95 -0
  162. package/dist/merge-cli.d.ts +3 -0
  163. package/dist/merge-cli.js +47 -0
  164. package/dist/merge.d.ts +125 -0
  165. package/dist/merge.js +0 -0
  166. package/dist/migrate.d.ts +15 -0
  167. package/dist/migrate.js +245 -0
  168. package/dist/node-runtime.d.ts +3 -0
  169. package/dist/node-runtime.js +22 -0
  170. package/dist/package-name.d.ts +17 -0
  171. package/dist/package-name.js +17 -0
  172. package/dist/ref-resolve.d.ts +77 -0
  173. package/dist/ref-resolve.js +170 -0
  174. package/dist/registry/cache.d.ts +55 -0
  175. package/dist/registry/cache.js +134 -0
  176. package/dist/registry/curated-overlay.d.ts +16 -0
  177. package/dist/registry/curated-overlay.js +62 -0
  178. package/dist/registry/extractor.d.ts +22 -0
  179. package/dist/registry/extractor.js +18 -0
  180. package/dist/registry/materialize-bin.d.ts +2 -0
  181. package/dist/registry/materialize-bin.js +7 -0
  182. package/dist/registry/materialize-cli.d.ts +1 -0
  183. package/dist/registry/materialize-cli.js +46 -0
  184. package/dist/registry/materializer.d.ts +23 -0
  185. package/dist/registry/materializer.js +34 -0
  186. package/dist/registry/path-cli.d.ts +1 -0
  187. package/dist/registry/path-cli.js +56 -0
  188. package/dist/registry/prepare-cli.d.ts +62 -0
  189. package/dist/registry/prepare-cli.js +185 -0
  190. package/dist/registry/pull-cli.d.ts +1 -0
  191. package/dist/registry/pull-cli.js +274 -0
  192. package/dist/registry/python.d.ts +26 -0
  193. package/dist/registry/python.js +46 -0
  194. package/dist/registry/search-cli.d.ts +52 -0
  195. package/dist/registry/search-cli.js +159 -0
  196. package/dist/schema-version.d.ts +165 -0
  197. package/dist/schema-version.js +148 -0
  198. package/dist/serialize.d.ts +64 -0
  199. package/dist/serialize.js +6398 -0
  200. package/lib/README.md +112 -0
  201. package/lib/library-json.samples/index.json +158 -0
  202. package/lib/library-json.samples/invariant-collections.json +83 -0
  203. package/lib/library-json.samples/list-envelopes.json +123 -0
  204. package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
  205. package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
  206. package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
  207. package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
  208. package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
  209. package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
  210. package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
  211. package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
  212. package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
  213. package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
  214. package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
  215. package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
  216. package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
  217. package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
  218. package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
  219. package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
  220. package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
  221. package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
  222. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
  223. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
  224. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
  225. package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
  226. package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
  227. package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
  228. package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
  229. package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
  230. package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
  231. package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
  232. package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
  233. package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
  234. package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
  235. package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
  236. package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
  237. package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
  238. package/package.json +85 -0
@@ -0,0 +1,1183 @@
1
+ /**
2
+ * library — resolve Integration Service connector definitions from the on-disk
3
+ * connector library cache (the same `library-json` the Flow Code arm uses).
4
+ *
5
+ * For a connector action we need two files per action:
6
+ * <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
7
+ * <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
8
+ */
9
+ import { readFileSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
12
+ import { lookupSpecOf } from './lookups.js';
13
+ import { loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
14
+ export class Library {
15
+ layers = [];
16
+ /** The LIST-ENVELOPE LEDGER, overlay first so a prepared entry can add a row. */
17
+ envelopeRows = [];
18
+ /**
19
+ * Everything `prepare --resolve` has recorded, from the overlay only.
20
+ *
21
+ * Never from the baked library: a resolution is a fact about ONE tenant's
22
+ * data, so shipping one in the published archive would hand every consumer an
23
+ * id from someone else's connection.
24
+ */
25
+ lookupResolutions;
26
+ /** Collections the ledger declares connector-invariant; overlay unions over baked. */
27
+ invariantCollections;
28
+ constructor(dir, opts = {}) {
29
+ this.lookupResolutions = loadLookupResolutions(opts.overlayDir);
30
+ this.invariantCollections = new Set([
31
+ ...loadInvariantCollections(dir),
32
+ ...loadInvariantCollections(opts.overlayDir),
33
+ ]);
34
+ // Overlay first (local wins), then the baked library.
35
+ if (opts.overlayDir) {
36
+ const oidx = join(opts.overlayDir, 'index.json');
37
+ if (existsSync(oidx))
38
+ this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(readFileSync(oidx, 'utf8')).entries ?? [], overlay: true });
39
+ this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir));
40
+ }
41
+ this.envelopeRows.push(...loadListEnvelopes(dir));
42
+ const idx = join(dir, 'index.json');
43
+ if (!existsSync(idx))
44
+ throw new Error(`connector library not found at ${idx}. Build it first (uipath-flow-v2-setup-library) ` +
45
+ `or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`);
46
+ this.layers.push({ dir, entries: JSON.parse(readFileSync(idx, 'utf8')).entries ?? [] });
47
+ }
48
+ /**
49
+ * Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
50
+ *
51
+ * `objectName` picks WHICH object a **generic** operation addresses; see
52
+ * {@link Library.resolveType} for what happens when it is omitted on a
53
+ * nodeType that needs it, and when it names an object the library has never
54
+ * heard of.
55
+ *
56
+ * The event family lives under two sibling namespaces and is resolved by
57
+ * `resolveEvent` below — same files, same sidecar convention, different
58
+ * position in the flow.
59
+ */
60
+ /**
61
+ * One operation's resolvable lookup fields, keyed by the WIRE field name.
62
+ *
63
+ * The same set the descriptor generator emits helpers for, decided by the same
64
+ * rules ({@link lookupSpecOf}) over the same field vocabulary — so a `check`
65
+ * diagnostic can never name a `.by*` the generator did not write.
66
+ *
67
+ * @param key - Connector library key.
68
+ * @param action - Operation id.
69
+ * @param version - Pin an operation version; newest otherwise.
70
+ * @param objectName - The object a generic operation addresses.
71
+ * @returns `{ field: spec }`, empty when the operation has no resolvable lookups.
72
+ */
73
+ lookupSpecs(key, action, version, objectName) {
74
+ let resolved;
75
+ try {
76
+ resolved = this.resolve(key, action, version, objectName);
77
+ }
78
+ catch {
79
+ return {};
80
+ }
81
+ const out = {};
82
+ for (const field of resolved.fields) {
83
+ const spec = lookupSpecOf(field.reference, this.invariantCollections);
84
+ if (spec)
85
+ out[field.name] = spec;
86
+ }
87
+ return out;
88
+ }
89
+ resolve(key, action, version, objectName) {
90
+ return this.resolveType(`uipath.connector.${key}.${action}`, version, objectName);
91
+ }
92
+ /**
93
+ * Whether a connector step's schema is materialized locally — see
94
+ * {@link SchemaReadiness}. Index rows are enough for the `object` question
95
+ * (activityType + objectName travel on every row); the `customFields`
96
+ * question reads one canonical entry, because the discovery flags and
97
+ * `requiresConnectionForSchema` live there and nowhere thinner.
98
+ *
99
+ * `undefined` means "this library has never heard of the operation", which
100
+ * is a different answer from "unprepared": the caller fails open on it, the
101
+ * same rule every other check callback follows.
102
+ */
103
+ schemaReadiness(key, action, version, object) {
104
+ const nodeType = `uipath.connector.${key}.${action}`;
105
+ const rows = [];
106
+ for (const layer of this.layers) {
107
+ for (const row of layer.entries) {
108
+ if (row?.nodeType !== nodeType)
109
+ continue;
110
+ if (version && String(row.version) !== version)
111
+ continue;
112
+ rows.push({ row, dir: layer.dir, overlay: layer.overlay === true });
113
+ }
114
+ }
115
+ if (rows.length === 0)
116
+ return undefined;
117
+ const generic = rows.some(({ row }) => row.activityType === 'Generic'
118
+ || row.activityType === 'GenericExpanded'
119
+ || row.objectFromDiscovery === true);
120
+ if (object) {
121
+ // An explicit object on a curated op is compile's business, not owed work.
122
+ if (!generic)
123
+ return { kind: 'static', prepared: true };
124
+ if (rows.some(({ row }) => row.objectName === object)) {
125
+ return { kind: 'object', prepared: true };
126
+ }
127
+ const canonical = rows.find(({ row }) => typeof row.objectName === 'string' && row.objectName !== ''
128
+ && row.objectName.toLowerCase() === object.toLowerCase())?.row.objectName;
129
+ return {
130
+ kind: 'object',
131
+ prepared: false,
132
+ ...(canonical ? { canonical: String(canonical) } : {}),
133
+ };
134
+ }
135
+ // No object named. A Generic op without one is a different, existing
136
+ // failure (compile refuses it outright); nothing to add here.
137
+ if (generic)
138
+ return { kind: 'static', prepared: true };
139
+ // Curated: is the real field set connection-resolved? Decided FIRST, from
140
+ // the canonical entry, so `kind` is never a label of convenience — an
141
+ // overlay row alone used to earn `customFields` without anyone checking
142
+ // the op resolves anything, which was a lie waiting for a consumer that
143
+ // branches on kind. Read the baked layer's entry where one exists: a
144
+ // prepared overlay copy may not carry the discovery block.
145
+ let entry;
146
+ try {
147
+ const source = rows.find(({ overlay }) => !overlay) ?? rows[0];
148
+ entry = JSON.parse(readFileSync(join(source.dir, source.row.path), 'utf8'));
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ const connectionResolved = entry?.runtime?.requiresConnectionForSchema === true
154
+ || entry?.connector?.discovery?.hasCustomFieldDiscovery === true;
155
+ if (!connectionResolved)
156
+ return { kind: 'static', prepared: true };
157
+ if (rows.some(({ overlay }) => overlay))
158
+ return { kind: 'customFields', prepared: true };
159
+ let bakedFields = [];
160
+ try {
161
+ bakedFields = this.resolve(key, action, version).fields.map((f) => f.name);
162
+ }
163
+ catch {
164
+ bakedFields = [];
165
+ }
166
+ return { kind: 'customFields', prepared: false, bakedFields };
167
+ }
168
+ /**
169
+ * Reverse a resolved connector activity back to the symbolic authoring key.
170
+ * Case decompile uses the same library that compile will use, so it never has
171
+ * to guess an action name from a display label.
172
+ */
173
+ identifyAction(wire) {
174
+ const prefix = `uipath.connector.${wire.connectorKey}.`;
175
+ for (const layer of this.layers) {
176
+ for (const indexed of layer.entries) {
177
+ if (indexed.connectorKey !== wire.connectorKey || !String(indexed.nodeType ?? '').startsWith(prefix))
178
+ continue;
179
+ if (wire.version && indexed.version !== wire.version)
180
+ continue;
181
+ const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
182
+ const operation = entry.operation ?? {};
183
+ if (wire.httpMethod && String(operation.httpMethod ?? '').toUpperCase() !== wire.httpMethod.toUpperCase())
184
+ continue;
185
+ if (wire.endpoint && operation.path !== wire.endpoint)
186
+ continue;
187
+ if (wire.objectName && operation.objectName !== wire.objectName)
188
+ continue;
189
+ const siblings = layer.entries.filter((candidate) => candidate.nodeType === indexed.nodeType);
190
+ return {
191
+ key: wire.connectorKey,
192
+ action: String(indexed.nodeType).slice(prefix.length),
193
+ version: String(indexed.version),
194
+ ...(siblings.length > 1 && operation.objectName ? { object: String(operation.objectName) } : {}),
195
+ };
196
+ }
197
+ }
198
+ throw new Error(`connector activity not found in library: ${wire.connectorKey} ${wire.httpMethod ?? '?'} ${wire.endpoint ?? '?'}`);
199
+ }
200
+ /**
201
+ * Resolve a connector EVENT: the START trigger
202
+ * (`uipath.connector.trigger.<key>.<event>`, a bpmn:StartEvent — the flow
203
+ * begins when the event fires) or the mid-flow wait
204
+ * (`uipath.connector.event.<key>.<event>`, a bpmn:ReceiveTask — the flow pauses
205
+ * until it fires). The platform ships both for one connector operation, with
206
+ * the same `uiPathActivityTypeId`; the namespace is the only difference.
207
+ */
208
+ /**
209
+ * The connector's discovery classification, from whichever layer carries it.
210
+ *
211
+ * `resolveType` takes METADATA from the highest-priority layer, and a
212
+ * `prepare-connector` overlay writes `connector: { key }` with no
213
+ * `discovery` block — and a `runtime.requiresConnectionForSchema: false`
214
+ * that CONTRADICTS the baked library (jira is `hasFieldDiscovery: true`
215
+ * there). So reading discovery off the resolved entry silently reclassifies
216
+ * every prepared connector as static, which is exactly backwards: preparing
217
+ * is what an author does on the connectors whose schema comes from the
218
+ * connection. Fields legitimately come from the overlay; this connector-level
219
+ * fact does not, so it is read across layers and the first real block wins.
220
+ */
221
+ connectorDiscovery(kind, key, event, objectName) {
222
+ const nodeType = `uipath.connector.${kind}.${key}.${event}`;
223
+ for (const layer of this.layers) {
224
+ const rc = this.resolveInLayer(layer, nodeType, undefined, objectName);
225
+ const discovery = rc?.entry
226
+ ?.connector?.discovery;
227
+ if (discovery && typeof discovery === 'object' && Object.keys(discovery).length) {
228
+ return discovery;
229
+ }
230
+ }
231
+ return {};
232
+ }
233
+ resolveEvent(kind, key, event, version, objectName) {
234
+ const nodeType = `uipath.connector.${kind}.${key}.${event}`;
235
+ let rc;
236
+ try {
237
+ rc = this.resolveType(nodeType, version, objectName);
238
+ }
239
+ catch (error) {
240
+ // The registry dump carries the WAIT form of every connector event and the
241
+ // START form of none (448 `.event.` entries, 0 `.trigger.`; only the curated
242
+ // overlay adds a handful of `.trigger.` pairs). The platform ships both for
243
+ // one operation and the namespace is the only difference, so the start form
244
+ // is DERIVED from its wait sibling here — measured against the platform's
245
+ // own `registry get` answers for both forms (see `deriveTriggerForm`).
246
+ if (kind !== 'trigger')
247
+ throw error;
248
+ let sibling;
249
+ try {
250
+ sibling = this.resolveType(`uipath.connector.event.${key}.${event}`, version, objectName);
251
+ }
252
+ catch {
253
+ throw error;
254
+ }
255
+ rc = deriveTriggerForm(sibling, nodeType);
256
+ }
257
+ return normalizeEventOperation(rc);
258
+ }
259
+ /**
260
+ * Reverse a resolved connector event payload back to its symbolic event name.
261
+ * Case decompile uses the same library that compile will use, mirroring
262
+ * {@link identifyAction} for connector activities.
263
+ */
264
+ identifyEvent(kind, wire) {
265
+ const prefix = `uipath.connector.${kind}.${wire.connectorKey}.`;
266
+ for (const layer of this.layers) {
267
+ for (const indexed of layer.entries) {
268
+ if (indexed.connectorKey !== wire.connectorKey || !String(indexed.nodeType ?? '').startsWith(prefix))
269
+ continue;
270
+ if (wire.version && indexed.version !== wire.version)
271
+ continue;
272
+ const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
273
+ const operation = normalizeEventOperation({ entry, v1def: readV1def(join(layer.dir, indexed.path)) }).entry.operation ?? {};
274
+ // A GENERIC event's entry names no object — the wire's object is the
275
+ // author's choice, so it identifies nothing here and comes back as `object`.
276
+ const generic = operation.activityType === 'GenericTrigger';
277
+ if (!generic && wire.objectName && operation.objectName !== wire.objectName)
278
+ continue;
279
+ if (wire.eventOperation && operation.eventOperation !== wire.eventOperation)
280
+ continue;
281
+ if (wire.eventMode && operation.eventMode !== wire.eventMode)
282
+ continue;
283
+ return {
284
+ key: wire.connectorKey,
285
+ event: String(indexed.nodeType).slice(prefix.length),
286
+ version: String(indexed.version),
287
+ ...(generic && wire.objectName ? { object: wire.objectName } : {}),
288
+ };
289
+ }
290
+ }
291
+ throw new Error(`connector ${kind} event not found in library: ${wire.connectorKey} `
292
+ + `${wire.eventOperation ?? '?'} ${wire.objectName ?? '?'}`);
293
+ }
294
+ /**
295
+ * The EVENT PARAMETERS a connector event operation declares — the `where` half
296
+ * of a subscription — or `undefined` if the library has never heard of the
297
+ * operation.
298
+ *
299
+ * `{ known: true, names: [] }` and `undefined` are DIFFERENT answers, and the
300
+ * difference is the whole reason this method exists: "this operation takes no
301
+ * event parameters" is a fact (HTTP Webhook scopes by the connection, so its
302
+ * only correct `where` is `{}`), while "I could not look it up" is not. Reading
303
+ * an empty list as "none declared, so scope it anyway" is what made
304
+ * `EVENT_NO_SCOPE` reject correct authoring for four of the FIVE event
305
+ * OPERATIONS the deployed corpus contains.
306
+ *
307
+ * Two declaration sites, in order of authority:
308
+ *
309
+ * 1. the v1def's `eventParameters.fields` — the registry's OWN enrichment,
310
+ * returned verbatim by `uip maestro flow registry get <trigger nodeType>
311
+ * --connection-id <id>`. Present only on the `.trigger.` form (IS enriches
312
+ * per node type, not per operation), so the pair is tried together;
313
+ * 2. the entry's `inputSchema.fields`, where a curated def declares them as
314
+ * `eventParameters.<name>` alongside `filter.<name>` — PR 10's convention.
315
+ *
316
+ * Note this is the one family where `inputSchema.fields` IS authoritative. For
317
+ * connector ACTIONS it is a lossy third summary (`readFields`' doc comment: 5,243
318
+ * of 7,029 entries disagree with the union) — but both event node types ship
319
+ * `inputDefinition: {}` in every version, so there is no other declaration to
320
+ * disagree with, and the whole population is the curated overlay's four defs (the
321
+ * raw library snapshot carries ZERO trigger/event entries).
322
+ */
323
+ eventParameterFields(key, event, objectName) {
324
+ let resolved;
325
+ const declaredOnEitherForm = [];
326
+ const selectors = [];
327
+ let known = false;
328
+ for (const kind of ['event', 'trigger']) {
329
+ let rc;
330
+ try {
331
+ rc = this.resolveEvent(kind, key, event, undefined, objectName);
332
+ }
333
+ catch {
334
+ continue; // the sibling may exist even when this one does not
335
+ }
336
+ resolved = rc;
337
+ const fromV1def = rc.v1def
338
+ ?.eventParameters?.fields;
339
+ if (Array.isArray(fromV1def)) {
340
+ known = true;
341
+ for (const f of fromV1def) {
342
+ if (typeof f?.name !== 'string' || f.name === '')
343
+ continue;
344
+ declaredOnEitherForm.push(f.name);
345
+ // A REQUIRED parameter outside the event bucket selects the schema
346
+ // rather than scoping the subscription — Jira's `project` and
347
+ // `issuetype` (both required, query-bucket) are what the connector
348
+ // derives an issue's filter and output fields FROM. `uip is triggers
349
+ // describe` takes no parameter values, so a vocabulary fetched
350
+ // without them is a baseline that the real one extends.
351
+ if (f.required && (f.bucket === 'query' || f.bucket === 'path')) {
352
+ selectors.push(f.name);
353
+ }
354
+ }
355
+ continue;
356
+ }
357
+ const fromEntry = rc.entry
358
+ ?.inputSchema?.fields;
359
+ if (Array.isArray(fromEntry)) {
360
+ known = true;
361
+ for (const f of fromEntry) {
362
+ if (typeof f?.name !== 'string')
363
+ continue;
364
+ if (f.name.startsWith('eventParameters.'))
365
+ declaredOnEitherForm.push(f.name.slice('eventParameters.'.length));
366
+ }
367
+ }
368
+ }
369
+ if (!resolved)
370
+ return undefined;
371
+ const activityType = String(resolved.entry?.operation?.activityType ?? '');
372
+ const generic = activityType === 'GenericTrigger';
373
+ // The same two questions `schemaReadiness` answers for an activity, asked
374
+ // of an event: does a variant for the named object exist, and has an
375
+ // overlay prepared this event at all. Both are index facts, so they are
376
+ // read the same way — across layers, noting which layer is the overlay.
377
+ let discovery = (resolved.entry
378
+ ?.connector?.discovery ?? {});
379
+ if (!Object.keys(discovery).length) {
380
+ for (const kind of ['event', 'trigger']) {
381
+ discovery = this.connectorDiscovery(kind, key, event, objectName);
382
+ if (Object.keys(discovery).length)
383
+ break;
384
+ }
385
+ }
386
+ let objectPrepared = false;
387
+ let overlayPrepared = false;
388
+ for (const layer of this.layers) {
389
+ for (const row of layer.entries) {
390
+ const nodeType = String(row?.nodeType ?? '');
391
+ if (nodeType !== `uipath.connector.event.${key}.${event}`
392
+ && nodeType !== `uipath.connector.trigger.${key}.${event}`)
393
+ continue;
394
+ if (layer.overlay === true)
395
+ overlayPrepared = true;
396
+ if (objectName && row.objectName === objectName)
397
+ objectPrepared = true;
398
+ }
399
+ }
400
+ const objectsFromConnection = Boolean(discovery.hasObjectDiscovery || discovery.hasCustomObjectDiscovery);
401
+ return {
402
+ known,
403
+ names: [...new Set(declaredOnEitherForm)],
404
+ generic,
405
+ object: resolved.objectName,
406
+ selectors: [...new Set(selectors)],
407
+ objectUnprepared: Boolean(generic && objectsFromConnection && objectName && !objectPrepared),
408
+ customFieldsUnprepared: Boolean(discovery.hasCustomFieldDiscovery && !overlayPrepared),
409
+ // Whether a PREPARED vocabulary can still be short. For an ACTIVITY it
410
+ // cannot: `resources describe` takes `-f`/`--action`, so a prepare can
411
+ // supply the parent values and materialize the custom fields. The
412
+ // trigger surface has NEITHER flag — `triggers describe` accepts only
413
+ // connector, operation, object and `--connection-id` — so wherever the
414
+ // schema comes from the connection at all, the event's filter list is a
415
+ // subset no prepare can close. Jira's designer grows past the 2 fields
416
+ // `describe` returns once a project and issue type are chosen; nothing
417
+ // we can call reaches that.
418
+ //
419
+ // ANY discovery flag, not `hasCustomFieldDiscovery` alone: the registry
420
+ // classifies jira `static` with `hasFieldDiscovery: true` and
421
+ // `hasCustomFieldDiscovery: false`, so keying on the custom flag would
422
+ // hard-error on the one connector we have measured growing. This is the
423
+ // same predicate as `runtime.requiresConnectionForSchema`, recomputed
424
+ // from `discovery` because the overlay's copy of that flag is wrong.
425
+ filtersPartial: Object.entries(discovery)
426
+ .some(([name, value]) => name !== 'type' && Boolean(value)),
427
+ };
428
+ }
429
+ /**
430
+ * The filterable-field names a connector event declares — the vocabulary a
431
+ * `filters` leaf may name.
432
+ *
433
+ * TWO SOURCES, because the platform builds the two kinds of event's filter
434
+ * list differently, and reading one array for both under-advertises the
435
+ * generic form by an order of magnitude:
436
+ *
437
+ * - a CURATED event filters on the fields its definition MARKS
438
+ * (`order: 1` / `isCuratedEventField`), which is what `triggers describe`
439
+ * returns as `FilterFields` and what lands in `v1def.filterFields.fields`.
440
+ * Outlook `email-received`: 15 of the Message resource's 73 fields.
441
+ * - a GENERIC event filters on EVERY field the object supports GET on,
442
+ * which is exactly `triggers describe`'s `OutputFields` and lands in the
443
+ * entry's `outputSchema.fields`. Verified against the connector
444
+ * definitions: jira `issue` 323 fields → 258 GET → 258 output; outlook
445
+ * `Message` 73 → 73 → 73; gmail `Message` 33 → 33 → 33.
446
+ *
447
+ * Reading `filterFields` for both is what made jira `record-created`
448
+ * advertise 2 filterable fields where the designer offers 258, and gmail
449
+ * `new-record-created` advertise none where it offers 33. The union is taken
450
+ * on the generic side rather than a swap, so a connector that populates both
451
+ * (outlook's generic form carries its curated sibling's 15) loses nothing.
452
+ *
453
+ * Only a connection-scoped prepare or an enriched generation writes either
454
+ * array: the thin baked manifests carry none, and for them this answers
455
+ * `undefined` (unknown), never `[]` (known-empty). A GENERIC event's
456
+ * vocabulary is per object, so pass the subscription's `object` to select
457
+ * the prepared variant.
458
+ */
459
+ eventFilterFields(key, event, objectName) {
460
+ const names = [];
461
+ let known = false;
462
+ for (const kind of ['event', 'trigger']) {
463
+ let rc;
464
+ try {
465
+ rc = this.resolveEvent(kind, key, event, undefined, objectName);
466
+ }
467
+ catch {
468
+ continue; // the sibling may exist even when this one does not
469
+ }
470
+ const marked = rc.v1def
471
+ ?.filterFields?.fields;
472
+ const generic = String(rc.entry?.operation?.activityType ?? '') === 'GenericTrigger';
473
+ const getFields = generic
474
+ ? rc.entry?.outputSchema?.fields
475
+ : undefined;
476
+ const sources = [marked, getFields].filter(Array.isArray);
477
+ if (!sources.length)
478
+ continue;
479
+ known = true;
480
+ for (const source of sources)
481
+ for (const f of source)
482
+ if (typeof f?.name === 'string' && f.name !== '')
483
+ names.push(f.name);
484
+ }
485
+ if (!known)
486
+ return undefined;
487
+ return [...new Set(names)];
488
+ }
489
+ /**
490
+ * Resolve one library entry for `nodeType`. Two orthogonal concerns compose:
491
+ * - **object selection** within a layer — a GENERIC operation encodes only the
492
+ * verb in its node type and the library expands it into one entry per object,
493
+ * so `objectName` picks which. See `resolveInLayer` for the four cases (#143).
494
+ * - **field union** across layers — a connection-scoped overlay
495
+ * (`prepare-connector`) is searched before the baked library, and the fields
496
+ * of the entries each layer resolves are UNIONED: the overlay can ADD fields
497
+ * but never drop ones the baked library already exposed (C4).
498
+ */
499
+ resolveType(nodeType, version, objectName) {
500
+ // Resolve the op within each layer (overlay first), then union fields across
501
+ // the layers that carry it.
502
+ const resolved = [];
503
+ for (const layer of this.layers) {
504
+ const r = this.resolveInLayer(layer, nodeType, version, objectName);
505
+ if (r)
506
+ resolved.push(r);
507
+ }
508
+ if (resolved.length) {
509
+ // Metadata comes from the highest-priority layer (the overlay when
510
+ // present); fields are the UNION across all layers.
511
+ const primary = resolved[0];
512
+ if (resolved.length === 1)
513
+ return primary;
514
+ const byName = new Map();
515
+ // Insert lowest-priority (baked) first so a shared field keeps the baked
516
+ // position but takes the overlay's (higher-priority) definition.
517
+ for (let i = resolved.length - 1; i >= 0; i--) {
518
+ for (const f of resolved[i].fields)
519
+ byName.set(f.name, f);
520
+ }
521
+ return { ...primary, fields: [...byName.values()] };
522
+ }
523
+ // Name the two event namespaces when the caller asked for one, so a typo in
524
+ // an event name does not read as "this connector is missing".
525
+ const hint = /^uipath\.connector\.(trigger|event)\./.test(nodeType)
526
+ ? ` Event triggers are curated per connector operation — check the event name against `
527
+ + `the library index (\`index.json\`), and remember the two namespaces: `
528
+ + `\`…connector.trigger.…\` starts a flow, \`…connector.event.…\` waits mid-flow.`
529
+ : '';
530
+ throw new Error(`connector not in library: ${nodeType}${version ? '@' + version : ''}.${hint}`
531
+ + this.describeMiss(nodeType));
532
+ }
533
+ /**
534
+ * Turn a lookup miss into something actionable: which operations this connector
535
+ * DOES carry, the nearest spelling to what was asked for, and where the library
536
+ * being searched actually is.
537
+ *
538
+ * Without this the message was a dead end. In the 2026-09-09 case eval an agent
539
+ * read the tenant's `objectName` (`send-mail-v2`) off `uip maestro case spec`
540
+ * and passed it as the OPERATION slug; the real slug is `send-email`, and
541
+ * `connector not in library: …send-mail-v2.` said nothing that would reveal
542
+ * that. It resorted to grepping the library's JSON out of `node_modules`.
543
+ */
544
+ describeMiss(nodeType) {
545
+ // `uipath.connector.<key>.<op>`, or `uipath.connector.(trigger|event).<key>.<ev>`
546
+ // for the two event namespaces. No connector key contains a dot.
547
+ const parts = nodeType.split('.');
548
+ const eventNs = parts[2] === 'trigger' || parts[2] === 'event';
549
+ const key = eventNs ? parts[3] : parts[2];
550
+ const asked = (eventNs ? parts.slice(4) : parts.slice(3)).join('.');
551
+ if (!key)
552
+ return '';
553
+ const prefix = eventNs ? `uipath.connector.${parts[2]}.${key}.` : `uipath.connector.${key}.`;
554
+ const ops = [...new Set(this.layers.flatMap((l) => l.entries
555
+ .map((e) => String(e.nodeType ?? ''))
556
+ .filter((t) => t.startsWith(prefix))
557
+ .map((t) => t.slice(prefix.length))))].sort();
558
+ const where = ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.`;
559
+ if (!ops.length) {
560
+ const keys = [...new Set(this.layers.flatMap((l) => l.entries.map((e) => {
561
+ const p = String(e.nodeType ?? '').split('.');
562
+ return (p[2] === 'trigger' || p[2] === 'event' ? p[3] : p[2]) ?? '';
563
+ })))].filter(Boolean).sort();
564
+ const near = nearest(key, keys);
565
+ return ` No connector "${key}" in the library (${keys.length} indexed).`
566
+ + (near && near !== key ? ` Closest: ${near}.` : '')
567
+ + where
568
+ + ' If the connector is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
569
+ }
570
+ const near = asked ? nearest(asked, ops) : undefined;
571
+ const listed = ops.length <= 12
572
+ ? `Operations: ${ops.join(', ')}.`
573
+ : `Operations (${ops.length}, first 12): ${ops.slice(0, 12).join(', ')}, … — `
574
+ + `the full list is every \`nodeType\` under "${key}" in the library's index.json.`;
575
+ return ` Connector "${key}" is present but carries no "${asked}".`
576
+ + (near ? ` Closest: ${near}.` : '')
577
+ + ` ${listed}`
578
+ + where
579
+ + ' Note the operation slug is the nodeType segment, NOT the tenant\'s `objectName`'
580
+ + ' — `uip maestro case spec` reports the latter, and the two can differ.'
581
+ + ' If the operation is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
582
+ }
583
+ /**
584
+ * Resolve the op WITHIN one layer, selecting the entry by `objectName` for a
585
+ * GENERIC operation. Returns undefined when the layer does not carry the op;
586
+ * throws on an author error (a missing/ambiguous object). The four cases (#143):
587
+ * (1) one entry — resolved (a disagreeing `objectName` is an error, unless the
588
+ * entry is an un-expanded GENERIC — then accepted connection-scoped like case 4);
589
+ * (2) many + matching `objectName` — that entry; (3) many + no `objectName` — a
590
+ * named error listing the objects; (4) many + an unlisted object — accepted with
591
+ * a warning, synthesized from a sibling entry with the object stamped in. A
592
+ * connector's object set belongs to the CONNECTION, not to the registry snapshot,
593
+ * and the platform's designer resolves such objects live.
594
+ */
595
+ resolveInLayer(layer, nodeType, version, objectName) {
596
+ const matches = layer.entries.filter((e) => e.nodeType === nodeType && (!version || e.version === version));
597
+ if (matches.length === 0)
598
+ return undefined;
599
+ if (objectName !== undefined) {
600
+ const exact = matches.find((e) => e.objectName === objectName);
601
+ if (exact)
602
+ return this.build(layer.dir, exact, nodeType);
603
+ }
604
+ if (matches.length === 1) {
605
+ // A single entry: the object is not a choice. Refuse a DISAGREEING one
606
+ // rather than quietly ignoring it — an author who passed `object` meant it.
607
+ const only = matches[0];
608
+ if (objectName !== undefined && (only.objectName ?? '') !== objectName) {
609
+ // …unless the single entry is a GENERIC op the snapshot ships
610
+ // UN-EXPANDED. Its object set belongs to the CONNECTION, exactly as in
611
+ // case 4 below — the snapshot just carries one object-less entry
612
+ // instead of many expanded ones, so entry COUNT cannot be the
613
+ // discriminator. Older snapshots pre-expanded these ops and this shape
614
+ // only ever reached case 4; a snapshot that stopped expanding turned
615
+ // the same author input into a hard error here, which broke the
616
+ // documented prepare-then-compile path for every Salesforce custom
617
+ // object (the overlay resolved `Company__c`, then the union pass hit
618
+ // this throw in the baked layer).
619
+ // A GENERIC EVENT (`record-created` on Data Fabric, Salesforce, ServiceNow,
620
+ // …) is the same shape one namespace over: one object-less entry whose
621
+ // object belongs to the connection. There the object is not a variant to
622
+ // warn about but the subscription's REQUIRED target, so it is stamped in
623
+ // silently; `check`/`compile` refuse the subscription that omits it.
624
+ if (only.activityType === 'GenericTrigger') {
625
+ return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
626
+ }
627
+ if (only.activityType === 'Generic' || only.objectFromDiscovery === true) {
628
+ process.stderr.write(` warn: connector ${nodeType}: the library carries this operation un-expanded `
629
+ + `(one object-less entry). Object "${objectName}" is accepted as a connection-scoped `
630
+ + `object — the schema comes from the generic entry and the tenant resolves the name at `
631
+ + `dispatch. If it is a typo, compile will not catch it.\n`);
632
+ return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
633
+ }
634
+ throw new Error(`connector ${nodeType} does not have an object "${objectName}": it is a single, `
635
+ + `non-generic operation addressing ${only.objectName ? `"${only.objectName}"` : 'no object'}. `
636
+ + `Drop the \`object\` option (it is only for generic operations, whose node type covers many objects).`);
637
+ }
638
+ return this.build(layer.dir, only, nodeType);
639
+ }
640
+ // Ambiguous. Either the author said nothing — which used to resolve to
641
+ // whichever object sorted first, silently — or named an object the library
642
+ // does not list.
643
+ const objects = matches
644
+ .map((e) => e.objectName)
645
+ .filter((o) => typeof o === 'string' && o.length > 0)
646
+ .sort();
647
+ if (objectName === undefined) {
648
+ throw new Error(`connector ${nodeType} is a GENERIC operation covering ${matches.length} objects — `
649
+ + `say which one with the \`object\` option, e.g. `
650
+ + `connector('${nodeType.split('.').slice(2, 3)[0]}', '${nodeType.split('.').slice(3).join('.')}', `
651
+ + `{ … }, { object: '${objects[0] ?? '<object>'}' }). ${describeObjects(objects)}`);
652
+ }
653
+ // Case 4 — an object the snapshot does not list. Accept it, but say so, and
654
+ // offer the nearest known name: a typo and a real tenant-custom object are
655
+ // indistinguishable here, and only the author knows which they meant. Prefer a
656
+ // sibling whose path template is exactly `/<its object>` — the only shape a
657
+ // substitution can re-point cleanly.
658
+ const near = nearest(objectName, objects);
659
+ process.stderr.write(` warn: connector ${nodeType}: object "${objectName}" is not one of the `
660
+ + `${objects.length} the library lists for it${near ? ` (nearest: "${near}")` : ''}. `
661
+ + `Accepted as a connection-scoped object — the schema comes from a sibling entry and the `
662
+ + `tenant resolves the name at dispatch. If it is a typo, compile will not catch it.\n`);
663
+ const sibling = this.pickDerivationSibling(layer.dir, matches);
664
+ return this.build(layer.dir, { ...sibling, objectName }, nodeType, { objectNameUnverified: true });
665
+ }
666
+ /**
667
+ * Pick the sibling entry a connection-scoped object borrows its schema from:
668
+ * the first whose path template is exactly `/<its own objectName>`, else the
669
+ * first whose template merely CONTAINS its object name, else the first entry.
670
+ * Index order breaks every tie, so the choice is stable across builds.
671
+ */
672
+ pickDerivationSibling(dir, matches) {
673
+ const templateOf = (e) => {
674
+ try {
675
+ const entry = JSON.parse(readFileSync(join(dir, e.path), 'utf8'));
676
+ return entry.operation?.pathTemplate ?? entry.operation?.path ?? '';
677
+ }
678
+ catch {
679
+ return '';
680
+ }
681
+ };
682
+ const canonical = matches.find((e) => e.objectName && templateOf(e) === `/${e.objectName}`);
683
+ if (canonical)
684
+ return canonical;
685
+ const substitutable = matches.find((e) => e.objectName && templateOf(e).includes(e.objectName));
686
+ return substitutable ?? matches[0];
687
+ }
688
+ build(dir, e, nodeType, opts = {}) {
689
+ const entryPath = join(dir, e.path);
690
+ const entry = JSON.parse(readFileSync(entryPath, 'utf8'));
691
+ const v1defRaw = JSON.parse(readFileSync(entryPath.replace(/\.json$/, '.v1def.json'), 'utf8'));
692
+ const v1def = Array.isArray(v1defRaw) ? v1defRaw[0] : v1defRaw;
693
+ // A stamped object (case 4) reaches here with the index entry's `objectName`
694
+ // replaced but the sibling's FILE on disk — so restate the object and derive
695
+ // the endpoint from the sibling's own template, rather than shipping a node
696
+ // whose `objectName` and `path` name two different objects.
697
+ // The index and the definition file can disagree about which object the
698
+ // operation addresses, and the definition is the stale side: for
699
+ // `uipath-microsoft-outlook365.send-email` the index says `send-mail-v2`
700
+ // (what the tenant's typecache reports, connector 1.39.0) while the
701
+ // definition still says `send-mail` with `/hubs/productivity/send-mail`.
702
+ // `build` reads the definition, so the compiled node dispatched the retired
703
+ // path and the runtime SendTask failed — the whole external-outcome half of
704
+ // one eval task, from a one-token disagreement.
705
+ //
706
+ // The index is the fresher side and the one resolution keys on, so restate
707
+ // the object from it and re-derive the endpoint, exactly as the stamped
708
+ // branch below does. Warned, not silent: it is upstream archive data, so the
709
+ // author should see that the two sources disagreed. Unique in the current
710
+ // archive (1 of 8035 entries).
711
+ if (!opts.objectNameUnverified && entry.operation
712
+ && typeof e.objectName === 'string' && e.objectName !== ''
713
+ && typeof entry.operation.objectName === 'string'
714
+ && entry.operation.objectName !== e.objectName) {
715
+ const stale = entry.operation.objectName;
716
+ const derived = deriveObjectPath(entry.operation, e.objectName);
717
+ process.stderr.write(` warn: connector ${nodeType}: the library index says objectName "${e.objectName}" but its `
718
+ + `definition file says "${stale}". Using the index and deriving the endpoint`
719
+ + (derived.path ? ` (${entry.operation.path} -> ${derived.path})` : '')
720
+ + `; the definition is the stale side when these disagree.\n`);
721
+ entry.operation = { ...entry.operation, objectName: e.objectName, ...derived };
722
+ }
723
+ if (opts.objectNameUnverified && entry.operation) {
724
+ entry.operation = {
725
+ ...entry.operation,
726
+ objectName: e.objectName,
727
+ ...deriveObjectPath(entry.operation, e.objectName),
728
+ };
729
+ delete entry.operation.objectDisplayName; // the sibling's label, not this object's
730
+ }
731
+ const objectName = entry.operation?.objectName ?? '';
732
+ return {
733
+ nodeType,
734
+ version: e.version,
735
+ connectorKey: entry.connector?.key ?? '',
736
+ connectorName: entry.connector?.name ?? '',
737
+ httpMethod: entry.operation?.httpMethod ?? 'POST',
738
+ endpoint: entry.operation?.pathTemplate ?? entry.operation?.path ?? '',
739
+ activityTypeId: findKey(v1def, 'uiPathActivityTypeId'),
740
+ v1def,
741
+ entry,
742
+ fields: readFields(v1def, entry),
743
+ multipartParts: readMultipartParts(entry),
744
+ objectName,
745
+ ...(opts.objectNameUnverified ? { objectNameUnverified: true } : {}),
746
+ ...this.resolveListEnvelope(entry, nodeType, objectName),
747
+ };
748
+ }
749
+ /**
750
+ * The measured LIST envelope for one resolved operation, if any.
751
+ *
752
+ * Two sources, entry-first: an OVERLAY entry may declare `envelope` on the
753
+ * entry JSON (a `prepare-connector` result that measured it live), and that
754
+ * wins; otherwise the shipped ledger beside `index.json`. Nothing is inferred
755
+ * — an operation the ledger has never measured comes back undeclared, and
756
+ * every consumer treats that as "behave exactly as before" (#156).
757
+ *
758
+ * Fenced to the LIST verb: the envelope question is only about a collection,
759
+ * and a row against a non-List operation would be a ledger mistake, so it is
760
+ * ignored rather than honoured.
761
+ */
762
+ resolveListEnvelope(entry, nodeType, objectName) {
763
+ if (entry?.operation?.name !== 'List')
764
+ return {};
765
+ if (entry.envelope !== undefined) {
766
+ return { listEnvelope: {
767
+ envelope: entry.envelope ?? null,
768
+ source: typeof entry.envelopeSource === 'string' ? entry.envelopeSource : 'the library entry',
769
+ tier: typeof entry.envelopeTier === 'string' ? entry.envelopeTier : 'A',
770
+ } };
771
+ }
772
+ const row = lookupListEnvelope(this.envelopeRows, nodeType, objectName || undefined);
773
+ return row
774
+ ? { listEnvelope: { envelope: row.envelope ?? null, source: row.source, tier: row.tier } }
775
+ : {};
776
+ }
777
+ }
778
+ /**
779
+ * Re-point a sibling entry's path template at a different object.
780
+ *
781
+ * The endpoint is **not** dispatch input — dispatch is
782
+ * `uip is resources run <verb> <connectorKey> <objectName>` and the platform's
783
+ * adapter resolves the object server-side (verified live against the tenant's
784
+ * ServiceNow connection) — but it IS in the artifact, so a node claiming
785
+ * `objectName: "acr_user"` must not also carry `path: "/advanced_curated_incident"`.
786
+ *
787
+ * Substitution only, never invention: if the sibling's template contains its own
788
+ * object name, swap it (`/advanced_curated_incident` → `/acr_user`, which is
789
+ * byte-identical to what the platform's own designer emitted for `acr_user` in
790
+ * all four green `generic_dynamic_node` nightlies). If it does not — 1,534 of the
791
+ * 5,693 generic entries have an object that never appears in their path, e.g.
792
+ * `Campaigns::Contact` → `/Campaigns/{id}/Contact/{contactId}` — the template is
793
+ * left alone and the warning already told the author the schema is a sibling's.
794
+ */
795
+ function deriveObjectPath(operation, objectName) {
796
+ const sibling = operation.objectName ?? '';
797
+ const out = {};
798
+ if (!sibling || !objectName)
799
+ return out;
800
+ for (const key of ['pathTemplate', 'path']) {
801
+ const tmpl = operation[key];
802
+ if (typeof tmpl === 'string' && tmpl.includes(sibling)) {
803
+ out[key] = tmpl.split(sibling).join(objectName);
804
+ }
805
+ }
806
+ return out;
807
+ }
808
+ /** Read the `.v1def.json` sidecar beside a library entry file. */
809
+ function readV1def(entryPath) {
810
+ const raw = JSON.parse(readFileSync(entryPath.replace(/\.json$/, '.v1def.json'), 'utf8'));
811
+ return Array.isArray(raw) ? raw[0] : raw;
812
+ }
813
+ /**
814
+ * The platform's `connectorDetail.configuration` blob — the designer's own
815
+ * declaration of an event operation (`eventOperation`, `eventMode`, `activityType`,
816
+ * …), carried as a JSON STRING inside the v1def's form. It is the one place a raw
817
+ * registry entry states the event's mode, so it is read here rather than guessed.
818
+ */
819
+ function connectorDetailConfiguration(v1def) {
820
+ const sections = Array.isArray(v1def?.form?.sections) ? v1def.form.sections : [];
821
+ for (const section of sections) {
822
+ for (const field of Array.isArray(section?.fields) ? section.fields : []) {
823
+ const raw = field?.componentProps?.connectorDetail?.configuration;
824
+ if (typeof raw !== 'string')
825
+ continue;
826
+ try {
827
+ const parsed = JSON.parse(raw);
828
+ if (parsed && typeof parsed === 'object')
829
+ return parsed;
830
+ }
831
+ catch {
832
+ // not JSON — fall through to the next field
833
+ }
834
+ }
835
+ }
836
+ return {};
837
+ }
838
+ /**
839
+ * Fill in what a raw registry EVENT entry leaves out, so every consumer of an
840
+ * event operation reads one complete shape.
841
+ *
842
+ * The 448 `.event.` entries of the registry dump carry `operation.name`
843
+ * (`CREATED`) but neither `eventOperation` nor `eventMode` — the curated overlay's
844
+ * four pairs are the only entries that do. Both are stated by the platform's own
845
+ * `connectorDetail.configuration` blob in the v1def (`{"eventOperation":"CREATED",
846
+ * "eventMode":"polling",…}`), which is what the designer reads too; the
847
+ * operation name is the same value under its registry spelling. A node emitted
848
+ * without them fails `flow validate` ("eventMode is required for trigger nodes")
849
+ * and the platform's own dispatcher refuses it (`buildEventCall`).
850
+ *
851
+ * `objectDisplayName` follows the stamped object of a generic event — the entry's
852
+ * empty label is a sibling's, not this object's.
853
+ */
854
+ function normalizeEventOperation(rc) {
855
+ const operation = rc.entry?.operation;
856
+ if (!operation || typeof operation !== 'object')
857
+ return rc;
858
+ const activityType = String(operation.activityType ?? '');
859
+ if (!/Trigger|WaitFor/.test(activityType) && !/^uipath\.connector\.(trigger|event)\./.test(rc.nodeType ?? ''))
860
+ return rc;
861
+ const declared = connectorDetailConfiguration(rc.v1def);
862
+ const eventOperation = operation.eventOperation
863
+ ?? (typeof declared.eventOperation === 'string' ? declared.eventOperation : undefined)
864
+ ?? (typeof operation.name === 'string' && operation.name !== '' ? operation.name : undefined);
865
+ const eventMode = operation.eventMode
866
+ ?? (typeof declared.eventMode === 'string' ? declared.eventMode : undefined)
867
+ ?? (typeof rc.v1def?.eventMode === 'string' ? rc.v1def.eventMode : undefined);
868
+ const normalized = {
869
+ ...operation,
870
+ ...(eventOperation !== undefined ? { eventOperation } : {}),
871
+ ...(eventMode !== undefined ? { eventMode } : {}),
872
+ ...(rc.objectNameUnverified && operation.objectName ? { objectDisplayName: operation.objectName } : {}),
873
+ };
874
+ return { ...rc, entry: { ...rc.entry, operation: normalized } };
875
+ }
876
+ /**
877
+ * Derive a connector event's START form (`uipath.connector.trigger.<key>.<event>`)
878
+ * from its WAIT form (`uipath.connector.event.<key>.<event>`).
879
+ *
880
+ * The platform serves both for one operation and the differences are structural,
881
+ * never per-connector — measured 2026-09-02 by asking `uip maestro flow registry
882
+ * get` for both forms of Data Fabric `record-created` (a GENERIC event) and
883
+ * comparing them with the curated OneDrive/Outlook/HTTP-webhook pairs:
884
+ *
885
+ * category `connector.<pkg>` → `trigger.<pkg>`
886
+ * tags `event` → `trigger`
887
+ * model.type `bpmn:ReceiveTask` → `bpmn:StartEvent`
888
+ * model.serviceType `Intsvc.WaitForEvent` → `Intsvc.EventTrigger`
889
+ * model +`entryPointId: true`, +`eventDefinition: bpmn:MessageEventDefinition`
890
+ * handles input + output + error → one output handle (a start has no
891
+ * input and no error port)
892
+ * outputDefinition output + error → output only ("…of the connector trigger")
893
+ * supportsErrorHandling, inputDefaults, debug → dropped
894
+ * activityType `CuratedWaitFor` → `CuratedTrigger` (a generic one stays `GenericTrigger`)
895
+ * runtime.bpmnType / serviceType → the start values above
896
+ *
897
+ * `model.bindings`, `model.context`, `display` (plus the start's `shape: circle`),
898
+ * the form (same `uiPathActivityTypeId`) and the version are kept verbatim.
899
+ */
900
+ function deriveTriggerForm(sibling, triggerType) {
901
+ const entry = JSON.parse(JSON.stringify(sibling.entry ?? {}));
902
+ const v1def = JSON.parse(JSON.stringify(sibling.v1def ?? {}));
903
+ const retag = (tags) => Array.isArray(tags) ? tags.map((t) => (t === 'event' ? 'trigger' : t)) : tags;
904
+ const recategorize = (category) => typeof category === 'string' ? category.replace(/^connector\./, 'trigger.') : category;
905
+ entry.nodeType = triggerType;
906
+ entry.category = recategorize(entry.category);
907
+ entry.tags = retag(entry.tags);
908
+ if (entry.operation && typeof entry.operation === 'object') {
909
+ if (entry.operation.activityType === 'CuratedWaitFor')
910
+ entry.operation.activityType = 'CuratedTrigger';
911
+ }
912
+ entry.runtime = { ...(entry.runtime ?? {}), bpmnType: 'bpmn:StartEvent', serviceType: 'Intsvc.EventTrigger' };
913
+ v1def.nodeType = triggerType;
914
+ v1def.category = recategorize(v1def.category);
915
+ v1def.tags = retag(v1def.tags);
916
+ delete v1def.supportsErrorHandling;
917
+ delete v1def.inputDefaults;
918
+ delete v1def.debug;
919
+ v1def.display = { ...(v1def.display ?? {}), shape: 'circle' };
920
+ v1def.handleConfiguration = [{
921
+ position: 'right',
922
+ handles: [{
923
+ id: 'output', type: 'source', handleType: 'output', showButton: true,
924
+ constraints: { forbiddenTargetCategories: ['trigger'] },
925
+ }],
926
+ visible: true,
927
+ }];
928
+ const model = v1def.model ?? {};
929
+ v1def.model = {
930
+ type: 'bpmn:StartEvent',
931
+ entryPointId: true,
932
+ serviceType: 'Intsvc.EventTrigger',
933
+ eventDefinition: 'bpmn:MessageEventDefinition',
934
+ ...(model.bindings !== undefined ? { bindings: model.bindings } : {}),
935
+ ...(model.context !== undefined ? { context: model.context } : {}),
936
+ };
937
+ v1def.inputDefinition = {};
938
+ const output = v1def.outputDefinition?.output;
939
+ v1def.outputDefinition = {
940
+ output: {
941
+ type: 'object',
942
+ description: 'The return value of the connector trigger.',
943
+ source: '=result.response',
944
+ var: 'output',
945
+ ...(output && typeof output === 'object' && output.schema !== undefined ? { schema: output.schema } : {}),
946
+ },
947
+ };
948
+ return { ...sibling, nodeType: triggerType, entry, v1def };
949
+ }
950
+ /**
951
+ * Name the objects an ambiguous nodeType covers, in a message a human can act on.
952
+ * Whole list when it is short; a sample plus the count when it is
953
+ * `uipath-oracle-netsuite.update-record` (197 of them) — a 197-name error is a
954
+ * wall of text nobody reads, and the library index is the full answer.
955
+ */
956
+ function describeObjects(objects) {
957
+ if (objects.length === 0)
958
+ return 'The library lists no object names for it.';
959
+ if (objects.length <= 12)
960
+ return `Objects: ${objects.join(', ')}.`;
961
+ return (`Objects (${objects.length}, first 12): ${objects.slice(0, 12).join(', ')}, … — `
962
+ + `the full list is every \`objectName\` for this nodeType in the library's index.json.`);
963
+ }
964
+ /**
965
+ * The closest known object name to `want`, or undefined when nothing is close.
966
+ *
967
+ * A case-insensitive hit is reported as-is (the index has **zero** case-folded
968
+ * collisions across all 448 ambiguous nodeTypes, so it is unambiguous); otherwise
969
+ * an edit distance within a third of the name's length, which catches a typo
970
+ * (`acr_users`) without pretending `incident` is near `acr_user`.
971
+ */
972
+ function nearest(want, objects) {
973
+ const ci = objects.find((o) => o.toLowerCase() === want.toLowerCase());
974
+ if (ci)
975
+ return ci;
976
+ let best;
977
+ let bestD = Math.max(2, Math.floor(want.length / 3));
978
+ for (const o of objects) {
979
+ const d = editDistance(want.toLowerCase(), o.toLowerCase());
980
+ if (d <= bestD) {
981
+ bestD = d;
982
+ best = o;
983
+ }
984
+ }
985
+ return best;
986
+ }
987
+ /** Levenshtein distance, two rows. Short strings only (object names). */
988
+ function editDistance(a, b) {
989
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
990
+ for (let i = 1; i <= a.length; i++) {
991
+ const cur = [i];
992
+ for (let j = 1; j <= b.length; j++) {
993
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
994
+ }
995
+ prev = cur;
996
+ }
997
+ return prev[b.length];
998
+ }
999
+ /**
1000
+ * The accepted input vocabulary for one connector operation, with each input's
1001
+ * wire LOCATION.
1002
+ *
1003
+ * A connector operation declares its inputs in two places, and reading only the
1004
+ * first made **515 of the library's 1,336 curated operations (38.5%)**
1005
+ * unauthorable — every "get one by id", every "list with a filter"
1006
+ * ([#132](https://github.com/UiPath/flow-builder-sdk/issues/132)):
1007
+ *
1008
+ * 1. **`<action>@<ver>.v1def.json` → `inputDefinition.fields`** — the request
1009
+ * BODY. For the 515 this array is literally `[]`.
1010
+ * 2. **`<action>@<ver>.json` → `operation.parameters`** — the QUERY and PATH
1011
+ * parameters, each carrying `type: "query" | "path"` and a `dataType`:
1012
+ *
1013
+ * ```jsonc
1014
+ * // uipath-atlassian-jira/get-issue@1.0.0.json
1015
+ * "operation": { "httpMethod": "GETBYID", "pathTemplate": "/curated_get_issue/{issueId}",
1016
+ * "parameters": [ { "name": "project", "type": "query", "dataType": "string", "required": true },
1017
+ * { "name": "issuetype","type": "query", "dataType": "string", "required": true },
1018
+ * { "name": "issueId", "type": "path", "dataType": "string", "required": true } ] }
1019
+ * ```
1020
+ *
1021
+ * **Why not `entry.inputSchema.fields`**, which looks like the merged list:
1022
+ * measured over the shipped library, **5,243 of 7,029 entries disagree** with
1023
+ * the union — e.g. `uipath-act-act365.delete-record` declares a `contactsId`
1024
+ * path parameter and an EMPTY `inputSchema.fields`. It is a third, lossy
1025
+ * summary, so the union of the two authoritative declarations is what we read.
1026
+ *
1027
+ * **A name in both wins as the PARAMETER** (its location is the wire truth).
1028
+ * Three curated ops do this and the endpoint template settles it: Trello
1029
+ * `add-card-attachment` declares `id` as a body field *and* as the `{id}` of
1030
+ * `/cards/{id}/attachments`.
1031
+ *
1032
+ * Multipart is a Flow-artifact transport, not a claim about the separate
1033
+ * `uip is resources run` command. The product designer emits it as
1034
+ * `detail.multipartParameters`, including binary expressions between connector
1035
+ * nodes, so authoring must preserve it even though that CLI command has no
1036
+ * multipart flag.
1037
+ *
1038
+ * **A multipart part is an author input only when it is a FILE.** The platform
1039
+ * splits a multipart operation the same way it splits every other one: the
1040
+ * author's fields are `inputDefinition.fields` (Teams `body.content`, Gmail
1041
+ * `Body`, GenAI `prompt`), and the `string`-typed multipart parameter (`body`,
1042
+ * `RagRequest`) is the transport CONTAINER the runtime composes from
1043
+ * `bodyParameters`. Measured on 413 designer-authored multipart nodes in the eval
1044
+ * archive: 0 carry a value on the string part, while `file` parts do (Slack
1045
+ * `send-file-to-channel` 70/72, Data Service `upload-file-to-record-field`
1046
+ * 20/20). Reading the string part as a required author field did three wrong
1047
+ * things at once: the generated docs advertised `body` as the REQUIRED input
1048
+ * beside the real `body.content`; `compile` refused the documented fields
1049
+ * (`'body.content'` → `missing required input "body"`, `body: { content }` →
1050
+ * `expected string, got object`); and the one shape it accepted, `body: 'text'`,
1051
+ * emitted `multipartParameters[body].value`, which the runtime ignores — verified
1052
+ * live 2026-09-02 on GenAI `content-generation`: the part value never reached the
1053
+ * model, `bodyParameters.prompt` did. So a non-file part goes to
1054
+ * {@link readMultipartParts}, not here, and its `required` flag does not bind the
1055
+ * author (the container is always sent; its fields carry their own flags).
1056
+ */
1057
+ function readFields(v1def, entry) {
1058
+ const params = authorParameters(entry);
1059
+ // LAST RESORT: `inputSchema.fields`, and only when the two authoritative
1060
+ // declarations above are BOTH empty.
1061
+ //
1062
+ // The doc comment's reason for distrusting `inputSchema.fields` stands — it is a
1063
+ // lossy third summary and 5,243 of 7,029 entries disagree with the union — but it
1064
+ // argues for PREFERRING the union, not for ignoring the summary when the union
1065
+ // says nothing at all. Where both are empty there is no disagreement to lose:
1066
+ // the choice is between the summary's fields and NO fields.
1067
+ //
1068
+ // No fields is not a neutral outcome, because `convert_library_to_md.py` reads
1069
+ // `inputSchema.fields` directly. So the markdown — the artifact the skill tells an
1070
+ // agent to author from — advertises inputs that compile then rejects as
1071
+ // `unknown input`, reporting `Required fields: (none required)`. Measured over the
1072
+ // shipped library: **804 ops, 43,991 field declarations**, including Jira
1073
+ // `insert-record`/`create_issue`, whose 83 documented fields (`fields.summary`,
1074
+ // and the REQUIRED `fields.project.key`) were all invisible to compile. An author
1075
+ // following the documentation exactly could not compile, and the diagnostic
1076
+ // actively misled — it named no required fields for an operation that has two.
1077
+ //
1078
+ // This does not weaken the unknown-field guard: a name in neither the union nor
1079
+ // the summary is still refused, which is what catches a typo.
1080
+ if ((v1def?.inputDefinition?.fields ?? []).length === 0 && params.length === 0) {
1081
+ const schema = entry?.inputSchema?.fields;
1082
+ if (Array.isArray(schema) && schema.length > 0) {
1083
+ return schema.map((f) => ({ ...f, location: 'body' }));
1084
+ }
1085
+ }
1086
+ const out = (v1def?.inputDefinition?.fields ?? []).map((f) => ({ ...f, location: 'body' }));
1087
+ for (const p of params) {
1088
+ const name = typeof p?.name === 'string' ? p.name : undefined;
1089
+ const location = PARAM_LOCATIONS[p?.type];
1090
+ if (!name || !location)
1091
+ continue;
1092
+ const field = {
1093
+ name,
1094
+ type: scalarType(p.dataType),
1095
+ required: p.required === true,
1096
+ location,
1097
+ ...(p.displayName ? { displayName: p.displayName } : {}),
1098
+ ...(p.description ? { description: p.description } : {}),
1099
+ // A QUERY parameter can declare a lookup too (Slack's `send_as` does).
1100
+ // Rebuilding the field from scratch used to drop it.
1101
+ ...(p.reference && typeof p.reference === 'object'
1102
+ ? { reference: p.reference }
1103
+ : {}),
1104
+ };
1105
+ // The parameter declaration is authoritative on location; replace a
1106
+ // same-named body field rather than shadowing it with a duplicate entry.
1107
+ const at = out.findIndex((f) => f.name === name);
1108
+ if (at >= 0)
1109
+ out[at] = field;
1110
+ else
1111
+ out.push(field);
1112
+ }
1113
+ return out;
1114
+ }
1115
+ /** `operation.parameters` minus the multipart CONTAINER parts — the ones an author fills. */
1116
+ function authorParameters(entry) {
1117
+ const params = Array.isArray(entry?.operation?.parameters) ? entry.operation.parameters : [];
1118
+ return params.filter((p) => !isContainerPart(p));
1119
+ }
1120
+ /** A multipart parameter that is not a file: the runtime composes it, the author does not fill it. */
1121
+ function isContainerPart(p) {
1122
+ return p?.type === 'multipart' && String(p?.dataType ?? 'string').toLowerCase() !== 'file';
1123
+ }
1124
+ /**
1125
+ * Every multipart part the operation declares, in declaration order, as the
1126
+ * platform lists them under `detail.multipartParameters` — the container part
1127
+ * (valueless) and the file parts (which take the author's value from `fields`).
1128
+ */
1129
+ function readMultipartParts(entry) {
1130
+ const params = Array.isArray(entry?.operation?.parameters) ? entry.operation.parameters : [];
1131
+ const out = [];
1132
+ for (const p of params) {
1133
+ if (p?.type !== 'multipart' || typeof p?.name !== 'string' || !p.name)
1134
+ continue;
1135
+ if (out.some((x) => x.name === p.name))
1136
+ continue;
1137
+ out.push({ name: p.name, dataType: typeof p.dataType === 'string' && p.dataType ? p.dataType : 'string' });
1138
+ }
1139
+ return out;
1140
+ }
1141
+ /**
1142
+ * `operation.parameters[].type` → the wire slot. The whole vocabulary the
1143
+ * shipped library uses is `query` · `path` · `multipart`. Anything else is
1144
+ * skipped: an input we cannot place is an input we must not accept.
1145
+ */
1146
+ const PARAM_LOCATIONS = {
1147
+ query: 'query',
1148
+ path: 'path',
1149
+ multipart: 'multipart',
1150
+ };
1151
+ /**
1152
+ * A parameter's `dataType` → the three scalar names `compile`'s literal
1153
+ * type-check understands. Measured across the curated library: `string` 2,009 ·
1154
+ * `integer` 230 · `boolean` 160 · `number` 3 · `double` 2.
1155
+ */
1156
+ function scalarType(dataType) {
1157
+ switch (dataType) {
1158
+ case 'integer':
1159
+ case 'number':
1160
+ case 'double':
1161
+ case 'float':
1162
+ case 'long': return 'number';
1163
+ case 'boolean': return 'boolean';
1164
+ case 'string': return 'string';
1165
+ default: return typeof dataType === 'string' ? dataType : undefined;
1166
+ }
1167
+ }
1168
+ /** Depth-first search for the first value of `key` anywhere in `obj`. */
1169
+ function findKey(obj, key) {
1170
+ if (obj == null || typeof obj !== 'object')
1171
+ return undefined;
1172
+ if (key in obj) {
1173
+ const v = obj[key];
1174
+ if (typeof v === 'string')
1175
+ return v;
1176
+ }
1177
+ for (const val of Object.values(obj)) {
1178
+ const found = findKey(val, key);
1179
+ if (found)
1180
+ return found;
1181
+ }
1182
+ return undefined;
1183
+ }