@uipath/maestro-builder-sdk 6.47.1 → 6.47.3

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.
@@ -20,6 +20,8 @@ import { createConnectorEvent, createExternalTask, intsvcOutputVar } from './int
20
20
  import { createExtensionPayload, createTypedNode, rowDeclaration, typedNodeOutputVar, verbatimOutputRows } from './typed-node.js';
21
21
  import { connectorOutputVars, typedNodeDeclaredVars } from './declared-vars.js';
22
22
  import { defaultEntryPointId } from './entry-point-id.js';
23
+ import { parseXmlRanges } from './xml-ranges.js';
24
+ import { ownEntry } from '../core/own-entry.js';
23
25
  const require = createRequire(import.meta.url);
24
26
  const uipathDescriptor = require('./uipath-moddle.v1.json');
25
27
  const NS = {
@@ -181,6 +183,22 @@ export async function serialize(built, opts = {}) {
181
183
  * of the trade. Revisit if the graders learn to treat absence as interrupting.
182
184
  */
183
185
  export function finalizeBpmnXml(xml) {
186
+ return jsonInputBodiesAsCdata(outsideCdata(xml, normalizeMarkup));
187
+ }
188
+ /**
189
+ * Apply `edit` to the markup only — never inside a CDATA section or a comment.
190
+ *
191
+ * The passes below are patterns over the text, and a CDATA payload is author data: a
192
+ * JSON value holding the literal `&#60;` or `<bpmn:boundaryEvent>` must come out of a
193
+ * second `finalizeBpmnXml` (or `tidy`) byte for byte, not rewritten as markup.
194
+ */
195
+ function outsideCdata(xml, edit) {
196
+ return xml
197
+ .split(/(<!\[CDATA\[[\s\S]*?\]\]>|<!--[\s\S]*?-->)/)
198
+ .map((part, i) => (i % 2 === 1 ? part : edit(part)))
199
+ .join('');
200
+ }
201
+ function normalizeMarkup(xml) {
184
202
  return (xml
185
203
  .replace(/<bpmn:boundaryEvent\b(?![^>]*\bcancelActivity=)([^>]*?)(\/?)>/g, '<bpmn:boundaryEvent cancelActivity="true"$1$2>')
186
204
  // Same reason, other schema default: a PARALLEL multi-instance marker is
@@ -206,6 +224,81 @@ export function finalizeBpmnXml(xml) {
206
224
  .replace(/&#(?:34|x22);/g, '&quot;')
207
225
  .replace(/&#(?:39|x27);/g, '&apos;'));
208
226
  }
227
+ /**
228
+ * Rewrite every json `uipath:input` text body as CDATA — the one body shape the cloud
229
+ * engine reads.
230
+ *
231
+ * `PO.BpmnEngine`'s `UiPathExtensionsProvider` (`ExtractContextInputs`,
232
+ * `ExtractActivityInputs`) takes an input's value from an `XCData` child or a `value`
233
+ * attribute and filters out any row with neither. moddle writes a body as entity-escaped
234
+ * TEXT, which is neither, so the engine silently dropped every json input the SDK
235
+ * authored: a script task ran with no `args` (`vars is not defined`), a connector task
236
+ * sent no body. `check`, `compile` and `uip maestro bpmn validate` all read text and
237
+ * CDATA alike, so nothing caught it before the run (#1390). Studio Web writes CDATA.
238
+ *
239
+ * moddle has no CDATA option for a body property, so this rewrites its output — by
240
+ * element range (`xml-ranges.ts`), not a pattern over the document. It runs LAST in
241
+ * `finalizeBpmnXml`, after the markup passes, which skip CDATA (`outsideCdata`), so no
242
+ * pass reaches into a payload this writes. An input whose content is not plain text (already CDATA,
243
+ * as `tidy` may hand it, or empty) is left alone, so this is idempotent.
244
+ */
245
+ function jsonInputBodiesAsCdata(xml) {
246
+ const bodies = [];
247
+ const collect = (nodes) => {
248
+ for (const node of nodes) {
249
+ if (node.name === 'uipath:input' && node.attributes.type === 'json')
250
+ bodies.push(node);
251
+ else
252
+ collect(node.children);
253
+ }
254
+ };
255
+ collect(parseXmlRanges(xml));
256
+ let out = xml;
257
+ // Splice from the end so the earlier offsets stay valid.
258
+ for (const input of bodies.sort((a, b) => b.innerStart - a.innerStart)) {
259
+ const text = xml.slice(input.innerStart, input.innerEnd);
260
+ if (text === '' || text.includes('<'))
261
+ continue;
262
+ out = out.slice(0, input.innerStart) + cdata(input.attributes.name ?? '', decodeXmlText(text)) + out.slice(input.innerEnd);
263
+ }
264
+ return out;
265
+ }
266
+ const NAMED_ENTITY = { lt: '<', gt: '>', amp: '&', quot: '"', apos: "'" };
267
+ /** Decode XML character data in one pass, so `&amp;lt;` stays the text `&lt;`. */
268
+ function decodeXmlText(text) {
269
+ return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(lt|gt|amp|quot|apos));/g, (_, dec, hex, named) => dec !== undefined
270
+ ? String.fromCodePoint(Number(dec))
271
+ : hex !== undefined
272
+ ? String.fromCodePoint(parseInt(hex, 16))
273
+ : ownEntry(NAMED_ENTITY, named));
274
+ }
275
+ /**
276
+ * ONE CDATA section holding `text`'s value.
277
+ *
278
+ * A `]]>` cannot sit in a CDATA section, and splitting it across two is no answer: the
279
+ * engine reads the FIRST `XCData` child only (`OfType<XCData>().FirstOrDefault()`), so it
280
+ * would get the JSON cut off at the split. In valid JSON a `]]>` can only sit inside a
281
+ * string (`>` is not JSON outside one), so its `>` is written as the escape `\u003e` —
282
+ * different bytes, the same value. A body that is not JSON has no such escape and could
283
+ * only reach the engine truncated, so it is refused.
284
+ */
285
+ function cdata(name, text) {
286
+ if (!text.includes(']]>'))
287
+ return `<![CDATA[${text}]]>`;
288
+ let value;
289
+ try {
290
+ value = JSON.parse(text);
291
+ }
292
+ catch {
293
+ throw new Error(`json input "${name}" contains "]]>" and is not valid JSON, so it cannot be written as the single CDATA section the engine reads.`);
294
+ }
295
+ const escaped = text.replaceAll(']]>', ']]\\u003e');
296
+ // Holds by the argument above; checked because a wrong payload here would be silent.
297
+ if (JSON.stringify(JSON.parse(escaped)) !== JSON.stringify(value)) {
298
+ throw new Error(`json input "${name}": escaping "]]>" changed its value.`);
299
+ }
300
+ return `<![CDATA[${escaped}]]>`;
301
+ }
209
302
  export function processId(id) {
210
303
  return /^[A-Za-z_][\w.-]*$/.test(id) ? id : 'Process_1';
211
304
  }
@@ -412,22 +412,47 @@ export type WaitConnectorSpec = WaitConnectorPlaceholderSpec | EventSubscription
412
412
  export type ConnectorSpecData = Extract<ActionSpec, {
413
413
  kind: 'connector';
414
414
  }>;
415
- /** Whether an external Integration Service task blocks for its result or waits for a callback. */
416
- export type ExternalExecutionMode = 'sync' | 'async';
415
+ /**
416
+ * The external agent's or automation's `executionType`, as Integration Service
417
+ * reports it for the connector object.
418
+ *
419
+ * @remarks
420
+ * This is a property of the connector object, not a choice about the case: copy
421
+ * it from the activity's `case spec` result
422
+ * (`Data.EssentialConfiguration.essentialConfiguration.executionType`). The Case
423
+ * Designer saves `sync` as the `Sync…Execution` service type and both `async`
424
+ * and `hybrid` as `Async…Execution`, and writes the value itself into the task's
425
+ * `executionType` context.
426
+ *
427
+ * `hybrid` is refused for now (EXTERNAL_TASK_HYBRID_UNSUPPORTED): the Designer
428
+ * also writes an event pair for it (`operation`, `eventMode`) that the connector
429
+ * library does not carry (#1387). Add a task on a hybrid object in Studio Web.
430
+ *
431
+ * It does not decide whether the stage waits for the task. A task the stage must
432
+ * not wait on is `.required(false)`, whatever its execution type. For example, a
433
+ * Snowflake Cortex Interact Agent is `sync` even when the stage moves on without
434
+ * it.
435
+ */
436
+ export type ExternalExecutionMode = 'sync' | 'async' | 'hybrid';
417
437
  /** Required wiring and typed inputs for an external agent/workflow invocation. */
418
438
  export interface ExternalTaskOptions<I extends Record<string, unknown>> {
419
439
  /** Symbolic Integration Service connection name declared in `bindings.json`. */
420
440
  connection: string;
421
441
  /** Symbolic Orchestrator folder binding name declared in `bindings.json`. */
422
442
  folder: string;
423
- /** Closed runtime mode; each family lowers this to its exact supported service type. */
443
+ /**
444
+ * The connector object's `executionType` from its `case spec` result. Required:
445
+ * the connector library does not carry it yet (#1369), and a wrong value passes
446
+ * `validate` but runs the wrong contract. Not whether the stage waits: that is
447
+ * `.required(false)`. See {@link ExternalExecutionMode}.
448
+ */
424
449
  mode: ExternalExecutionMode;
425
450
  /** Inputs statically checked by the generated connector descriptor. */
426
451
  inputs: I;
427
452
  }
428
453
  /** Normalized descriptor and execution mode stored on a built external task. */
429
454
  export interface ExternalTaskSpecData {
430
- /** Requested blocking or callback execution mode. */
455
+ /** The connector object's `executionType`. */
431
456
  mode: ExternalExecutionMode;
432
457
  /** Shared connector action identity, inputs, and symbolic bindings. */
433
458
  connector: ConnectorSpecData;
@@ -934,7 +959,7 @@ declare class TaskBuilder {
934
959
  * Invoke an external agent through its generated Integration Service descriptor.
935
960
  *
936
961
  * @param descriptor - An `AgentExecution` operation from a generated connector module.
937
- * @param opts - Required connection/folder bindings, sync/async mode, and descriptor-typed inputs.
962
+ * @param opts - Required connection/folder bindings, the object's execution type, and descriptor-typed inputs.
938
963
  * @returns This builder, so calls chain.
939
964
  */
940
965
  externalAgent<I extends Record<string, unknown>, O>(descriptor: ConnectorDescriptor<I, O>, opts: ExternalTaskOptions<I>): this;
@@ -942,7 +967,7 @@ declare class TaskBuilder {
942
967
  * Invoke an external workflow through its generated Integration Service descriptor.
943
968
  *
944
969
  * @param descriptor - A `ProcessExecution` operation from a generated connector module.
945
- * @param opts - Required connection/folder bindings, sync/async mode, and descriptor-typed inputs.
970
+ * @param opts - Required connection/folder bindings, the object's execution type, and descriptor-typed inputs.
946
971
  * @returns This builder, so calls chain.
947
972
  */
948
973
  externalWorkflow<I extends Record<string, unknown>, O>(descriptor: ConnectorDescriptor<I, O>, opts: ExternalTaskOptions<I>): this;
@@ -328,7 +328,7 @@ class TaskBuilder {
328
328
  * Invoke an external agent through its generated Integration Service descriptor.
329
329
  *
330
330
  * @param descriptor - An `AgentExecution` operation from a generated connector module.
331
- * @param opts - Required connection/folder bindings, sync/async mode, and descriptor-typed inputs.
331
+ * @param opts - Required connection/folder bindings, the object's execution type, and descriptor-typed inputs.
332
332
  * @returns This builder, so calls chain.
333
333
  */
334
334
  externalAgent(descriptor, opts) {
@@ -338,7 +338,7 @@ class TaskBuilder {
338
338
  * Invoke an external workflow through its generated Integration Service descriptor.
339
339
  *
340
340
  * @param descriptor - A `ProcessExecution` operation from a generated connector module.
341
- * @param opts - Required connection/folder bindings, sync/async mode, and descriptor-typed inputs.
341
+ * @param opts - Required connection/folder bindings, the object's execution type, and descriptor-typed inputs.
342
342
  * @returns This builder, so calls chain.
343
343
  */
344
344
  externalWorkflow(descriptor, opts) {
@@ -6,7 +6,7 @@ import { schemaVersionRefusal } from '../schema-version.js';
6
6
  import { caseJsonPreservation, caseJsonPreservationErrors } from './preserve.js';
7
7
  import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
8
8
  import { checkEventResolution } from '../core/event-checks.js';
9
- import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT } from './external.js';
9
+ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_HYBRID_UNSUPPORTED, externalHybridRefusal, isExternalExecutionMode } from './external.js';
10
10
  import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
11
11
  const TYPE_NAMES = new Set(Object.values(types));
12
12
  // audit-case-coverage: rule-slot table start
@@ -571,11 +571,18 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
571
571
  where: t.name,
572
572
  });
573
573
  else {
574
- if (t.external.mode !== 'sync' && t.external.mode !== 'async')
574
+ if (!isExternalExecutionMode(t.external.mode))
575
575
  diags.push({
576
576
  level: 'error',
577
577
  code: 'EXTERNAL_TASK_MODE',
578
- message: `${family} task "${t.name}" mode must be "sync" or "async"; no other service type is valid.`,
578
+ message: `${family} task "${t.name}" mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
579
+ where: t.name,
580
+ });
581
+ else if (t.external.mode === 'hybrid')
582
+ diags.push({
583
+ level: 'error',
584
+ code: EXTERNAL_TASK_HYBRID_UNSUPPORTED,
585
+ message: externalHybridRefusal(`${family} task "${t.name}"`),
579
586
  where: t.name,
580
587
  });
581
588
  if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
@@ -1,6 +1,7 @@
1
1
  import { commentText } from '../core/comment-text.js';
2
2
  import { tsLiteral, tsNumber } from '../core/ts-literal.js';
3
3
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
4
+ import { EXTERNAL_TASK_CONTEXT, externalDescriptorRefusal, externalHybridRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
4
5
  import { preserveSchemaVersion } from '../schema-version.js';
5
6
  import { ownEntry } from '../core/own-entry.js';
6
7
  import { isUnrestorableSlotId } from './preserve.js';
@@ -523,6 +524,42 @@ function connectorVersion(context) {
523
524
  function toCamel(value) {
524
525
  return value.replace(/_([a-z0-9])/gi, (_match, char) => String(char).toUpperCase());
525
526
  }
527
+ /**
528
+ * A connector activity's authored inputs, lifted from its `pathParameters` /
529
+ * `queryParameters` / `body` input containers. A `body` key is the authored
530
+ * name; a path or query key is the camelCased wire name, mapped back to the
531
+ * operation's parameter name. Shared by the plain connector lift and the
532
+ * external task lift, so the two cannot drift.
533
+ *
534
+ * With `owner`, the lift is strict (the external task shape): a row that is not
535
+ * one of the three containers, or a container whose body is not an object, is
536
+ * refused. Without it, such rows are skipped, as the connector lift always did.
537
+ */
538
+ function connectorInputs(slots, resolved, owner) {
539
+ const parameterByWireName = new Map();
540
+ for (const parameter of resolved.entry.operation?.parameters ?? [])
541
+ parameterByWireName.set(toCamel(String(parameter.name)), String(parameter.name));
542
+ const inputs = {};
543
+ for (const slot of slots) {
544
+ const name = String(slot?.name ?? '');
545
+ if (owner && !CONNECTOR_INPUT_CONTAINERS.has(name))
546
+ throw new Error(`${owner} has unsupported input row "${name}".`);
547
+ const body = slot?.body;
548
+ if (owner && body === undefined)
549
+ continue;
550
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
551
+ if (owner)
552
+ throw new Error(`${owner} input container "${name}" must carry an object body.`);
553
+ continue;
554
+ }
555
+ for (const [wireName, value] of Object.entries(body)) {
556
+ const authoredName = name === 'body' ? wireName : (parameterByWireName.get(wireName) ?? wireName);
557
+ inputs[authoredName] = value;
558
+ }
559
+ }
560
+ return inputs;
561
+ }
562
+ const CONNECTOR_INPUT_CONTAINERS = new Set(['pathParameters', 'queryParameters', 'body']);
526
563
  function connectorCall(task, refs, library) {
527
564
  if (!library)
528
565
  throw new Error(`connector task "${task.displayName ?? task.id ?? '?'}" needs the connector library; pass DecompileOptions.library or --library.`);
@@ -542,19 +579,7 @@ function connectorCall(task, refs, library) {
542
579
  version: connectorVersion(context),
543
580
  });
544
581
  const resolved = library.resolve(identity.key, identity.action, identity.version, identity.object);
545
- const parameterByWireName = new Map();
546
- for (const parameter of resolved.entry.operation?.parameters ?? [])
547
- parameterByWireName.set(toCamel(String(parameter.name)), String(parameter.name));
548
- const inputs = {};
549
- for (const slot of Array.isArray(data.inputs) ? data.inputs : []) {
550
- const body = slot?.body;
551
- if (!body || typeof body !== 'object' || Array.isArray(body))
552
- continue;
553
- for (const [wireName, value] of Object.entries(body)) {
554
- const authoredName = slot.name === 'body' ? wireName : (parameterByWireName.get(wireName) ?? wireName);
555
- inputs[authoredName] = value;
556
- }
557
- }
582
+ const inputs = connectorInputs(Array.isArray(data.inputs) ? data.inputs : [], resolved);
558
583
  const connection = bindingName(context.get('connection')?.value, refs);
559
584
  const folder = bindingName(context.get('folderKey')?.value, refs);
560
585
  const options = opts([
@@ -565,6 +590,14 @@ function connectorCall(task, refs, library) {
565
590
  ]);
566
591
  return `.connector(${tsLiteral(identity.key)}, ${tsLiteral(identity.action)}, ${tsLiteral(inputs)}${options ? `, ${options}` : ''})`;
567
592
  }
593
+ /**
594
+ * Lift an External Agent / External Workflow task saved in the Case Designer's
595
+ * shape (the one `serialize` emits): a connector activity's context and input
596
+ * containers plus the object's `executionType`, which becomes `mode`. The
597
+ * service type must be the one that execution type gives, so a plan whose two
598
+ * disagree is refused rather than lifted to a source that would compile to
599
+ * something else.
600
+ */
568
601
  function externalCall(task, refs, library) {
569
602
  const kind = task.type;
570
603
  const owner = `${kind} task "${task.displayName ?? task.id ?? '?'}"`;
@@ -575,33 +608,24 @@ function externalCall(task, refs, library) {
575
608
  const unknownData = Object.keys(data).find((name) => !allowed.has(name));
576
609
  if (unknownData)
577
610
  throw new Error(`${owner} has unsupported data field "${unknownData}".`);
578
- if (!Array.isArray(data.bindings) || data.bindings.length !== 0)
611
+ if (data.bindings !== undefined && (!Array.isArray(data.bindings) || data.bindings.length !== 0))
579
612
  throw new Error(`${owner} data.bindings must be an empty array; bindings live at case root.`);
580
- const modes = kind === 'external-agent'
581
- ? new Map([
582
- [CASE_FORMAT_PROFILE.externalAgentSyncServiceType, 'sync'],
583
- [CASE_FORMAT_PROFILE.externalAgentAsyncServiceType, 'async'],
584
- ])
585
- : new Map([
586
- [CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType, 'sync'],
587
- [CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType, 'async'],
588
- ]);
589
- const mode = modes.get(data.serviceType);
590
- if (!mode)
591
- throw new Error(`${owner} has unsupported serviceType "${String(data.serviceType ?? '')}".`);
592
613
  if (!Array.isArray(data.context))
593
614
  throw new Error(`${owner} requires a resolved context array.`);
594
- // A trailing `activityConfigurationVersion: v2` row only picks the Designer
595
- // panel; compile writes it back from the plan's `intsvcActivityConfig` flag.
596
- const rows = data.context;
597
- const last = rows[rows.length - 1];
598
- const configRows = last?.name === 'activityConfigurationVersion' && last.value === 'v2' && last.type === 'string'
599
- ? rows.slice(0, -1)
600
- : rows;
601
- const expectedContext = ['activity', 'connectorKey', 'connection', 'folderKey', 'method', 'path'];
602
- if (configRows.map((row) => row.name).join('\0') !== expectedContext.join('\0'))
603
- throw new Error(`${owner} context must contain exactly ${expectedContext.join(', ')} in canonical order, optionally followed by activityConfigurationVersion v2.`);
604
- const context = contextMap({ context: configRows });
615
+ // A hybrid task carries an event pair the SDK cannot write back (#1387): refuse
616
+ // it by name, before its extra context rows are reported as unknown.
617
+ if (data.context.some((row) => row?.name === 'executionType' && row.value === 'hybrid'))
618
+ throw new Error(externalHybridRefusal(owner));
619
+ // Exactly the context rows serialize writes (the Designer's set): an unknown
620
+ // or repeated row would be dropped by the lift and lost on recompile.
621
+ const contextNames = data.context.map((row) => String(row?.name ?? ''));
622
+ const unknownContext = contextNames.find((name, at) => !EXTERNAL_TASK_CONTEXT.includes(name) || contextNames.indexOf(name) !== at);
623
+ if (unknownContext !== undefined)
624
+ throw new Error(`${owner} has unsupported or repeated context row "${unknownContext}".`);
625
+ const absentContext = EXTERNAL_TASK_CONTEXT.find((name) => !contextNames.includes(name));
626
+ if (absentContext)
627
+ throw new Error(`${owner} is missing context row "${absentContext}".`);
628
+ const context = contextMap(data);
605
629
  const stringValue = (name) => {
606
630
  const row = context.get(name);
607
631
  if (row?.type !== 'string' || typeof row.value !== 'string' || !row.value)
@@ -617,36 +641,26 @@ function externalCall(task, refs, library) {
617
641
  throw new Error(`${owner} context ${name} references missing root binding "${id}".`);
618
642
  return bindingLabel(refs, id);
619
643
  };
644
+ const mode = stringValue('executionType');
645
+ if (!isExternalExecutionMode(mode))
646
+ throw new Error(`${owner} has unsupported executionType "${mode}".`);
647
+ const serviceType = externalServiceType(kind, mode);
648
+ if (data.serviceType !== serviceType)
649
+ throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
620
650
  const identity = library.identifyAction({
621
651
  connectorKey: stringValue('connectorKey'),
622
652
  httpMethod: stringValue('method'),
623
653
  endpoint: stringValue('path'),
654
+ objectName: stringValue('objectName'),
655
+ version: connectorVersion(context),
624
656
  });
625
657
  const resolved = library.resolve(identity.key, identity.action, identity.version, identity.object);
626
- const expectedSubType = kind === 'external-agent' ? 'AgentExecution' : 'ProcessExecution';
627
- if (resolved.entry.operation?.subType !== expectedSubType)
628
- throw new Error(`${owner} resolves to ${String(resolved.entry.operation?.subType ?? 'no subtype')}, not ${expectedSubType}.`);
629
- if (stringValue('activity') !== resolved.activityTypeId)
630
- throw new Error(`${owner} activity does not match its descriptor.`);
658
+ const refused = externalDescriptorRefusal(kind, resolved);
659
+ if (refused)
660
+ throw new Error(`${owner}: ${refused}`);
631
661
  if (!Array.isArray(data.inputs))
632
662
  throw new Error(`${owner} inputs must be an array.`);
633
- const fields = new Map(resolved.fields.map((field) => [field.name, field]));
634
- const inputs = {};
635
- for (const row of data.inputs) {
636
- const name = String(row?.name ?? '');
637
- if (!name || row.id !== name || row.target !== name || !fields.has(name))
638
- throw new Error(`${owner} has unsupported input row "${name}".`);
639
- inputs[name] = row.value;
640
- }
641
- const missing = resolved.fields.find((field) => field.required && !(field.name in inputs));
642
- if (missing)
643
- throw new Error(`${owner} is missing required input "${missing.name}".`);
644
- if (!Array.isArray(data.outputs) || data.outputs.length !== 1)
645
- throw new Error(`${owner} must carry exactly one response output.`);
646
- const output = data.outputs[0];
647
- if (output.name !== 'response' || output.type !== 'json' || output.source !== '.'
648
- || typeof output.var !== 'string' || output.id !== output.var)
649
- throw new Error(`${owner} has an unsupported response output.`);
663
+ const inputs = connectorInputs(data.inputs, resolved, owner);
650
664
  const descriptor = `{ nodeType: ${tsLiteral(resolved.nodeType)}, version: ${tsLiteral(resolved.version)} }`;
651
665
  const options = opts([
652
666
  ['connection', tsLiteral(binding('connection'))],
@@ -8,6 +8,45 @@
8
8
  * `buildConnectorInputs`, which `check` already runs as `connectorInputs`.
9
9
  */
10
10
  import type { ResolvedConnector } from '../core/library.js';
11
+ import type { ExternalExecutionMode } from './case-sdk.js';
12
+ /**
13
+ * The context rows `serialize` writes on an external task: the set the Case
14
+ * Designer saves for a sync object (`uip`'s
15
+ * `tests/fixtures/external-tasks/designer-saved.json`, the Snowflake Cortex
16
+ * agent). `decompile` refuses a task with any other row, since the lift would
17
+ * drop it without a word. A `hybrid` task (which also carries `operation` and
18
+ * `eventMode`) is refused before this check; see {@link externalHybridRefusal}.
19
+ */
20
+ export declare const EXTERNAL_TASK_CONTEXT: readonly string[];
21
+ /** Check's code for {@link externalHybridRefusal}. */
22
+ export declare const EXTERNAL_TASK_HYBRID_UNSUPPORTED = "EXTERNAL_TASK_HYBRID_UNSUPPORTED";
23
+ /**
24
+ * Why an external task on a `hybrid` object cannot be compiled or decompiled
25
+ * yet. The Case Designer saves such a task with an event pair (context
26
+ * `operation`, e.g. `TRIGGER_CREATED`, and `eventMode`, e.g. `polling`, also in
27
+ * the metadata's `activityPropertyConfiguration`) that it reads from live
28
+ * Integration Service metadata. The connector library does not carry that pair
29
+ * (#1387), so the SDK cannot write the task faithfully, and a guessed pair could
30
+ * deploy a task that never completes. `check`, `serialize` and `decompile` all
31
+ * refuse with this text.
32
+ *
33
+ * @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
34
+ * @returns The refusal.
35
+ */
36
+ export declare function externalHybridRefusal(owner: string): string;
37
+ /** The execution types an external task accepts: the values Integration Service reports. */
38
+ export declare function isExternalExecutionMode(value: unknown): value is ExternalExecutionMode;
39
+ /**
40
+ * The `data.serviceType` the Case Designer saves for an external task of this
41
+ * execution type. Studio Web runs `hybrid` as `async` (`determineActualIntsvcType`),
42
+ * so both save as the `Async…Execution` type; the CLI's `externalServiceType`
43
+ * and its strict validator read the same table.
44
+ *
45
+ * @param kind - The task kind.
46
+ * @param mode - The connector object's `executionType`.
47
+ * @returns The service type.
48
+ */
49
+ export declare function externalServiceType(kind: 'external-agent' | 'external-workflow', mode: ExternalExecutionMode): string;
11
50
  /** Check's code for {@link externalDescriptorRefusal}. */
12
51
  export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPTOR_CONTRACT";
13
52
  /**
@@ -17,8 +56,8 @@ export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPT
17
56
  * Workflow task a `ProcessExecution` one: the service type the task emits
18
57
  * (`Intsvc.SyncAgentExecution`, …) is chosen by the family, and a descriptor of
19
58
  * the other kind (or a plain connector action) would run the wrong contract.
20
- * Both need the operation's `uiPathActivityTypeId`, which the task emits as its
21
- * `activity` context.
59
+ * Both need the operation's `uiPathActivityTypeId`, which the task's `metadata`
60
+ * context carries.
22
61
  *
23
62
  * @param kind - The task kind.
24
63
  * @param rc - What `library.resolve` returned for the task's descriptor.
@@ -1,3 +1,57 @@
1
+ import { CASE_FORMAT_PROFILE } from './format-profile.js';
2
+ /**
3
+ * The context rows `serialize` writes on an external task: the set the Case
4
+ * Designer saves for a sync object (`uip`'s
5
+ * `tests/fixtures/external-tasks/designer-saved.json`, the Snowflake Cortex
6
+ * agent). `decompile` refuses a task with any other row, since the lift would
7
+ * drop it without a word. A `hybrid` task (which also carries `operation` and
8
+ * `eventMode`) is refused before this check; see {@link externalHybridRefusal}.
9
+ */
10
+ export const EXTERNAL_TASK_CONTEXT = [
11
+ 'connectorKey', 'connection', 'resourceKey', 'folderKey', 'objectName', 'method', 'path',
12
+ 'executionType', 'metadata', 'activityConfigurationVersion',
13
+ ];
14
+ /** Check's code for {@link externalHybridRefusal}. */
15
+ export const EXTERNAL_TASK_HYBRID_UNSUPPORTED = 'EXTERNAL_TASK_HYBRID_UNSUPPORTED';
16
+ /**
17
+ * Why an external task on a `hybrid` object cannot be compiled or decompiled
18
+ * yet. The Case Designer saves such a task with an event pair (context
19
+ * `operation`, e.g. `TRIGGER_CREATED`, and `eventMode`, e.g. `polling`, also in
20
+ * the metadata's `activityPropertyConfiguration`) that it reads from live
21
+ * Integration Service metadata. The connector library does not carry that pair
22
+ * (#1387), so the SDK cannot write the task faithfully, and a guessed pair could
23
+ * deploy a task that never completes. `check`, `serialize` and `decompile` all
24
+ * refuse with this text.
25
+ *
26
+ * @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
27
+ * @returns The refusal.
28
+ */
29
+ export function externalHybridRefusal(owner) {
30
+ return (`${owner} is on a "hybrid" object, which Case cannot compile yet: the connector library does not carry ` +
31
+ `the event pair the Case Designer writes for it (operation, e.g. "TRIGGER_CREATED", and eventMode, e.g. "polling"), ` +
32
+ `so the task cannot be written faithfully. Add this task in Studio Web for now; ` +
33
+ `https://github.com/UiPath/builder-sdk/issues/1387 tracks the library change.`);
34
+ }
35
+ /** The execution types an external task accepts: the values Integration Service reports. */
36
+ export function isExternalExecutionMode(value) {
37
+ return value === 'sync' || value === 'async' || value === 'hybrid';
38
+ }
39
+ /**
40
+ * The `data.serviceType` the Case Designer saves for an external task of this
41
+ * execution type. Studio Web runs `hybrid` as `async` (`determineActualIntsvcType`),
42
+ * so both save as the `Async…Execution` type; the CLI's `externalServiceType`
43
+ * and its strict validator read the same table.
44
+ *
45
+ * @param kind - The task kind.
46
+ * @param mode - The connector object's `executionType`.
47
+ * @returns The service type.
48
+ */
49
+ export function externalServiceType(kind, mode) {
50
+ const sync = mode === 'sync';
51
+ if (kind === 'external-agent')
52
+ return sync ? CASE_FORMAT_PROFILE.externalAgentSyncServiceType : CASE_FORMAT_PROFILE.externalAgentAsyncServiceType;
53
+ return sync ? CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType : CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType;
54
+ }
1
55
  /** Check's code for {@link externalDescriptorRefusal}. */
2
56
  export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTRACT';
3
57
  /**
@@ -7,8 +61,8 @@ export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTR
7
61
  * Workflow task a `ProcessExecution` one: the service type the task emits
8
62
  * (`Intsvc.SyncAgentExecution`, …) is chosen by the family, and a descriptor of
9
63
  * the other kind (or a plain connector action) would run the wrong contract.
10
- * Both need the operation's `uiPathActivityTypeId`, which the task emits as its
11
- * `activity` context.
64
+ * Both need the operation's `uiPathActivityTypeId`, which the task's `metadata`
65
+ * context carries.
12
66
  *
13
67
  * @param kind - The task kind.
14
68
  * @param rc - What `library.resolve` returned for the task's descriptor.