@uipath/maestro-builder-sdk 6.68.6 → 6.68.7

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.
@@ -191,6 +191,16 @@ export function createExternalTask(ctx, n) {
191
191
  `${n.key}/${n.action} is "${String(op.subType ?? 'untyped')}". ` +
192
192
  `The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
193
193
  }
194
+ // The object's execution type, when the library records it (#1369): a `mode`
195
+ // that contradicts it compiles the other contract, which validate accepts and
196
+ // the run gets wrong. Studio Web runs `hybrid` as `async`.
197
+ const recorded = op.executionType;
198
+ if (recorded === 'sync' || recorded === 'async' || recorded === 'hybrid') {
199
+ const runs = recorded === 'sync' ? 'sync' : 'async';
200
+ if (n.mode !== runs)
201
+ throw new CompileRefusal('EXTERNAL_TASK_MODE_MISMATCH', `external ${n.family} "${n.id}" has mode "${n.mode}", but the connector library records ${n.key}/${n.action}'s ` +
202
+ `executionType as "${recorded}", which runs ${runs}; the node would run the wrong contract. Use mode: "${runs}".`);
203
+ }
194
204
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
195
205
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
196
206
  if (!conn || !folder) {
@@ -430,9 +430,12 @@ export type ConnectorSpecData = Extract<ActionSpec, {
430
430
  * reports it for the connector object.
431
431
  *
432
432
  * @remarks
433
- * This is a property of the connector object, not a choice about the case: copy
434
- * it from the activity's `case spec` result
435
- * (`Data.EssentialConfiguration.essentialConfiguration.executionType`). The Case
433
+ * This is a property of the connector object, not a choice about the case. The
434
+ * connector library records it for the object when it was built or prepared
435
+ * from Integration Service metadata that reports it, and compile then takes it
436
+ * from there: leave `mode` out. Otherwise copy it from the activity's `case spec`
437
+ * result (`Data.EssentialConfiguration.essentialConfiguration.executionType`).
438
+ * A `mode` the library contradicts is refused (EXTERNAL_TASK_MODE_MISMATCH). The Case
436
439
  * Designer saves `sync` as the `Sync…Execution` service type and both `async`
437
440
  * and `hybrid` as `Async…Execution`, and writes the value itself into the task's
438
441
  * `executionType` context.
@@ -457,10 +460,10 @@ export type ExternalExecutionMode = 'sync' | 'async' | 'hybrid';
457
460
  * the Case Designer reads it from the object's metadata (its first event mode
458
461
  * and that mode's first event type) and saves it as the task's `operation` and
459
462
  * `eventMode` context rows, and as `eventType` / `eventMode` in the metadata's
460
- * `activityPropertyConfiguration`. Copy it from the same two rows of the
461
- * activity's `case spec` result (`caseShape.context`). `decompile` lifts it from
462
- * a saved task. For
463
- * example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
463
+ * `activityPropertyConfiguration`. Like `mode`, compile takes it from the
464
+ * connector library when the library records it; otherwise copy it from the same
465
+ * two rows of the activity's `case spec` result (`caseShape.context`).
466
+ * `decompile` lifts it from a saved task. For example, the Power Automate "Invoke a Flow" object (`hybrid`) answers with
464
467
  * `{ operation: 'TRIGGER_CREATED', mode: 'polling' }`.
465
468
  */
466
469
  export interface ExternalEventPair {
@@ -476,16 +479,18 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
476
479
  /** Symbolic Orchestrator folder binding name declared in `bindings.json`. */
477
480
  folder: string;
478
481
  /**
479
- * The connector object's `executionType` from its `case spec` result. Required:
480
- * the connector library does not carry it yet (#1369), and a wrong value passes
481
- * `validate` but runs the wrong contract. Not whether the stage waits: that is
482
+ * The connector object's `executionType`. Leave it out when the connector
483
+ * library records it (compile reads it from there); otherwise copy it from the
484
+ * `case spec` result. Compile refuses a task with no mode from either
485
+ * (EXTERNAL_TASK_MODE), and one the library contradicts
486
+ * (EXTERNAL_TASK_MODE_MISMATCH). Not whether the stage waits: that is
482
487
  * `.required(false)`. See {@link ExternalExecutionMode}.
483
488
  */
484
- mode: ExternalExecutionMode;
489
+ mode?: ExternalExecutionMode;
485
490
  /**
486
- * The event an `async` or `hybrid` object answers with. Required for `hybrid`;
487
- * optional for `async`; refused for `sync`, which answers inline. See
488
- * {@link ExternalEventPair}.
491
+ * The event an `async` or `hybrid` object answers with. Leave it out when the
492
+ * connector library records it. A `hybrid` task needs one from either place;
493
+ * a `sync` one refuses it, since it answers inline. See {@link ExternalEventPair}.
489
494
  */
490
495
  event?: ExternalEventPair;
491
496
  /** Inputs statically checked by the generated connector descriptor. */
@@ -493,9 +498,9 @@ export interface ExternalTaskOptions<I extends Record<string, unknown>> {
493
498
  }
494
499
  /** Normalized descriptor and execution mode stored on a built external task. */
495
500
  export interface ExternalTaskSpecData {
496
- /** The connector object's `executionType`. */
497
- mode: ExternalExecutionMode;
498
- /** The event an `async` or `hybrid` object answers with, when given. */
501
+ /** The connector object's `executionType`, when authored (else the library's). */
502
+ mode?: ExternalExecutionMode;
503
+ /** The event an `async` or `hybrid` object answers with, when authored (else the library's). */
499
504
  event?: ExternalEventPair;
500
505
  /** Shared connector action identity, inputs, and symbolic bindings. */
501
506
  connector: ConnectorSpecData;
@@ -333,7 +333,7 @@ class TaskBuilder {
333
333
  external(kind, descriptor, opts) {
334
334
  this.setKind(kind);
335
335
  this._external = {
336
- mode: opts.mode,
336
+ ...(opts.mode !== undefined ? { mode: opts.mode } : {}),
337
337
  ...(opts.event !== undefined ? { event: { ...opts.event } } : {}),
338
338
  connector: makeConnector(descriptor, opts.inputs, {
339
339
  connection: opts.connection,
@@ -52,6 +52,25 @@ export type CaseCheckOpts = Pick<ConnectorCheckOpts, 'connectorResolveError' | '
52
52
  version?: string;
53
53
  object?: string;
54
54
  }) => string | undefined;
55
+ /**
56
+ * What Case `compile` would refuse an External Agent / External Workflow
57
+ * task's mode and event with, judged against what the library records for
58
+ * the object (`externalExecution`): EXTERNAL_TASK_MODE (no mode from the
59
+ * author or the library), EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT.
60
+ * `undefined` when they are fine, or the descriptor does not resolve.
61
+ */
62
+ externalExecutionError?: (owner: string, descriptor: {
63
+ key: string;
64
+ action: string;
65
+ version?: string;
66
+ object?: string;
67
+ }, authored: {
68
+ mode?: unknown;
69
+ event?: unknown;
70
+ }) => {
71
+ code: string;
72
+ message: string;
73
+ } | undefined;
55
74
  subscriptionError?: (kind: 'trigger' | 'event', subscription: EventSubscription) => {
56
75
  code: string;
57
76
  message: string;
@@ -7,7 +7,7 @@ import { schemaVersionRefusal } from '../schema-version.js';
7
7
  import { caseJsonPreservation, caseJsonPreservationErrors, preservedBindings } from './preserve.js';
8
8
  import { EQUALS_EXPR_DIALECT, checkBindingLabels, checkConnectorContract, checkConnectorLookups, checkConnectorSchema, } from '../core/connector-checks.js';
9
9
  import { checkEventResolution } from '../core/event-checks.js';
10
- import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_EVENT, externalEventRefusal, isExternalExecutionMode } from './external.js';
10
+ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, externalExecution } from './external.js';
11
11
  import { isResourceLabelRefusal, resolveResourceRef } from './resource-identity.js';
12
12
  import { taskShapeProblems } from './task-shape.js';
13
13
  import { isConnectorPlaceholder } from './connector-placeholder.js';
@@ -303,7 +303,7 @@ export function check(built, opts = {}) {
303
303
  // positive that blocks emit (this gate runs before serialization), so it must be
304
304
  // re-derived from the serializer rather than from remembered probe results.
305
305
  for (const stage of built.stages) {
306
- checkStage(stage, diags, refStage, refPlacement, opts.bindings);
306
+ checkStage(stage, diags, refStage, refPlacement, opts.bindings, opts.externalExecutionError !== undefined);
307
307
  stage.slaRules.forEach((s, i) => checkSla(s, `stage "${stage.label}" SLA #${i + 1}`, diags));
308
308
  }
309
309
  built.slaRules.forEach((s, i) => checkSla(s, `case SLA #${i + 1}`, diags));
@@ -325,7 +325,9 @@ export function check(built, opts = {}) {
325
325
  * CONNECTOR_OBJECT_REQUIRED, CONNECTOR_OBJECT_NOT_GENERIC; then their inputs
326
326
  * (`buildConnectorInputs`): CONNECTOR_INPUT, LOOKUP_UNRESOLVED;
327
327
  * - an external task's resolved descriptor (`externalDescriptorRefusal`):
328
- * EXTERNAL_TASK_DESCRIPTOR_CONTRACT;
328
+ * EXTERNAL_TASK_DESCRIPTOR_CONTRACT; and its mode and event against what the
329
+ * library records for the object (`externalExecution`): EXTERNAL_TASK_MODE,
330
+ * EXTERNAL_TASK_MODE_MISMATCH, EXTERNAL_TASK_EVENT;
329
331
  * - an event trigger, a wait-for-connector task and a wait-for-connector rule
330
332
  * resolve their subscription (`resolveCaseSubscription`): EVENT_UNKNOWN,
331
333
  * EVENT_GENERIC_NO_OBJECT.
@@ -391,6 +393,9 @@ function checkLibraryConnectors(built, opts, diags) {
391
393
  const refused = opts.externalDescriptorError?.(t.kind, spec);
392
394
  if (refused !== undefined)
393
395
  diags.push({ level: 'error', code: EXTERNAL_TASK_DESCRIPTOR_CONTRACT, message: `${where}: ${refused}`, where: t.name });
396
+ const execution = opts.externalExecutionError?.(where, spec, t.external);
397
+ if (execution !== undefined)
398
+ diags.push({ level: 'error', code: execution.code, message: execution.message, where: t.name });
394
399
  }
395
400
  if (t.kind === 'wait-for-connector' && isSubscription(t.waitConnector))
396
401
  subscription('event', t.waitConnector, `Wait-for-connector task "${t.name}"`, t.name);
@@ -441,7 +446,9 @@ function isNilOrEmpty(value) {
441
446
  return Object.keys(value).length === 0;
442
447
  return false;
443
448
  }
444
- function checkStage(stage, diags, refStage, refPlacement, bindings) {
449
+ function checkStage(stage, diags, refStage, refPlacement, bindings,
450
+ /** A library judges external tasks' mode and event (`externalExecutionError`). */
451
+ externalJudge) {
445
452
  const where = `stage "${stage.label}"`;
446
453
  if (stage.exception && stage.required !== false)
447
454
  diags.push({
@@ -622,17 +629,15 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
622
629
  where: t.name,
623
630
  });
624
631
  else {
625
- if (!isExternalExecutionMode(t.external.mode))
626
- diags.push({
627
- level: 'error',
628
- code: 'EXTERNAL_TASK_MODE',
629
- message: `${family} task "${t.name}" mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
630
- where: t.name,
631
- });
632
- else {
633
- const refused = externalEventRefusal(`${family} task "${t.name}"`, t.external.mode, t.external.event);
634
- if (refused)
635
- diags.push({ level: 'error', code: EXTERNAL_TASK_EVENT, message: refused, where: t.name });
632
+ // The authored mode and event alone. With a library at hand, the
633
+ // library pass judges them against the object instead (and reports a
634
+ // missing mode), so they are judged here only without one.
635
+ const libraryJudges = externalJudge
636
+ && !!t.external.connector?.key?.trim() && !!t.external.connector?.action?.trim();
637
+ if (!libraryJudges) {
638
+ const judged = externalExecution(`${family} task "${t.name}"`, t.external, undefined);
639
+ if (judged?.code !== undefined)
640
+ diags.push({ level: 'error', code: judged.code, message: judged.message, where: t.name });
636
641
  }
637
642
  if (!t.external.connector?.key?.trim() || !t.external.connector?.action?.trim())
638
643
  diags.push({
@@ -8,7 +8,7 @@
8
8
  * both `case check` and `case compile`'s check pass call.
9
9
  */
10
10
  import { connectorCheckOpts } from '../core/cli-library.js';
11
- import { externalDescriptorRefusal } from './external.js';
11
+ import { externalDescriptorRefusal, externalExecution } from './external.js';
12
12
  import { EVENT_GENERIC_NO_OBJECT, GenericEventNoObjectError, resolveCaseSubscription } from './subscription.js';
13
13
  /**
14
14
  * Case's check options from a library the CLI already opened, or `{}` without one.
@@ -48,6 +48,17 @@ export function caseCheckOpts(library, bindings) {
48
48
  }
49
49
  return externalDescriptorRefusal(kind, rc);
50
50
  },
51
+ externalExecutionError: (owner, d, authored) => {
52
+ let rc;
53
+ try {
54
+ rc = library.resolve(d.key, d.action, d.version, d.object);
55
+ }
56
+ catch {
57
+ return undefined; // connectorResolveError's to report
58
+ }
59
+ const judged = externalExecution(owner, authored, rc.entry?.operation ?? {});
60
+ return judged?.code !== undefined ? { code: judged.code, message: judged.message } : undefined;
61
+ },
51
62
  subscriptionError: (kind, sub) => {
52
63
  try {
53
64
  resolveCaseSubscription(library, kind, sub);
@@ -2,7 +2,7 @@ import { commentText } from '../core/comment-text.js';
2
2
  import { implicitInArgTrigger } from './in-arg-binding.js';
3
3
  import { tsLiteral, tsNumber } from '../core/ts-literal.js';
4
4
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
5
- import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode } from './external.js';
5
+ import { EXTERNAL_TASK_CONTEXT, EXTERNAL_TASK_EVENT_CONTEXT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode, libraryExecution } from './external.js';
6
6
  import { preserveSchemaVersion } from '../schema-version.js';
7
7
  import { ownEntry } from '../core/own-entry.js';
8
8
  import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
@@ -676,8 +676,8 @@ function externalCall(task, refs, library) {
676
676
  const serviceType = externalServiceType(kind, mode);
677
677
  if (data.serviceType !== serviceType)
678
678
  throw new Error(`${owner} has serviceType "${String(data.serviceType ?? '')}", but its executionType "${mode}" saves as "${serviceType}".`);
679
- // The event the object answers with, as saved: the same rule compile applies,
680
- // so a lifted task always compiles back (a hybrid task without one does not).
679
+ // The event the object answers with, as saved, judged by compile's event rule
680
+ // here, and against the library's record below (`externalExecution` in compile).
681
681
  const event = eventRows.length ? { operation: stringValue('operation'), mode: stringValue('eventMode') } : undefined;
682
682
  // A saved plan has no `event` option to drop, so a sync task with event rows
683
683
  // gets its own text naming the rows; every other case is compile's rule.
@@ -699,6 +699,19 @@ function externalCall(task, refs, library) {
699
699
  const refused = externalDescriptorRefusal(kind, resolved);
700
700
  if (refused)
701
701
  throw new Error(`${owner}: ${refused}`);
702
+ // Compile refuses a mode or event the library contradicts (#1369), so a task
703
+ // saved against a different record would lift to source that does not compile
704
+ // back: refuse it here, by name, before the author edits anything.
705
+ const recorded = libraryExecution(resolved.entry?.operation);
706
+ if (recorded.mode !== undefined && recorded.mode !== mode)
707
+ throw new Error(`${owner} is saved with executionType "${mode}", but the connector library records the object's ` +
708
+ `executionType as "${recorded.mode}", so the lifted source would not compile back. Re-save the task in Studio Web, ` +
709
+ `or rebuild the library (registry prepare) if the object changed.`);
710
+ if (event !== undefined && recorded.event !== undefined
711
+ && (event.operation !== recorded.event.operation || event.mode !== recorded.event.mode))
712
+ throw new Error(`${owner} is saved with event ${JSON.stringify(event)}, but the connector library records the object's ` +
713
+ `event as ${JSON.stringify(recorded.event)}, so the lifted source would not compile back. Re-save the task in Studio Web, ` +
714
+ `or rebuild the library (registry prepare) if the object changed.`);
702
715
  if (!Array.isArray(data.inputs))
703
716
  throw new Error(`${owner} inputs must be an array.`);
704
717
  const inputs = connectorInputs(data.inputs, resolved, owner);
@@ -58,6 +58,52 @@ export declare function isExternalExecutionMode(value: unknown): value is Extern
58
58
  * @returns The service type.
59
59
  */
60
60
  export declare function externalServiceType(kind: 'external-agent' | 'external-workflow', mode: ExternalExecutionMode): string;
61
+ /** Check's and compile's code when an authored `mode` disagrees with the library. */
62
+ export declare const EXTERNAL_TASK_MODE_MISMATCH = "EXTERNAL_TASK_MODE_MISMATCH";
63
+ /** A judged execution: the task's mode and event, or why it cannot be written. */
64
+ export type ExternalExecution = {
65
+ mode: ExternalExecutionMode;
66
+ event?: ExternalEventPair;
67
+ code?: undefined;
68
+ } | {
69
+ code: string;
70
+ message: string;
71
+ };
72
+ /**
73
+ * The execution facts the connector library records for the object
74
+ * (`operation.executionType` and `operation.executionEvent`, from
75
+ * `registry build-library` / `registry prepare`, #1369). A value the SDK does
76
+ * not know is ignored, as if it were absent.
77
+ */
78
+ export declare function libraryExecution(operation: unknown): {
79
+ mode?: ExternalExecutionMode;
80
+ event?: ExternalEventPair;
81
+ };
82
+ /**
83
+ * The mode and event an external task is written with: what the author passed,
84
+ * else what the connector library records for the object. `check` and
85
+ * `serialize` both call this, so they refuse the same tasks with the same text.
86
+ *
87
+ * - `mode` comes from the author or the library. With neither, the task is
88
+ * refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
89
+ * object to the sync contract without a word (#1370 was closed for that).
90
+ * - An authored `mode` the library contradicts is refused
91
+ * (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
92
+ * - `event` comes from the author or, for an `async` / `hybrid` object, the
93
+ * library; an authored one the library contradicts is refused
94
+ * (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
95
+ *
96
+ * @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
97
+ * @param authored - The task's authored `mode` and `event`.
98
+ * @param operation - The resolved library entry's `operation`, or `undefined`
99
+ * when no library is at hand (model-only `check`, which then judges only what
100
+ * was authored and reports no missing mode).
101
+ * @returns The mode and event, or the refusal.
102
+ */
103
+ export declare function externalExecution(owner: string, authored: {
104
+ mode?: unknown;
105
+ event?: unknown;
106
+ }, operation: unknown): ExternalExecution | undefined;
61
107
  /** Check's code for {@link externalDescriptorRefusal}. */
62
108
  export declare const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = "EXTERNAL_TASK_DESCRIPTOR_CONTRACT";
63
109
  /**
@@ -45,7 +45,8 @@ export function externalEventRefusal(owner, mode, event) {
45
45
  return undefined;
46
46
  return (`${owner} is on a "hybrid" object, which answers through an event: pass the event the object reports as ` +
47
47
  `event: { operation, mode } (for example { operation: "TRIGGER_CREATED", mode: "polling" }, ` +
48
- `the "operation" and "eventMode" rows of the case spec result's caseShape.context). Without it the task has nothing to wait on.`);
48
+ `the "operation" and "eventMode" rows of the case spec result's caseShape.context), or prepare the connector so the ` +
49
+ `library records it. Without it the task has nothing to wait on.`);
49
50
  }
50
51
  if (mode === 'sync')
51
52
  return `${owner} is on a "sync" object, which answers inline and has no event; drop event, or fix mode.`;
@@ -79,6 +80,83 @@ export function externalServiceType(kind, mode) {
79
80
  return sync ? CASE_FORMAT_PROFILE.externalAgentSyncServiceType : CASE_FORMAT_PROFILE.externalAgentAsyncServiceType;
80
81
  return sync ? CASE_FORMAT_PROFILE.externalWorkflowSyncServiceType : CASE_FORMAT_PROFILE.externalWorkflowAsyncServiceType;
81
82
  }
83
+ /** Check's and compile's code when an authored `mode` disagrees with the library. */
84
+ export const EXTERNAL_TASK_MODE_MISMATCH = 'EXTERNAL_TASK_MODE_MISMATCH';
85
+ /**
86
+ * The execution facts the connector library records for the object
87
+ * (`operation.executionType` and `operation.executionEvent`, from
88
+ * `registry build-library` / `registry prepare`, #1369). A value the SDK does
89
+ * not know is ignored, as if it were absent.
90
+ */
91
+ export function libraryExecution(operation) {
92
+ const op = (operation ?? {});
93
+ const mode = isExternalExecutionMode(op.executionType) ? op.executionType : undefined;
94
+ const raw = op.executionEvent;
95
+ const event = mode !== undefined && mode !== 'sync' && typeof raw?.operation === 'string' && raw.operation
96
+ && isExternalEventMode(raw.mode)
97
+ ? { operation: raw.operation, mode: raw.mode }
98
+ : undefined;
99
+ return { ...(mode ? { mode } : {}), ...(event ? { event } : {}) };
100
+ }
101
+ /**
102
+ * The mode and event an external task is written with: what the author passed,
103
+ * else what the connector library records for the object. `check` and
104
+ * `serialize` both call this, so they refuse the same tasks with the same text.
105
+ *
106
+ * - `mode` comes from the author or the library. With neither, the task is
107
+ * refused (EXTERNAL_TASK_MODE): a guessed default would compile an `async`
108
+ * object to the sync contract without a word (#1370 was closed for that).
109
+ * - An authored `mode` the library contradicts is refused
110
+ * (EXTERNAL_TASK_MODE_MISMATCH): the plan would run the wrong contract.
111
+ * - `event` comes from the author or, for an `async` / `hybrid` object, the
112
+ * library; an authored one the library contradicts is refused
113
+ * (EXTERNAL_TASK_EVENT), and so is any pair {@link externalEventRefusal} refuses.
114
+ *
115
+ * @param owner - The task as the message names it, e.g. `External Workflow task "Run"`.
116
+ * @param authored - The task's authored `mode` and `event`.
117
+ * @param operation - The resolved library entry's `operation`, or `undefined`
118
+ * when no library is at hand (model-only `check`, which then judges only what
119
+ * was authored and reports no missing mode).
120
+ * @returns The mode and event, or the refusal.
121
+ */
122
+ export function externalExecution(owner, authored, operation) {
123
+ if (authored.mode !== undefined && !isExternalExecutionMode(authored.mode))
124
+ return {
125
+ code: 'EXTERNAL_TASK_MODE',
126
+ message: `${owner} mode must be the connector object's executionType: "sync", "async" or "hybrid".`,
127
+ };
128
+ const library = operation === undefined ? {} : libraryExecution(operation);
129
+ if (authored.mode !== undefined && library.mode !== undefined && authored.mode !== library.mode)
130
+ return {
131
+ code: EXTERNAL_TASK_MODE_MISMATCH,
132
+ message: `${owner} has mode "${authored.mode}", but the connector library records the object's executionType as ` +
133
+ `"${library.mode}"; the plan would run the wrong contract. Drop mode to use the library's, or fix it.`,
134
+ };
135
+ const mode = authored.mode ?? library.mode;
136
+ if (mode === undefined) {
137
+ if (operation === undefined)
138
+ return undefined; // model-only check: the library decides
139
+ return {
140
+ code: 'EXTERNAL_TASK_MODE',
141
+ message: `${owner} has no mode, and the connector library records no executionType for the object. ` +
142
+ `Pass mode: the object's executionType from its case spec result ` +
143
+ `(Data.EssentialConfiguration.essentialConfiguration.executionType), or rebuild the library with registry prepare.`,
144
+ };
145
+ }
146
+ const authoredEvent = authored.event;
147
+ if (authoredEvent !== undefined && library.event !== undefined
148
+ && (authoredEvent?.operation !== library.event.operation || authoredEvent?.mode !== library.event.mode))
149
+ return {
150
+ code: EXTERNAL_TASK_EVENT,
151
+ message: `${owner} has event ${JSON.stringify(authoredEvent)}, but the connector library records the object's event as ` +
152
+ `${JSON.stringify(library.event)}. Drop event to use the library's, or fix it.`,
153
+ };
154
+ const event = authoredEvent ?? (mode !== 'sync' ? library.event : undefined);
155
+ const refused = externalEventRefusal(owner, mode, event);
156
+ if (refused)
157
+ return { code: EXTERNAL_TASK_EVENT, message: refused };
158
+ return { mode, ...(event !== undefined ? { event } : {}) };
159
+ }
82
160
  /** Check's code for {@link externalDescriptorRefusal}. */
83
161
  export const EXTERNAL_TASK_DESCRIPTOR_CONTRACT = 'EXTERNAL_TASK_DESCRIPTOR_CONTRACT';
84
162
  /**
@@ -19,7 +19,7 @@ import { bindingLabelRefusal } from '../core/binding-messages.js';
19
19
  import { resolveEventWhere } from '../core/lookups.js';
20
20
  import { resolveCaseSubscription } from './subscription.js';
21
21
  import { dedupeConditionNames } from './rule-names.js';
22
- import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, EXTERNAL_TASK_EVENT, externalDescriptorRefusal, externalEventRefusal, externalServiceType, isExternalExecutionMode, } from './external.js';
22
+ import { EXTERNAL_TASK_DESCRIPTOR_CONTRACT, externalDescriptorRefusal, externalExecution, externalServiceType, } from './external.js';
23
23
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, caseVersionMismatch } from './format-profile.js';
24
24
  import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './resource-identity.js';
25
25
  import { ownEntry } from '../core/own-entry.js';
@@ -1828,24 +1828,26 @@ function externalTaskData(kind, spec, resolver, elementId, where) {
1828
1828
  const refused = externalDescriptorRefusal(kind, rc);
1829
1829
  if (refused)
1830
1830
  throw new CompileRefusal(EXTERNAL_TASK_DESCRIPTOR_CONTRACT, refused);
1831
- if (!isExternalExecutionMode(spec.mode))
1832
- throw new CompileRefusal('EXTERNAL_TASK_MODE', `${kind} task mode must be the connector object's executionType: "sync", "async" or "hybrid".`);
1833
- const eventRefused = externalEventRefusal(where, spec.mode, spec.event);
1834
- if (eventRefused)
1835
- throw new CompileRefusal(EXTERNAL_TASK_EVENT, eventRefused);
1831
+ // The authored mode and event, else the library's (#1369): the same call check makes.
1832
+ const execution = externalExecution(where, spec, rc.entry?.operation ?? {});
1833
+ if (!execution)
1834
+ throw new Error(`internal SDK error: ${where} was judged without its library operation.`);
1835
+ if (execution.code !== undefined)
1836
+ throw new CompileRefusal(execution.code, execution.message);
1837
+ const { mode, event } = execution;
1836
1838
  if (!spec.connector.connection || !spec.connector.folder)
1837
1839
  throw new CompileRefusal(spec.connector.connection ? 'EXTERNAL_TASK_FOLDER' : 'EXTERNAL_TASK_CONNECTION', `${kind} task requires non-empty connection and folder binding names.`);
1838
- const data = connectorTaskData(spec.connector, resolver, elementId, where, spec.mode, spec.event);
1840
+ const data = connectorTaskData(spec.connector, resolver, elementId, where, mode, event);
1839
1841
  const context = data.context;
1840
1842
  const metadataAt = context.findIndex((row) => row.name === 'metadata');
1841
1843
  // The Designer's order: the event pair (when the object answers through one),
1842
1844
  // then the execution type, just before `metadata`.
1843
- context.splice(metadataAt, 0, ...(spec.event ? [
1844
- io('operation', 'string', { value: spec.event.operation }),
1845
- io('eventMode', 'string', { value: spec.event.mode }),
1846
- ] : []), io('executionType', 'string', { value: spec.mode }));
1845
+ context.splice(metadataAt, 0, ...(event ? [
1846
+ io('operation', 'string', { value: event.operation }),
1847
+ io('eventMode', 'string', { value: event.mode }),
1848
+ ] : []), io('executionType', 'string', { value: mode }));
1847
1849
  context.push(io('activityConfigurationVersion', 'string', { value: 'v2' }));
1848
- return { ...data, serviceType: externalServiceType(kind, spec.mode) };
1850
+ return { ...data, serviceType: externalServiceType(kind, mode) };
1849
1851
  }
1850
1852
  /** A context/output name/value entry (the caseplan `InputOutput` shape). */
1851
1853
  function io(name, type, extra) {
@@ -278,6 +278,7 @@ export declare const REFUSALS: {
278
278
  readonly EXTERNAL_TASK_EVENT: "lowering";
279
279
  readonly EXTERNAL_TASK_FOLDER: "lowering";
280
280
  readonly EXTERNAL_TASK_MODE: "lowering";
281
+ readonly EXTERNAL_TASK_MODE_MISMATCH: "lowering";
281
282
  readonly EXTRACT_SCHEMA_SHAPE: "lint";
282
283
  readonly FAKE_JOIN: "lint";
283
284
  readonly FILE_NOT_FOUND: "lowering";
@@ -329,6 +329,7 @@ export const REFUSALS = {
329
329
  EXTERNAL_TASK_EVENT: 'lowering',
330
330
  EXTERNAL_TASK_FOLDER: 'lowering',
331
331
  EXTERNAL_TASK_MODE: 'lowering',
332
+ EXTERNAL_TASK_MODE_MISMATCH: 'lowering',
332
333
  EXTRACT_SCHEMA_SHAPE: 'lint',
333
334
  FAKE_JOIN: 'lint',
334
335
  FILE_NOT_FOUND: 'lowering',
@@ -1,5 +1,6 @@
1
1
  import type { LibraryEntry } from "../model/library.js";
2
2
  import { type BuildContext } from "./context.js";
3
+ import { type Dict } from "./py.js";
3
4
  /**
4
5
  * Registry `httpMethod` (e.g. `GETBYID`) → a guess at the `is resources`
5
6
  * operation name, for an entry whose canonical `operation.name` is empty.
@@ -18,6 +19,25 @@ export declare function discoverOperationName(ctx: BuildContext, entry: LibraryE
18
19
  * payload; `objectDisplayName` and `display.operationLabel` fill gaps only.
19
20
  */
20
21
  export declare function applyDescribe(entry: LibraryEntry, data: unknown): LibraryEntry;
22
+ /**
23
+ * Record an external agent's or automation's execution facts on its operation,
24
+ * from the describe payload (#1369, #1387).
25
+ *
26
+ * Integration Service keeps them on the object's metadata, not in the registry
27
+ * node: `executionType` (`sync`, `async` or `hybrid`) and, for an `async` or
28
+ * `hybrid` object, the event it answers with. `uip is resources describe`
29
+ * reports them as `ExecutionType`, `EventType` and `EventMode` (uip#5702,
30
+ * read with the same `externalExecutionOf` as `case spec`). They become
31
+ * `operation.executionType` and `operation.executionEvent` (`{ operation, mode }`),
32
+ * which Case and BPMN derive the task's `mode` and `event` from. The event is
33
+ * kept apart from `eventOperation` / `eventMode`, which mark a connector EVENT
34
+ * entry (a trigger) and which `identifyEvent` matches on.
35
+ *
36
+ * Only an `AgentExecution` / `ProcessExecution` operation gets them, and only
37
+ * what the payload states (an execution type outside the three is not stored): a describe from a CLI that does not report them
38
+ * leaves the entry as it was, so an older build stays byte-identical.
39
+ */
40
+ export declare function applyExternalExecution(operation: Dict, data: unknown): void;
21
41
  /**
22
42
  * `enrich_entry`: augment a canonical entry with `is resources describe` data.
23
43
  * On any failure the entry is returned as it was, so the pipeline degrades
@@ -78,6 +78,7 @@ export function applyDescribe(entry, data) {
78
78
  const opCurated = pick(opData, "Curated", "curated");
79
79
  if (pyTruthy(opCurated) && !pyTruthy(get(display, "operationLabel")))
80
80
  display.operationLabel = opCurated;
81
+ applyExternalExecution(operation, data);
81
82
  // Parameters (path / query) — raw shape, camelCase keys.
82
83
  const params = pyOr(pick(data, "Parameters", "parameters"), []);
83
84
  if (pyTruthy(params))
@@ -92,6 +93,45 @@ export function applyDescribe(entry, data) {
92
93
  e.outputSchema = { fields: normalizeFieldKeys(responseFields) };
93
94
  return entry;
94
95
  }
96
+ /** The operation subtypes Case's and BPMN's external agent / workflow tasks run. */
97
+ const EXTERNAL_SUBTYPES = new Set(["AgentExecution", "ProcessExecution"]);
98
+ /** The execution types Integration Service reports, as Case and BPMN read them. */
99
+ const EXECUTION_TYPES = new Set(["sync", "async", "hybrid"]);
100
+ /**
101
+ * Record an external agent's or automation's execution facts on its operation,
102
+ * from the describe payload (#1369, #1387).
103
+ *
104
+ * Integration Service keeps them on the object's metadata, not in the registry
105
+ * node: `executionType` (`sync`, `async` or `hybrid`) and, for an `async` or
106
+ * `hybrid` object, the event it answers with. `uip is resources describe`
107
+ * reports them as `ExecutionType`, `EventType` and `EventMode` (uip#5702,
108
+ * read with the same `externalExecutionOf` as `case spec`). They become
109
+ * `operation.executionType` and `operation.executionEvent` (`{ operation, mode }`),
110
+ * which Case and BPMN derive the task's `mode` and `event` from. The event is
111
+ * kept apart from `eventOperation` / `eventMode`, which mark a connector EVENT
112
+ * entry (a trigger) and which `identifyEvent` matches on.
113
+ *
114
+ * Only an `AgentExecution` / `ProcessExecution` operation gets them, and only
115
+ * what the payload states (an execution type outside the three is not stored): a describe from a CLI that does not report them
116
+ * leaves the entry as it was, so an older build stays byte-identical.
117
+ */
118
+ export function applyExternalExecution(operation, data) {
119
+ if (!EXTERNAL_SUBTYPES.has(String(get(operation, "subType") ?? "")))
120
+ return;
121
+ // Stored only as one of the three values Case and BPMN read, lowercased: a
122
+ // `Hybrid` kept verbatim would be ignored downstream and read as "no record".
123
+ const reported = pick(data, "ExecutionType", "executionType");
124
+ const executionType = typeof reported === "string" ? reported.trim().toLowerCase() : "";
125
+ if (!EXECUTION_TYPES.has(executionType))
126
+ return;
127
+ operation.executionType = executionType;
128
+ if (executionType === "sync")
129
+ return;
130
+ const eventType = pick(data, "EventType", "eventType");
131
+ const eventMode = pick(data, "EventMode", "eventMode");
132
+ if (typeof eventType === "string" && eventType && typeof eventMode === "string" && eventMode)
133
+ operation.executionEvent = { operation: eventType, mode: eventMode };
134
+ }
95
135
  /**
96
136
  * `enrich_entry`: augment a canonical entry with `is resources describe` data.
97
137
  * On any failure the entry is returned as it was, so the pipeline degrades
@@ -85,6 +85,16 @@ export interface EntryOperation {
85
85
  objectFromDiscovery?: boolean;
86
86
  eventOperation?: string;
87
87
  eventMode?: string;
88
+ /**
89
+ * An external agent's or automation's `executionType` (`sync`, `async`,
90
+ * `hybrid`), on an `AgentExecution` / `ProcessExecution` operation only.
91
+ */
92
+ executionType?: string;
93
+ /** The event an `async` / `hybrid` external object answers with. */
94
+ executionEvent?: {
95
+ operation: string;
96
+ mode: string;
97
+ };
88
98
  schemaAction?: SchemaAction | null;
89
99
  dispatchStyle?: string;
90
100
  paramGrammars?: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.68.6",
3
+ "version": "6.68.7",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "7e50034dc8a8317fe205d355859bc80f047174b3"
103
+ "gitref": "436dbc08c3130fcde3197adec572df61116f4ede"
104
104
  }