@uipath/maestro-builder-sdk 6.58.1 → 6.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +16 -10
- package/dist/api-members.md +66 -42
- package/dist/core/dotnet-types.d.ts +23 -0
- package/dist/core/dotnet-types.js +80 -0
- package/dist/core/refusals.d.ts +8 -0
- package/dist/core/refusals.js +8 -0
- package/dist/nodes/agent/flow/resources.d.ts +0 -9
- package/dist/nodes/agent/flow/resources.js +2 -13
- package/dist/nodes/hitl/flow/action-schema.d.ts +63 -0
- package/dist/nodes/hitl/flow/action-schema.js +228 -0
- package/dist/nodes/hitl/flow/check.js +63 -3
- package/dist/nodes/hitl/flow/node-type.js +22 -0
- package/dist/nodes/hitl/flow/spec.d.ts +99 -0
- package/dist/nodes/process/flow/check.js +21 -2
- package/dist/nodes/process/flow/node-type.js +16 -6
- package/dist/nodes/process/flow/spec.d.ts +27 -2
- package/package.json +2 -2
package/dist/core/refusals.js
CHANGED
|
@@ -188,6 +188,9 @@ export const REFUSALS = {
|
|
|
188
188
|
FORM_TRIGGER_NO_INPUTS: 'lint',
|
|
189
189
|
FUNCTION_EMPTY_RETURNS: 'lint',
|
|
190
190
|
FUNCTION_KEY_NOT_KEY: 'lint',
|
|
191
|
+
FUNCTION_LOCAL_HAS_FOLDER: 'lint',
|
|
192
|
+
FUNCTION_LOCAL_NO_PROJECT_ID: 'lint',
|
|
193
|
+
FUNCTION_LOCAL_PROJECT_ID_NOT_GUID: 'lint',
|
|
191
194
|
FUNCTION_NAME_IS_PATH: 'lint',
|
|
192
195
|
FUNCTION_NO_FOLDER: 'lint',
|
|
193
196
|
FUNCTION_NO_KEY: 'lint',
|
|
@@ -203,7 +206,12 @@ export const REFUSALS = {
|
|
|
203
206
|
GUARDRAIL_NO_VALIDATOR: 'lint',
|
|
204
207
|
GUARDRAIL_TYPE_UNKNOWN: 'lint',
|
|
205
208
|
HITL_APP_INCOMPLETE: 'lint',
|
|
209
|
+
HITL_APP_INPUT_REQUIRED: 'lint',
|
|
210
|
+
HITL_APP_INPUT_UNKNOWN: 'lint',
|
|
206
211
|
HITL_APP_MISSING: 'lint',
|
|
212
|
+
HITL_APP_NO_SCHEMA: 'lint',
|
|
213
|
+
HITL_APP_OUTCOME_UNKNOWN: 'lint',
|
|
214
|
+
HITL_APP_SCHEMA_INVALID: 'lint',
|
|
207
215
|
HITL_APP_WITHOUT_VARIANT: 'lint',
|
|
208
216
|
HITL_APP_WITH_FIELDS: 'lint',
|
|
209
217
|
HITL_CHANNEL_UNKNOWN: 'lint',
|
|
@@ -174,15 +174,6 @@ export declare function httpRequestToolResourceJson(label: string, inputs: Recor
|
|
|
174
174
|
id: string;
|
|
175
175
|
canvasNodeId: string;
|
|
176
176
|
}): Record<string, unknown>;
|
|
177
|
-
/**
|
|
178
|
-
* The .NET type an Action Center app argument of each field type carries. Studio
|
|
179
|
-
* Web serializes the task payload through `inputSchemaDotnetTypeMapping`, which
|
|
180
|
-
* the platform schema REQUIRES on an `actionCenter` channel; it reads the raw
|
|
181
|
-
* type off the app's action schema, which the SDK does not fetch, so this is
|
|
182
|
-
* the inverse of flow-workbench's `dotNetTypeToJsonSchema` for the scalars and
|
|
183
|
-
* `System.Object` for the two composite types it has no single answer for.
|
|
184
|
-
*/
|
|
185
|
-
export declare const DOTNET_TYPE: Record<string, string>;
|
|
186
177
|
/**
|
|
187
178
|
* The folder an app escalation names: the author's `folderPath`, or
|
|
188
179
|
* `solution_folder` when they gave none — an app that ships in this solution.
|
|
@@ -10,6 +10,7 @@ import ixpDefinition from '../../../core/ixp-definition.json' with { type: 'json
|
|
|
10
10
|
import { JOB_ATTACHMENT_REF, jobAttachmentDefinition } from '../../../core/agent-sidecar.js';
|
|
11
11
|
import { BUILTIN_TOOL_TYPE, CONNECTOR_TOOL_RESOURCE_TYPE, HTTP_REQUEST_TOOL_TYPE, INTERNAL_TOOL_RESOURCE_TYPE, TOOL_RESOURCE_TYPE, } from '../../../core/agent-tool-resources.js';
|
|
12
12
|
import { buildConfiguration } from '../../../core/connector-configuration.js';
|
|
13
|
+
import { ESCALATION_DOTNET_TYPE } from '../../../core/dotnet-types.js';
|
|
13
14
|
import { multipartBodyFieldName, transportHttpMethod } from '../../../core/connector-inputs.js';
|
|
14
15
|
import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexType } from '../../../core/context-index.js';
|
|
15
16
|
import { formFieldJsonSchema } from '../../../core/form-field-type.js';
|
|
@@ -445,18 +446,6 @@ export function httpRequestToolResourceJson(label, inputs, spec) {
|
|
|
445
446
|
properties: { toolType: HTTP_REQUEST_TOOL_TYPE },
|
|
446
447
|
};
|
|
447
448
|
}
|
|
448
|
-
/**
|
|
449
|
-
* The .NET type an Action Center app argument of each field type carries. Studio
|
|
450
|
-
* Web serializes the task payload through `inputSchemaDotnetTypeMapping`, which
|
|
451
|
-
* the platform schema REQUIRES on an `actionCenter` channel; it reads the raw
|
|
452
|
-
* type off the app's action schema, which the SDK does not fetch, so this is
|
|
453
|
-
* the inverse of flow-workbench's `dotNetTypeToJsonSchema` for the scalars and
|
|
454
|
-
* `System.Object` for the two composite types it has no single answer for.
|
|
455
|
-
*/
|
|
456
|
-
export const DOTNET_TYPE = {
|
|
457
|
-
string: 'System.String', number: 'System.Double', integer: 'System.Int32',
|
|
458
|
-
boolean: 'System.Boolean', object: 'System.Object', array: 'System.Object',
|
|
459
|
-
};
|
|
460
449
|
/**
|
|
461
450
|
* The folder an app escalation names: the author's `folderPath`, or
|
|
462
451
|
* `solution_folder` when they gave none — an app that ships in this solution.
|
|
@@ -537,7 +526,7 @@ export function escalationResourceJson(esc, spec) {
|
|
|
537
526
|
}
|
|
538
527
|
else {
|
|
539
528
|
const app = esc.app;
|
|
540
|
-
const mapping = (fields) => Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, ownEntry(
|
|
529
|
+
const mapping = (fields) => Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, ownEntry(ESCALATION_DOTNET_TYPE, t) ?? 'System.Object']));
|
|
541
530
|
channel = {
|
|
542
531
|
...common,
|
|
543
532
|
type: 'actionCenter',
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ActionSchema, AppRef } from './spec.js';
|
|
2
|
+
/** One argument of the schema, in JSON Schema terms. */
|
|
3
|
+
export interface ActionSchemaArg {
|
|
4
|
+
name: string;
|
|
5
|
+
schema: Record<string, unknown>;
|
|
6
|
+
required: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** The schema, whichever spelling it came in. */
|
|
9
|
+
export interface NormalizedActionSchema {
|
|
10
|
+
inputs: ActionSchemaArg[];
|
|
11
|
+
outputs: ActionSchemaArg[];
|
|
12
|
+
inOuts: ActionSchemaArg[];
|
|
13
|
+
outcomes: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The schema as one shape, or a sentence saying what is wrong with it. Two
|
|
17
|
+
* spellings are accepted, both verbatim:
|
|
18
|
+
*
|
|
19
|
+
* - a coded action app's `action-schema.json` — each section a JSON-Schema-ish
|
|
20
|
+
* `{ type: 'object', properties: { <name>: { type, required? } } }`;
|
|
21
|
+
* - the platform's action schema — each section an array of
|
|
22
|
+
* `{ name, type: 'System.String', required, isList }` (the resource file's
|
|
23
|
+
* `spec.actionSchema`, and what the Apps API returns).
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeActionSchema(schema: unknown): NormalizedActionSchema | string;
|
|
26
|
+
/**
|
|
27
|
+
* `inputs.app.inputSchema`: the inputs and the inOuts, with the required ones
|
|
28
|
+
* listed — `createInputSchemaFromAppSchema`'s shape, key for key.
|
|
29
|
+
*/
|
|
30
|
+
export declare function appInputSchema(s: NormalizedActionSchema): Record<string, unknown>;
|
|
31
|
+
/** What the reviewer answers: the outputs and the inOuts, by name. */
|
|
32
|
+
export declare function appAnswerSchemas(s: NormalizedActionSchema): Array<[string, Record<string, unknown>]>;
|
|
33
|
+
/**
|
|
34
|
+
* What a `check` rule needs of an app's contract — the input names, the
|
|
35
|
+
* required ones, the answers and the buttons — from whichever of `actionSchema`
|
|
36
|
+
* and a verbatim `inputSchema` the app carries; `undefined` with neither, a
|
|
37
|
+
* sentence when one is malformed.
|
|
38
|
+
*/
|
|
39
|
+
export declare function appContract(app: AppRef | undefined): {
|
|
40
|
+
inputs: string[];
|
|
41
|
+
required: string[];
|
|
42
|
+
answers: string[];
|
|
43
|
+
outcomes: string[];
|
|
44
|
+
} | string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The schema back from a node, as decompile prints it:
|
|
47
|
+
*
|
|
48
|
+
* - `{ actionSchema }`, in the `action-schema.json` spelling, when one
|
|
49
|
+
* reproduces the node's `inputSchema` and typed answer BYTE FOR BYTE — what
|
|
50
|
+
* the SDK and Studio Web's picker write. A property in both the input schema
|
|
51
|
+
* and the answer is an inOut; the rest split by side; outcomes are the task's.
|
|
52
|
+
* - `{ inputSchema }`, verbatim, otherwise — a hand-edited schema (a `title`, a
|
|
53
|
+
* nested object's `properties`) that no action schema lowers to. Recompiling
|
|
54
|
+
* writes it back unchanged.
|
|
55
|
+
*
|
|
56
|
+
* Either way serialize writes the node's `inputSchema` back as it was: the lift
|
|
57
|
+
* is checked against the emission, not assumed to invert it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function liftActionSchema(inputSchema: unknown, answers: Record<string, unknown> | undefined, outcomes: string[]): {
|
|
60
|
+
actionSchema: ActionSchema;
|
|
61
|
+
} | {
|
|
62
|
+
inputSchema: Record<string, unknown>;
|
|
63
|
+
} | undefined;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Action App's action schema — the contract of its form: what the task sends
|
|
3
|
+
* it (`inputs`), what the reviewer answers (`outputs`), what goes both ways
|
|
4
|
+
* (`inOuts`) and which buttons complete it (`outcomes`) — read in either of the
|
|
5
|
+
* two spellings an author can hold, and lowered to what the node carries.
|
|
6
|
+
*
|
|
7
|
+
* Why the node needs it: flow-v1's serializer types the task's
|
|
8
|
+
* `HitlTaskArguments` from `inputs.app.inputSchema`, and with none it falls back
|
|
9
|
+
* to `{ type: 'string' }` for every bound input (`hitl-serialization.ts`, the
|
|
10
|
+
* `appInputBindings` fallback). A number or an integer then reaches the app as a
|
|
11
|
+
* string. Studio Web's app picker writes the schema from the app's action schema
|
|
12
|
+
* (`HITLAppPicker.tsx` → `createInputSchemaFromAppSchema`); this SDK is offline,
|
|
13
|
+
* so the author hands it the same schema.
|
|
14
|
+
*
|
|
15
|
+
* The emission is the PRODUCT's, not a reading of JSON Schema: the platform
|
|
16
|
+
* spelling goes through the picker's own `.NET → JSON` table, and the
|
|
17
|
+
* `action-schema.json` spelling first through `uip codedapp push`'s `JSON → .NET`
|
|
18
|
+
* table — the path the same app's schema takes to reach the picker — so one app
|
|
19
|
+
* written either way emits the same bytes (core/dotnet-types.ts). That path
|
|
20
|
+
* keeps only a type: an object input's nested `properties`, a `description`, a
|
|
21
|
+
* `title` do not survive it, and do not survive here.
|
|
22
|
+
*/
|
|
23
|
+
import { actionSchemaFileDotNetType, dotNetTypeToJsonSchema } from '../../../core/dotnet-types.js';
|
|
24
|
+
const FILE_TYPES = 'string, integer, number, boolean, object, file, ContentValidationData or array';
|
|
25
|
+
/** An `action-schema.json` property's JSON Schema, or what is wrong with it. */
|
|
26
|
+
function fileSchema(where, p) {
|
|
27
|
+
const isList = p.type === 'array';
|
|
28
|
+
const item = (isList ? p.items : p);
|
|
29
|
+
if (isList && (!item || typeof item !== 'object'))
|
|
30
|
+
return `"${where}" is an array with no items schema`;
|
|
31
|
+
if (item.type === 'array')
|
|
32
|
+
return `"${where}" is an array of arrays, which uip codedapp push refuses`;
|
|
33
|
+
const dotNet = actionSchemaFileDotNetType(item.type, item.format);
|
|
34
|
+
if (dotNet === undefined) {
|
|
35
|
+
return `"${where}" has the type ${JSON.stringify(item.type)}; an action-schema.json type is one of ${FILE_TYPES}`;
|
|
36
|
+
}
|
|
37
|
+
return dotNetTypeToJsonSchema(dotNet, isList);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The schema as one shape, or a sentence saying what is wrong with it. Two
|
|
41
|
+
* spellings are accepted, both verbatim:
|
|
42
|
+
*
|
|
43
|
+
* - a coded action app's `action-schema.json` — each section a JSON-Schema-ish
|
|
44
|
+
* `{ type: 'object', properties: { <name>: { type, required? } } }`;
|
|
45
|
+
* - the platform's action schema — each section an array of
|
|
46
|
+
* `{ name, type: 'System.String', required, isList }` (the resource file's
|
|
47
|
+
* `spec.actionSchema`, and what the Apps API returns).
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeActionSchema(schema) {
|
|
50
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema))
|
|
51
|
+
return 'it is not an object';
|
|
52
|
+
const s = schema;
|
|
53
|
+
const section = (key) => {
|
|
54
|
+
const v = s[key];
|
|
55
|
+
if (v === undefined || v === null)
|
|
56
|
+
return [];
|
|
57
|
+
if (Array.isArray(v)) {
|
|
58
|
+
const args = [];
|
|
59
|
+
for (const a of v) {
|
|
60
|
+
if (!a || typeof a.name !== 'string' || a.name === '' || typeof a.type !== 'string') {
|
|
61
|
+
return `"${key}" has an entry without a name and a type`;
|
|
62
|
+
}
|
|
63
|
+
args.push({ name: a.name, schema: dotNetTypeToJsonSchema(a.type, a.isList === true), required: a.required === true });
|
|
64
|
+
}
|
|
65
|
+
return args;
|
|
66
|
+
}
|
|
67
|
+
if (typeof v !== 'object')
|
|
68
|
+
return `"${key}" is neither an array nor an object`;
|
|
69
|
+
const props = v.properties ?? {};
|
|
70
|
+
if (!props || typeof props !== 'object' || Array.isArray(props))
|
|
71
|
+
return `"${key}.properties" is not an object`;
|
|
72
|
+
const args = [];
|
|
73
|
+
for (const [name, p] of Object.entries(props)) {
|
|
74
|
+
if (!p || typeof p !== 'object' || typeof p.type !== 'string')
|
|
75
|
+
return `"${key}.properties.${name}" has no type`;
|
|
76
|
+
// Outcomes are buttons: push flattens them to System.String whatever they say.
|
|
77
|
+
const schema = key === 'outcomes' ? { type: 'string' } : fileSchema(`${key}.properties.${name}`, p);
|
|
78
|
+
if (typeof schema === 'string')
|
|
79
|
+
return schema;
|
|
80
|
+
args.push({ name, schema, required: p.required === true });
|
|
81
|
+
}
|
|
82
|
+
return args;
|
|
83
|
+
};
|
|
84
|
+
const inputs = section('inputs');
|
|
85
|
+
const outputs = section('outputs');
|
|
86
|
+
const inOuts = section('inOuts');
|
|
87
|
+
const outcomes = section('outcomes');
|
|
88
|
+
for (const r of [inputs, outputs, inOuts, outcomes])
|
|
89
|
+
if (typeof r === 'string')
|
|
90
|
+
return r;
|
|
91
|
+
return {
|
|
92
|
+
inputs: inputs,
|
|
93
|
+
outputs: outputs,
|
|
94
|
+
inOuts: inOuts,
|
|
95
|
+
outcomes: outcomes.map((o) => o.name),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `inputs.app.inputSchema`: the inputs and the inOuts, with the required ones
|
|
100
|
+
* listed — `createInputSchemaFromAppSchema`'s shape, key for key.
|
|
101
|
+
*/
|
|
102
|
+
export function appInputSchema(s) {
|
|
103
|
+
const all = [...s.inputs, ...s.inOuts];
|
|
104
|
+
const required = all.filter((a) => a.required).map((a) => a.name);
|
|
105
|
+
return {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: Object.fromEntries(all.map((a) => [a.name, { ...a.schema }])),
|
|
108
|
+
...(required.length > 0 ? { required } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** What the reviewer answers: the outputs and the inOuts, by name. */
|
|
112
|
+
export function appAnswerSchemas(s) {
|
|
113
|
+
return [...s.outputs, ...s.inOuts].map((a) => [a.name, { ...a.schema }]);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* What a `check` rule needs of an app's contract — the input names, the
|
|
117
|
+
* required ones, the answers and the buttons — from whichever of `actionSchema`
|
|
118
|
+
* and a verbatim `inputSchema` the app carries; `undefined` with neither, a
|
|
119
|
+
* sentence when one is malformed.
|
|
120
|
+
*/
|
|
121
|
+
export function appContract(app) {
|
|
122
|
+
if (app?.actionSchema !== undefined && app?.inputSchema !== undefined) {
|
|
123
|
+
return 'it gives both actionSchema and inputSchema; inputSchema is the node\'s verbatim form, for a decompiled flow, so keep one';
|
|
124
|
+
}
|
|
125
|
+
if (app?.actionSchema !== undefined) {
|
|
126
|
+
const s = normalizeActionSchema(app.actionSchema);
|
|
127
|
+
if (typeof s === 'string')
|
|
128
|
+
return s;
|
|
129
|
+
const all = [...s.inputs, ...s.inOuts];
|
|
130
|
+
return {
|
|
131
|
+
inputs: all.map((a) => a.name),
|
|
132
|
+
required: all.filter((a) => a.required).map((a) => a.name),
|
|
133
|
+
answers: [...s.outputs, ...s.inOuts].map((a) => a.name),
|
|
134
|
+
outcomes: s.outcomes,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (app?.inputSchema !== undefined) {
|
|
138
|
+
const i = app.inputSchema;
|
|
139
|
+
if (!i || typeof i !== 'object' || !i.properties || typeof i.properties !== 'object' || Array.isArray(i.properties)) {
|
|
140
|
+
return 'its inputSchema has no properties object';
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
inputs: Object.keys(i.properties),
|
|
144
|
+
required: Array.isArray(i.required) ? i.required.filter((r) => typeof r === 'string') : [],
|
|
145
|
+
answers: [],
|
|
146
|
+
outcomes: [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/** The `action-schema.json` property a picker-shaped JSON Schema came from, if any. */
|
|
152
|
+
function fileProperty(p) {
|
|
153
|
+
if (!p || typeof p !== 'object' || Array.isArray(p))
|
|
154
|
+
return undefined;
|
|
155
|
+
const { type, format, items, ...rest } = p;
|
|
156
|
+
if (Object.keys(rest).length > 0)
|
|
157
|
+
return undefined;
|
|
158
|
+
if (type === 'array') {
|
|
159
|
+
const item = fileProperty(items);
|
|
160
|
+
return item !== undefined && format === undefined ? { type: 'array', items: item } : undefined;
|
|
161
|
+
}
|
|
162
|
+
if (items !== undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (type === 'string' && format === 'date-time')
|
|
165
|
+
return { type: 'string', format: 'date' };
|
|
166
|
+
if (format !== undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
const byType = {
|
|
169
|
+
string: 'string', integer: 'integer', number: 'number', boolean: 'boolean',
|
|
170
|
+
'System.Object': 'object',
|
|
171
|
+
'UiPath.Platform.ResourceHandling.IResource': 'file',
|
|
172
|
+
'UiPath.DocumentProcessing.Contracts.Actions.ContentValidationData': 'ContentValidationData',
|
|
173
|
+
};
|
|
174
|
+
return typeof type === 'string' && Object.hasOwn(byType, type) ? { type: byType[type] } : undefined;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The schema back from a node, as decompile prints it:
|
|
178
|
+
*
|
|
179
|
+
* - `{ actionSchema }`, in the `action-schema.json` spelling, when one
|
|
180
|
+
* reproduces the node's `inputSchema` and typed answer BYTE FOR BYTE — what
|
|
181
|
+
* the SDK and Studio Web's picker write. A property in both the input schema
|
|
182
|
+
* and the answer is an inOut; the rest split by side; outcomes are the task's.
|
|
183
|
+
* - `{ inputSchema }`, verbatim, otherwise — a hand-edited schema (a `title`, a
|
|
184
|
+
* nested object's `properties`) that no action schema lowers to. Recompiling
|
|
185
|
+
* writes it back unchanged.
|
|
186
|
+
*
|
|
187
|
+
* Either way serialize writes the node's `inputSchema` back as it was: the lift
|
|
188
|
+
* is checked against the emission, not assumed to invert it.
|
|
189
|
+
*/
|
|
190
|
+
export function liftActionSchema(inputSchema, answers, outcomes) {
|
|
191
|
+
if (!inputSchema || typeof inputSchema !== 'object' || Array.isArray(inputSchema))
|
|
192
|
+
return undefined;
|
|
193
|
+
const verbatim = { inputSchema: JSON.parse(JSON.stringify(inputSchema)) };
|
|
194
|
+
const props = (inputSchema.properties ?? {});
|
|
195
|
+
const required = new Set(Array.isArray(inputSchema.required)
|
|
196
|
+
? inputSchema.required.filter((r) => typeof r === 'string') : []);
|
|
197
|
+
const answerProps = Object.fromEntries(Object.entries(answers ?? {}).filter(([k]) => k !== 'Action'));
|
|
198
|
+
const lift = (entries, req) => {
|
|
199
|
+
const out = [];
|
|
200
|
+
for (const [k, p] of entries) {
|
|
201
|
+
const f = fileProperty(p);
|
|
202
|
+
if (f === undefined)
|
|
203
|
+
return undefined;
|
|
204
|
+
out.push([k, { ...f, ...(req(k) ? { required: true } : {}) }]);
|
|
205
|
+
}
|
|
206
|
+
return { type: 'object', properties: Object.fromEntries(out) };
|
|
207
|
+
};
|
|
208
|
+
const ins = lift(Object.entries(props).filter(([k]) => !(k in answerProps)), (k) => required.has(k));
|
|
209
|
+
const ios = lift(Object.entries(props).filter(([k]) => k in answerProps), (k) => required.has(k));
|
|
210
|
+
const outs = lift(Object.entries(answerProps).filter(([k]) => !(k in props)), () => false);
|
|
211
|
+
if (!ins || !ios || !outs)
|
|
212
|
+
return verbatim;
|
|
213
|
+
const actionSchema = {
|
|
214
|
+
inputs: ins,
|
|
215
|
+
...(Object.keys(outs.properties).length > 0 ? { outputs: outs } : {}),
|
|
216
|
+
...(Object.keys(ios.properties).length > 0 ? { inOuts: ios } : {}),
|
|
217
|
+
...(outcomes.length > 0
|
|
218
|
+
? { outcomes: { type: 'object', properties: Object.fromEntries(outcomes.map((o) => [o, { type: 'string' }])) } }
|
|
219
|
+
: {}),
|
|
220
|
+
};
|
|
221
|
+
// Only a lift that serializes back to the same bytes is an action schema.
|
|
222
|
+
const again = normalizeActionSchema(actionSchema);
|
|
223
|
+
if (typeof again === 'string')
|
|
224
|
+
return verbatim;
|
|
225
|
+
const same = JSON.stringify(appInputSchema(again)) === JSON.stringify(inputSchema)
|
|
226
|
+
&& JSON.stringify(Object.fromEntries(appAnswerSchemas(again))) === JSON.stringify(answerProps);
|
|
227
|
+
return same ? { actionSchema } : verbatim;
|
|
228
|
+
}
|
|
@@ -3,6 +3,7 @@ import { nearestName } from '../../../core/edit-distance.js';
|
|
|
3
3
|
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText, } from '../../../core/form-field-type.js';
|
|
4
4
|
import { isAnswerField } from '../../../core/hitl-answers.js';
|
|
5
5
|
import { hitlDerivesOutcomeHandles, outcomeIdOf } from '../../../core/hitl-routing.js';
|
|
6
|
+
import { appContract } from './action-schema.js';
|
|
6
7
|
export const HITL_DIAGNOSTICS = {
|
|
7
8
|
HITL_VARIANT_UNKNOWN: { severity: 'error', summary: 'a variant that names no human-task node type' },
|
|
8
9
|
HITL_OUTCOME_PORTS_VARIANT: { severity: 'error', summary: 'exposeError or outcomePorts on a variant that cannot honour it' },
|
|
@@ -19,6 +20,11 @@ export const HITL_DIAGNOSTICS = {
|
|
|
19
20
|
HITL_APP_WITH_FIELDS: { severity: 'error', summary: 'form fields on an app-backed task' },
|
|
20
21
|
HITL_APP_MISSING: { severity: 'error', summary: 'an action-app task that names no app' },
|
|
21
22
|
HITL_APP_INCOMPLETE: { severity: 'error', summary: 'an app reference missing name, key or folderPath' },
|
|
23
|
+
HITL_APP_SCHEMA_INVALID: { severity: 'error', summary: 'an app actionSchema that is not an action schema' },
|
|
24
|
+
HITL_APP_NO_SCHEMA: { severity: 'warning', summary: 'inputs sent to an app with no actionSchema, so every one is a string' },
|
|
25
|
+
HITL_APP_INPUT_UNKNOWN: { severity: 'error', summary: 'an app input the action schema does not declare' },
|
|
26
|
+
HITL_APP_INPUT_REQUIRED: { severity: 'error', summary: 'a required app input left unbound' },
|
|
27
|
+
HITL_APP_OUTCOME_UNKNOWN: { severity: 'error', summary: 'an outcome the app has no button for' },
|
|
22
28
|
HITL_NO_FIELDS: { severity: 'error', summary: 'no fields array' },
|
|
23
29
|
HITL_FIELD_NO_ID: { severity: 'error', summary: 'a form field with no id' },
|
|
24
30
|
HITL_DUP_FIELD: { severity: 'error', summary: 'a form field id declared twice' },
|
|
@@ -183,6 +189,48 @@ export function checkHitl(inputs, ctx) {
|
|
|
183
189
|
return;
|
|
184
190
|
}
|
|
185
191
|
}
|
|
192
|
+
// The app's action schema: what types its inputs, and what it answers.
|
|
193
|
+
const bound = Object.keys(app.inputs ?? {});
|
|
194
|
+
const contract = appContract(app);
|
|
195
|
+
if (contract === undefined) {
|
|
196
|
+
if (bound.length > 0) {
|
|
197
|
+
at('HITL_APP_NO_SCHEMA', `Human task "${step}" sends ${bound.length} input(s) to its Action App but gives no actionSchema. `
|
|
198
|
+
+ `The platform types the task's arguments from the app's schema and falls back to string for every `
|
|
199
|
+
+ `input without one, so a number or a boolean reaches the app as text — and its answer fields `
|
|
200
|
+
+ `cannot be read with out('${step}', …).`, `app: { …, actionSchema: <the app's action-schema.json> }`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (typeof contract === 'string') {
|
|
204
|
+
at('HITL_APP_SCHEMA_INVALID', `Human task "${step}" gives its Action App a schema that is not usable: ${contract}. `
|
|
205
|
+
+ `Pass the app's action-schema.json verbatim, or the platform's action schema (its `
|
|
206
|
+
+ `inputs/outputs/inOuts/outcomes arrays).`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const names = contract.inputs;
|
|
211
|
+
for (const k of bound) {
|
|
212
|
+
if (!names.includes(k)) {
|
|
213
|
+
const nearest = nearestName(k, names);
|
|
214
|
+
at('HITL_APP_INPUT_UNKNOWN', `Human task "${step}" sends "${k}" to its Action App, which declares no such input `
|
|
215
|
+
+ `(it takes ${names.length > 0 ? names.join(', ') : 'none'}). The value is dropped.`, nearest !== undefined ? `inputs: { ${JSON.stringify(nearest)}: … }` : undefined);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const missing = contract.required.filter((r) => !bound.includes(r));
|
|
219
|
+
if (missing.length > 0) {
|
|
220
|
+
at('HITL_APP_INPUT_REQUIRED', `Human task "${step}" leaves the Action App's required input(s) ${missing.join(', ')} unbound, `
|
|
221
|
+
+ `so the task's arguments fail the app's own schema.`, `inputs: { ${missing.map((m) => `${JSON.stringify(m)}: …`).join(', ')} }`);
|
|
222
|
+
}
|
|
223
|
+
if (contract.outcomes.length > 0) {
|
|
224
|
+
for (const o of Array.isArray(inputs?.outcomes) ? inputs.outcomes : []) {
|
|
225
|
+
const name = typeof o === 'string' ? o : o?.name;
|
|
226
|
+
if (typeof name === 'string' && name !== '' && !contract.outcomes.includes(name)) {
|
|
227
|
+
const nearest = nearestName(name, contract.outcomes);
|
|
228
|
+
at('HITL_APP_OUTCOME_UNKNOWN', `Human task "${step}" routes on the outcome "${name}", but its Action App completes only with `
|
|
229
|
+
+ `${contract.outcomes.join(', ')}. The reviewer can never pick it.`, nearest !== undefined ? `'${nearest}'` : undefined);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
186
234
|
// Outcomes still matter for an app task: the flow's own routing and its
|
|
187
235
|
// `Action`/`status` outputs are derived from them, so fall through to the
|
|
188
236
|
// outcome rules below rather than returning here.
|
|
@@ -389,11 +437,23 @@ export function checkHitlRead({ node: name, field, rest, reader: step }, form, r
|
|
|
389
437
|
return; // `$vars.<name>.output` — the whole object, fine.
|
|
390
438
|
if (answer === 'Action')
|
|
391
439
|
return; // which outcome was pressed.
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
440
|
+
const appSchema = form?.variant === 'action-app' && form.app?.actionSchema !== undefined
|
|
441
|
+
? appContract({ ...form.app, inputSchema: undefined }) : undefined;
|
|
442
|
+
const appAnswers = typeof appSchema === 'object' ? appSchema.answers : [];
|
|
443
|
+
const answers = [
|
|
444
|
+
...(form?.fields ?? []).filter((f) => isAnswerField(f) && typeof f?.id === 'string').map((f) => f.id),
|
|
445
|
+
...appAnswers,
|
|
446
|
+
];
|
|
395
447
|
if (answers.includes(answer))
|
|
396
448
|
return;
|
|
449
|
+
if (form?.variant === 'action-app') {
|
|
450
|
+
const nearestApp = nearestName(answer, [...answers, 'Action']);
|
|
451
|
+
report('HITL_READ_UNKNOWN_FIELD', `Human task "${name}" has no answer "${answer}" (read as "$vars.${name}.output.${answer}" in `
|
|
452
|
+
+ `"${step}"). Its Action App answers with ${appSchema === undefined
|
|
453
|
+
? 'fields only its actionSchema declares, and it gives none'
|
|
454
|
+
: `its schema's outputs and inOuts (${appAnswers.length > 0 ? appAnswers.join(', ') : 'none'})`} plus "Action".`, nearestApp !== undefined ? `out('${name}', '${nearestApp}')` : `app: { …, actionSchema: <the app's action-schema.json> }`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
397
457
|
const shown = (form?.fields ?? [])
|
|
398
458
|
.filter((f) => f?.direction === 'input' && typeof f?.id === 'string')
|
|
399
459
|
.map((f) => f.id);
|
|
@@ -19,6 +19,7 @@ import { OUTCOME_HANDLE_NODE_TYPES, hitlExitModel, outcomeIdOf, outcomeSlug } fr
|
|
|
19
19
|
import { jsonClone } from '../../../core/json-clone.js';
|
|
20
20
|
import { schemaId } from '../../../core/stable-id.js';
|
|
21
21
|
import { wireExprBody } from '../../../core/wire-expr.js';
|
|
22
|
+
import { appAnswerSchemas, appInputSchema, liftActionSchema, normalizeActionSchema } from './action-schema.js';
|
|
22
23
|
import { checkHitl, checkHitlRead } from './check.js';
|
|
23
24
|
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
24
25
|
const BASE = 'uipath.human-in-the-loop';
|
|
@@ -107,6 +108,15 @@ function hitlInputs(inputs, nodeType, ctx) {
|
|
|
107
108
|
continue;
|
|
108
109
|
properties[f.id] = { ...formFieldJsonSchema(f.type) };
|
|
109
110
|
}
|
|
111
|
+
// An app's answer is its action schema's outputs and inOuts — the app owns the
|
|
112
|
+
// form, so the schema is the only place they are declared.
|
|
113
|
+
const appSchema = isApp && inputs?.app?.actionSchema !== undefined
|
|
114
|
+
? normalizeActionSchema(inputs.app.actionSchema) : undefined;
|
|
115
|
+
const schema = typeof appSchema === 'object' ? appSchema : undefined;
|
|
116
|
+
if (schema !== undefined) {
|
|
117
|
+
for (const [name, s] of appAnswerSchemas(schema))
|
|
118
|
+
properties[name] = s;
|
|
119
|
+
}
|
|
110
120
|
const names = outcomes.map((o) => o.name).filter((n) => typeof n === 'string');
|
|
111
121
|
if (names.length > 0) {
|
|
112
122
|
properties.Action = { type: 'string', enum: names, default: names[0] };
|
|
@@ -187,6 +197,10 @@ function hitlInputs(inputs, nodeType, ctx) {
|
|
|
187
197
|
key: app.key,
|
|
188
198
|
folderPath: app.folderPath,
|
|
189
199
|
...(app.appSystemName !== undefined ? { appSystemName: app.appSystemName } : {}),
|
|
200
|
+
// What types the task's arguments (see action-schema.ts): lowered
|
|
201
|
+
// from the action schema, or the decompiled node's own, verbatim.
|
|
202
|
+
...(schema !== undefined ? { inputSchema: appInputSchema(schema) }
|
|
203
|
+
: isApp && app.inputSchema !== undefined ? { inputSchema: jsonClone(app.inputSchema) } : {}),
|
|
190
204
|
},
|
|
191
205
|
...(Object.keys(appInputBindings).length > 0 ? { appInputBindings } : {}),
|
|
192
206
|
}
|
|
@@ -406,6 +420,12 @@ function liftHitl(node, typeName, ctx) {
|
|
|
406
420
|
...(app.appSystemName === null ? { appSystemName: null }
|
|
407
421
|
: typeof app.appSystemName === 'string' ? { appSystemName: app.appSystemName } : {}),
|
|
408
422
|
...(appInputs !== undefined ? { inputs: appInputs } : {}),
|
|
423
|
+
// The schema comes back as an action schema when one serializes to the
|
|
424
|
+
// node's exact bytes, else as the node's inputSchema verbatim — either way a
|
|
425
|
+
// recompile writes the same input schema (action-schema.ts).
|
|
426
|
+
...(variant === 'action-app' && app.inputSchema !== undefined
|
|
427
|
+
? liftActionSchema(app.inputSchema, n.outputs?.output?.properties, outcomes.map((o) => (typeof o === 'string' ? o : o.name)))
|
|
428
|
+
: {}),
|
|
409
429
|
};
|
|
410
430
|
// The validation station's own inputs ride as PLAIN `$vars.…` paths. Anything
|
|
411
431
|
// but a `$vars.` reference stays the wire text verbatim.
|
|
@@ -502,6 +522,8 @@ function printHitl({ inputs }, ctx) {
|
|
|
502
522
|
: h.app.appSystemName !== undefined ? ctx.string(h.app.appSystemName) : undefined],
|
|
503
523
|
['inputs', h.app.inputs !== undefined
|
|
504
524
|
? obj(Object.entries(h.app.inputs).map(([k, v]) => [k, val(v)]), ctx) : undefined],
|
|
525
|
+
['actionSchema', h.app.actionSchema !== undefined ? ctx.value(h.app.actionSchema) : undefined],
|
|
526
|
+
['inputSchema', h.app.inputSchema !== undefined ? ctx.value(h.app.inputSchema) : undefined],
|
|
505
527
|
], ctx);
|
|
506
528
|
const document = h.document === undefined ? undefined : obj([
|
|
507
529
|
['extractionResult', val(h.document.extractionResult)],
|
|
@@ -182,7 +182,106 @@ export interface AppRef {
|
|
|
182
182
|
* into the task's `HitlTaskArguments`.
|
|
183
183
|
*/
|
|
184
184
|
inputs?: Record<string, Expr | string>;
|
|
185
|
+
/**
|
|
186
|
+
* The app's action schema, verbatim — what types each input, and what the
|
|
187
|
+
* reviewer answers. For a coded action app that is its `action-schema.json`,
|
|
188
|
+
* so `import schema from './InvoiceReview/action-schema.json'` and pass it;
|
|
189
|
+
* the platform's own spelling (the arrays of `{ name, type: 'System.Decimal',
|
|
190
|
+
* required }` in the solution's `resources/solution_folder/app/codedAction/<app>.json`
|
|
191
|
+
* `spec.actionSchema`) is accepted too.
|
|
192
|
+
*
|
|
193
|
+
* Without it every input reaches the app as a STRING: the platform types the
|
|
194
|
+
* task's arguments from `inputs.app.inputSchema`, which is written from this,
|
|
195
|
+
* and falls back to `string` for each bound input when there is none. It also
|
|
196
|
+
* declares the answer, so `out('<step>', '<output>')` reads are checked.
|
|
197
|
+
*
|
|
198
|
+
* @enforcedBy HITL_APP_NO_SCHEMA Inputs sent to an app without its schema all become strings.
|
|
199
|
+
* @enforcedBy HITL_APP_INPUT_UNKNOWN An input the app does not declare.
|
|
200
|
+
* @enforcedBy HITL_APP_INPUT_REQUIRED A required app input left unbound.
|
|
201
|
+
* @enforcedBy HITL_APP_OUTCOME_UNKNOWN An outcome the app has no button for.
|
|
202
|
+
*
|
|
203
|
+
* Only each argument's TYPE reaches the node, as Studio Web writes it: an
|
|
204
|
+
* object input becomes `{ type: 'System.Object' }` and a `file` input
|
|
205
|
+
* `{ type: 'UiPath.Platform.ResourceHandling.IResource' }` (the platform's .NET
|
|
206
|
+
* names, passed through by its picker), and a nested object's `properties`, a
|
|
207
|
+
* `description` or a `title` are not carried.
|
|
208
|
+
*/
|
|
209
|
+
actionSchema?: ActionSchema;
|
|
210
|
+
/**
|
|
211
|
+
* The node's `inputs.app.inputSchema`, verbatim — what decompile writes for a
|
|
212
|
+
* flow whose schema no {@link AppRef.actionSchema} lowers to (a hand-edited
|
|
213
|
+
* `title`, a nested object's `properties`), so recompiling leaves it as it
|
|
214
|
+
* was. Author new flows with `actionSchema`; give one or the other.
|
|
215
|
+
*
|
|
216
|
+
* @enforcedBy HITL_APP_SCHEMA_INVALID Both forms, or an inputSchema with no properties.
|
|
217
|
+
*/
|
|
218
|
+
inputSchema?: Record<string, unknown>;
|
|
219
|
+
}
|
|
220
|
+
/** One property of an `action-schema.json` section. */
|
|
221
|
+
export interface ActionSchemaProperty {
|
|
222
|
+
/**
|
|
223
|
+
* Its type: `'string'`, `'integer'`, `'number'`, `'boolean'`, `'object'`,
|
|
224
|
+
* `'file'`, `'ContentValidationData'`, or `'array'` with `items` — the set
|
|
225
|
+
* `uip codedapp push` accepts.
|
|
226
|
+
*/
|
|
227
|
+
type: string;
|
|
228
|
+
/** Whether the app needs it. A required input must be bound in `app.inputs`. */
|
|
229
|
+
required?: boolean;
|
|
230
|
+
/** What it is for. Documentation only: it is not emitted. */
|
|
231
|
+
description?: string;
|
|
232
|
+
/** `'date'` (a `System.DateOnly`) or `'uuid'` (a `System.Guid`). */
|
|
233
|
+
format?: string;
|
|
234
|
+
/** An array's item schema, e.g. `{ type: 'string' }`. */
|
|
235
|
+
items?: Record<string, unknown>;
|
|
236
|
+
/** An object's own properties. The app keeps them; the node does not. */
|
|
237
|
+
properties?: Record<string, ActionSchemaProperty>;
|
|
238
|
+
}
|
|
239
|
+
/** One section (`inputs`, `outputs`, `inOuts`, `outcomes`) of an `action-schema.json`. */
|
|
240
|
+
export interface ActionSchemaSection {
|
|
241
|
+
/** Always `'object'`. */
|
|
242
|
+
type?: 'object';
|
|
243
|
+
/** The section's arguments, by the app's own names. */
|
|
244
|
+
properties?: Record<string, ActionSchemaProperty>;
|
|
245
|
+
}
|
|
246
|
+
/** A coded action app's `action-schema.json`. */
|
|
247
|
+
export interface ActionSchemaFile {
|
|
248
|
+
/** What the task sends the app. Becomes `inputs.app.inputSchema`. */
|
|
249
|
+
inputs?: ActionSchemaSection;
|
|
250
|
+
/** What the reviewer answers. Readable as `out('<step>', '<name>')`. */
|
|
251
|
+
outputs?: ActionSchemaSection;
|
|
252
|
+
/** Sent to the app AND answered back. */
|
|
253
|
+
inOuts?: ActionSchemaSection;
|
|
254
|
+
/** The buttons that complete the task. The flow's `outcomes` must be among them. */
|
|
255
|
+
outcomes?: ActionSchemaSection;
|
|
256
|
+
}
|
|
257
|
+
/** One argument of the platform's action schema. */
|
|
258
|
+
export interface PlatformActionSchemaArg {
|
|
259
|
+
/** The argument's name, as the app declares it. */
|
|
260
|
+
name: string;
|
|
261
|
+
/** A .NET type name, e.g. `'System.Decimal'`. */
|
|
262
|
+
type: string;
|
|
263
|
+
/** Whether the app needs it. */
|
|
264
|
+
required?: boolean;
|
|
265
|
+
/** A list of `type`, which lowers to a JSON array. */
|
|
266
|
+
isList?: boolean;
|
|
267
|
+
/** The platform's other keys (`key`, `version`, `typeNamespace`, …), ignored. */
|
|
268
|
+
[field: string]: unknown;
|
|
269
|
+
}
|
|
270
|
+
/** The platform's action schema (Apps API, a solution's `spec.actionSchema`). */
|
|
271
|
+
export interface PlatformActionSchema {
|
|
272
|
+
/** What the task sends the app. */
|
|
273
|
+
inputs?: PlatformActionSchemaArg[];
|
|
274
|
+
/** What the reviewer answers. */
|
|
275
|
+
outputs?: PlatformActionSchemaArg[];
|
|
276
|
+
/** Sent and answered back. */
|
|
277
|
+
inOuts?: PlatformActionSchemaArg[];
|
|
278
|
+
/** The buttons that complete the task. */
|
|
279
|
+
outcomes?: PlatformActionSchemaArg[];
|
|
280
|
+
/** The schema's other keys (`key`, `id`, `name`, …), ignored. */
|
|
281
|
+
[field: string]: unknown;
|
|
185
282
|
}
|
|
283
|
+
/** An Action App's action schema, in either spelling. */
|
|
284
|
+
export type ActionSchema = ActionSchemaFile | PlatformActionSchema;
|
|
186
285
|
export interface HitlInputs {
|
|
187
286
|
/**
|
|
188
287
|
* Which human-task node type to emit. Omit it for today's default — see
|