@formio/uag 1.13.2 → 2.0.0-rc.10
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/README.md +237 -535
- package/bin/uag-eval.js +99 -0
- package/bin/uag.js +116 -0
- package/lib/UAGFormInterface.d.ts +72 -2
- package/lib/UAGFormInterface.js +226 -57
- package/lib/UAGProjectInterface.d.ts +45 -5
- package/lib/UAGProjectInterface.js +187 -36
- package/lib/actionLogs.d.ts +24 -0
- package/lib/actionLogs.js +149 -0
- package/lib/actions/runTask.d.ts +43 -0
- package/lib/actions/runTask.js +324 -0
- package/lib/agent/anonymous.d.ts +34 -0
- package/lib/agent/anonymous.js +99 -0
- package/lib/agent/chat.d.ts +114 -0
- package/lib/agent/chat.js +475 -0
- package/lib/agent/engines.d.ts +17 -0
- package/lib/agent/engines.js +46 -0
- package/lib/agent/errors.d.ts +25 -0
- package/lib/agent/errors.js +132 -0
- package/lib/agent/index.d.ts +4 -0
- package/lib/agent/index.js +19 -0
- package/lib/agent/remote.d.ts +71 -0
- package/lib/agent/remote.js +252 -0
- package/lib/agent/requestId.d.ts +4 -0
- package/lib/agent/requestId.js +19 -0
- package/lib/agent/router.d.ts +61 -0
- package/lib/agent/router.js +307 -0
- package/lib/agent/stubs.d.ts +37 -0
- package/lib/agent/stubs.js +39 -0
- package/lib/agent/tools.d.ts +31 -0
- package/lib/agent/tools.js +76 -0
- package/lib/config.d.ts +97 -3
- package/lib/config.js +19 -0
- package/lib/database.d.ts +70 -0
- package/lib/database.js +212 -0
- package/lib/documents/convert.d.ts +38 -0
- package/lib/documents/convert.js +151 -0
- package/lib/documents/converters.d.ts +28 -0
- package/lib/documents/converters.js +149 -0
- package/lib/documents/detect.d.ts +31 -0
- package/lib/documents/detect.js +169 -0
- package/lib/documents/index.d.ts +9 -0
- package/lib/documents/index.js +43 -0
- package/lib/documents/mime.d.ts +39 -0
- package/lib/documents/mime.js +100 -0
- package/lib/documents/plugins.d.ts +18 -0
- package/lib/documents/plugins.js +65 -0
- package/lib/documents/readers.d.ts +19 -0
- package/lib/documents/readers.js +64 -0
- package/lib/documents/routes.d.ts +52 -0
- package/lib/documents/routes.js +99 -0
- package/lib/documents/types.d.ts +126 -0
- package/lib/documents/types.js +2 -0
- package/lib/documents/uagJs.d.ts +17 -0
- package/lib/documents/uagJs.js +157 -0
- package/lib/formflow.d.ts +47 -0
- package/lib/formflow.js +28 -0
- package/lib/identity.d.ts +53 -0
- package/lib/identity.js +107 -0
- package/lib/index.d.ts +98 -1
- package/lib/index.js +382 -8
- package/lib/markdown.d.ts +1 -0
- package/lib/markdown.js +166 -0
- package/lib/modules/api.d.ts +25 -0
- package/lib/modules/api.js +98 -0
- package/lib/modules/index.d.ts +4 -0
- package/lib/modules/index.js +24 -0
- package/lib/modules/routes.d.ts +14 -0
- package/lib/modules/routes.js +21 -0
- package/lib/modules/types.d.ts +68 -0
- package/lib/modules/types.js +5 -0
- package/lib/pluginFiles.d.ts +28 -0
- package/lib/pluginFiles.js +101 -0
- package/lib/plugins.d.ts +17 -0
- package/lib/plugins.js +93 -0
- package/lib/richText.d.ts +20 -0
- package/lib/richText.js +99 -0
- package/lib/router.d.ts +9 -0
- package/lib/router.js +34 -25
- package/lib/runner.d.ts +149 -0
- package/lib/runner.js +353 -0
- package/lib/runs.d.ts +27 -0
- package/lib/runs.js +13 -0
- package/lib/template.js +7 -9
- package/lib/templates/uagComponentNotFound.md +5 -5
- package/lib/testing.d.ts +74 -0
- package/lib/testing.js +180 -0
- package/lib/tools/SchemaBuilder.d.ts +3 -3
- package/lib/tools/SchemaBuilder.js +4 -4
- package/lib/tools/agentDecide.d.ts +8 -0
- package/lib/tools/agentDecide.js +57 -0
- package/lib/tools/agentProvide.d.ts +8 -0
- package/lib/tools/agentProvide.js +97 -0
- package/lib/tools/collectData.js +3 -2
- package/lib/tools/confirmSubmission.js +1 -1
- package/lib/tools/fetchExternalData.js +8 -17
- package/lib/tools/findSubmission.js +62 -56
- package/lib/tools/getFieldInfo.js +1 -1
- package/lib/tools/getFormFields.js +1 -1
- package/lib/tools/getForms.js +1 -1
- package/lib/tools/index.d.ts +4 -1
- package/lib/tools/index.js +21 -14
- package/lib/tools/readDocument.d.ts +17 -0
- package/lib/tools/readDocument.js +99 -0
- package/lib/tools/submissionUpdate.js +4 -3
- package/lib/tools/submitForm.js +1 -1
- package/lib/tools/utils.d.ts +40 -0
- package/lib/tools/utils.js +16 -1
- package/lib/version.d.ts +2 -0
- package/lib/version.js +5 -0
- package/package.json +49 -19
- package/testing.d.ts +1 -0
- package/testing.js +2 -0
- package/lib/tools/agentProvideData.d.ts +0 -3
- package/lib/tools/agentProvideData.js +0 -62
package/lib/UAGFormInterface.js
CHANGED
|
@@ -2,34 +2,92 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UAGFormInterface = void 0;
|
|
4
4
|
const appserver_1 = require("@formio/appserver");
|
|
5
|
+
const formflow_1 = require("./formflow");
|
|
6
|
+
const richText_1 = require("./richText");
|
|
5
7
|
const core_1 = require("@formio/core");
|
|
6
8
|
const lodash_1 = require("lodash");
|
|
7
9
|
const node_html_markdown_1 = require("node-html-markdown");
|
|
10
|
+
const router_1 = require("./agent/router");
|
|
11
|
+
/** Component types a chat cannot collect yet: tables, nested forms, uploads, drawings and the like. */
|
|
12
|
+
const CHAT_UNSUPPORTED = new Set([
|
|
13
|
+
'datagrid', 'editgrid', 'datatable', 'dynamicWizard', 'tagpad', 'form', 'datamap', 'address',
|
|
14
|
+
'file', 'signature', 'sketchpad', 'datasource', 'password', 'captcha'
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* The components inside a layout or container flagged with `uag`, which makes
|
|
18
|
+
* each input inside it one of the task's fields. Panels, fieldsets, wells, tabs
|
|
19
|
+
* and containers hold theirs in `components`; columns in each column's
|
|
20
|
+
* `components`; tables in each cell's. Null for a component that holds none.
|
|
21
|
+
*/
|
|
22
|
+
function layoutChildren(component) {
|
|
23
|
+
const comp = component;
|
|
24
|
+
if (Array.isArray(comp.components))
|
|
25
|
+
return comp.components;
|
|
26
|
+
if (Array.isArray(comp.columns))
|
|
27
|
+
return comp.columns.flatMap((column) => column?.components || []);
|
|
28
|
+
if (Array.isArray(comp.rows))
|
|
29
|
+
return comp.rows.flat().flatMap((cell) => cell?.components || []);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
8
32
|
class UAGFormInterface extends appserver_1.FormInterface {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The form's routes, with its agent operations (`agent/extract`, `agent/decide`,
|
|
35
|
+
* `agent/generate`, `agent/chat`) beside its submissions: the project's authentication has
|
|
36
|
+
* already run, and the form is the one of the URL.
|
|
37
|
+
*/
|
|
38
|
+
async router() {
|
|
39
|
+
const router = await super.router();
|
|
40
|
+
const project = this.project;
|
|
41
|
+
router.use((0, router_1.FormAgentRouter)(project, (0, router_1.agentCredentials)(project)));
|
|
42
|
+
return router;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sets the form, deriving its UAG registration and agent fields from it. The form is set again
|
|
46
|
+
* whenever it loads, and whenever a TTL refresh or a deploy brings a new version, so both are
|
|
47
|
+
* rebuilt from the form each time rather than added to what an earlier version left.
|
|
48
|
+
*/
|
|
49
|
+
setForm(form) {
|
|
12
50
|
this.uagFields = {};
|
|
51
|
+
const result = super.setForm(form);
|
|
52
|
+
const machineName = result.machineName;
|
|
53
|
+
this.uag = result.tags?.includes('uag') ? {
|
|
54
|
+
machineName,
|
|
55
|
+
name: result.name || result.path || machineName,
|
|
56
|
+
title: result.title || result.name || machineName,
|
|
57
|
+
description: result.properties?.description || `A form to submit new ${result.title} records.`
|
|
58
|
+
} : null;
|
|
59
|
+
return result;
|
|
13
60
|
}
|
|
14
61
|
setComponent(form, component, path, components, parent, paths) {
|
|
15
62
|
if (form.tags?.includes('uag') && component.properties?.uag) {
|
|
16
|
-
const
|
|
63
|
+
const task = component.properties?.uag;
|
|
17
64
|
if (!this.uagFields) {
|
|
18
65
|
this.uagFields = {};
|
|
19
66
|
}
|
|
20
|
-
let uagFields = this.uagFields[
|
|
67
|
+
let uagFields = this.uagFields[task];
|
|
21
68
|
if (!uagFields) {
|
|
22
|
-
uagFields = { components: {} };
|
|
69
|
+
uagFields = { task, components: {} };
|
|
23
70
|
}
|
|
24
71
|
if (component.properties?.uagField) {
|
|
25
72
|
const property = component.properties?.uagField;
|
|
26
|
-
if (
|
|
73
|
+
if (property === 'input') {
|
|
74
|
+
// An input can be a single field or a nested component; for a
|
|
75
|
+
// nested one, every child value under this path is input.
|
|
76
|
+
const inputPath = paths?.dataPath || component.key;
|
|
77
|
+
uagFields.inputs = [...(uagFields.inputs || []), inputPath];
|
|
78
|
+
}
|
|
79
|
+
else if (component?.html) {
|
|
27
80
|
uagFields[property] = this.htmlToMarkdown(component.html);
|
|
28
81
|
}
|
|
82
|
+
if (property === 'criteria') {
|
|
83
|
+
const { uag, uagField, ...settings } = component.properties || {};
|
|
84
|
+
uagFields.settings = { ...(uagFields.settings || {}), ...settings };
|
|
85
|
+
}
|
|
29
86
|
}
|
|
30
87
|
else {
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
const children = layoutChildren(component);
|
|
89
|
+
if (children) {
|
|
90
|
+
core_1.Utils.eachComponent(children, (subComp, subPath) => {
|
|
33
91
|
subPath = paths?.dataPath ? `${paths.dataPath}.${subPath}` : subPath;
|
|
34
92
|
const compInfo = this.getComponentInfo(subComp, subPath);
|
|
35
93
|
compInfo.rule = this.getComponentValueRule(subComp);
|
|
@@ -42,7 +100,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
42
100
|
uagFields.components[compInfo.path] = compInfo;
|
|
43
101
|
}
|
|
44
102
|
}
|
|
45
|
-
this.uagFields[
|
|
103
|
+
this.uagFields[task] = uagFields;
|
|
46
104
|
}
|
|
47
105
|
return super.setComponent(form, component, path, components, parent, paths);
|
|
48
106
|
}
|
|
@@ -72,7 +130,8 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
72
130
|
if (component.placeholder) {
|
|
73
131
|
fieldInfo.prompt = component.placeholder;
|
|
74
132
|
}
|
|
75
|
-
|
|
133
|
+
// A radio's options are what its rule tells the agent to choose from.
|
|
134
|
+
if (component.type === 'select' || component.type === 'selectboxes' || component.type === 'radio') {
|
|
76
135
|
if (component.dataSrc === 'url') {
|
|
77
136
|
fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve available options from the external URL.` }];
|
|
78
137
|
}
|
|
@@ -88,10 +147,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
88
147
|
fieldInfo.options = [{ label: '', value: 'No options available' }];
|
|
89
148
|
}
|
|
90
149
|
else {
|
|
91
|
-
fieldInfo.options = values.
|
|
92
|
-
acc.push({ label: v.label, value: v.value });
|
|
93
|
-
return acc;
|
|
94
|
-
}, []);
|
|
150
|
+
fieldInfo.options = values.map((v) => ({ label: v.label, value: v.value }));
|
|
95
151
|
}
|
|
96
152
|
}
|
|
97
153
|
}
|
|
@@ -114,19 +170,18 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
114
170
|
label: parent.label || parent.key,
|
|
115
171
|
data_path: parent_path
|
|
116
172
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
parent.type === 'editgrid') {
|
|
120
|
-
parentInfo.isTable = true;
|
|
121
|
-
return parentInfo;
|
|
122
|
-
}
|
|
173
|
+
// The type comes first: the builder saves containers with `tree: true`
|
|
174
|
+
// too, and a container holds one object, not rows.
|
|
123
175
|
if (parent.type === 'form') {
|
|
124
176
|
parentInfo.isForm = true;
|
|
125
|
-
return parentInfo;
|
|
126
177
|
}
|
|
127
|
-
if (parent.type === 'container') {
|
|
178
|
+
else if (parent.type === 'container') {
|
|
128
179
|
parentInfo.isContainer = true;
|
|
129
|
-
|
|
180
|
+
}
|
|
181
|
+
else if (parent.tree ||
|
|
182
|
+
parent.type === 'datagrid' ||
|
|
183
|
+
parent.type === 'editgrid') {
|
|
184
|
+
parentInfo.isTable = true;
|
|
130
185
|
}
|
|
131
186
|
return parentInfo;
|
|
132
187
|
}
|
|
@@ -163,20 +218,17 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
163
218
|
return `To determine what fields are within this component, use the \`get_form_fields\` tool with \`parent_path\`="<data_path>" (e.g. \`parent_path\`="${parent.data_path}"). To collect data for this component, use the \`collect_field_data\` tool with the \`parent_path\` set for the parent's data_path.`;
|
|
164
219
|
}
|
|
165
220
|
getComponentValueRule(component, data_path) {
|
|
166
|
-
let rule = '';
|
|
167
221
|
const parent = this.getParentInfoFromComponent(component, data_path);
|
|
168
222
|
if (parent?.isTable) {
|
|
169
|
-
|
|
170
|
-
return rule;
|
|
223
|
+
return 'The value is a table of rows (array of objects), where each row is a new instance of data values for the child components. ' + this.getParentToolDescription(parent) + ' All `data_path`(s) for the components within this table should contain the current row index (e.g. `dataGrid[0].a`, `dataGrid[0].b`, `dataGrid[1].b`, etc.).';
|
|
171
224
|
}
|
|
172
225
|
if (parent?.isForm) {
|
|
173
|
-
|
|
174
|
-
return rule;
|
|
226
|
+
return 'The value is a nested form submission in the format `{data: {...}}` where `{...}` is the values for the child components. ' + this.getParentToolDescription(parent) + ' All `data_path`(s) for the components within this nested form should be prefixed with `data` (e.g. `nestedForm.data.exampleField`).';
|
|
175
227
|
}
|
|
176
228
|
if (parent?.isContainer) {
|
|
177
|
-
|
|
178
|
-
return rule;
|
|
229
|
+
return 'The value is an object/map of nested component values in the format `{...}`. ' + this.getParentToolDescription(parent) + ' All `data_path`(s) for the components within this container should be prefixed with the container\'s `data_path` (e.g. `container.exampleField`).';
|
|
179
230
|
}
|
|
231
|
+
let rule = '';
|
|
180
232
|
switch (component.type) {
|
|
181
233
|
case 'tags':
|
|
182
234
|
rule += 'The value can be any alphanumeric string, containing letters, numbers, but no white space characters or symbols. Multiple tags should be comma-separated.';
|
|
@@ -184,8 +236,12 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
184
236
|
case 'signature':
|
|
185
237
|
rule += 'Have the user draw their signature. The value will be a base64-encoded PNG image string of that signature.';
|
|
186
238
|
break;
|
|
187
|
-
case 'textfield':
|
|
188
239
|
case 'textarea':
|
|
240
|
+
rule += (0, richText_1.isRichText)(component)
|
|
241
|
+
? richText_1.RICH_TEXT_RULE
|
|
242
|
+
: 'The value can be any alphanumeric string, containing letters, numbers, white space characters, and common symbols.';
|
|
243
|
+
break;
|
|
244
|
+
case 'textfield':
|
|
189
245
|
case 'hidden':
|
|
190
246
|
rule += 'The value can be any alphanumeric string, containing letters, numbers, white space characters, and common symbols.';
|
|
191
247
|
break;
|
|
@@ -245,8 +301,6 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
245
301
|
rule += `\n - **${value.label}** (${value.value})`;
|
|
246
302
|
});
|
|
247
303
|
break;
|
|
248
|
-
default:
|
|
249
|
-
rule += '';
|
|
250
304
|
}
|
|
251
305
|
return rule;
|
|
252
306
|
}
|
|
@@ -259,13 +313,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
259
313
|
* @returns { boolean } - True if the component is a nested data component, false otherwise.
|
|
260
314
|
*/
|
|
261
315
|
isNestedComponent(component) {
|
|
262
|
-
|
|
316
|
+
return !!component.tree ||
|
|
263
317
|
component.type === 'datagrid' ||
|
|
264
318
|
component.type === 'editgrid' ||
|
|
265
|
-
component.type === 'form'
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
return false;
|
|
319
|
+
component.type === 'form';
|
|
269
320
|
}
|
|
270
321
|
/**
|
|
271
322
|
* Determine if the component is a non-input component that should be skipped when collecting fields.
|
|
@@ -304,12 +355,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
304
355
|
* Override the process methods for the UAG.
|
|
305
356
|
*/
|
|
306
357
|
async process(submission, auth, headers = {}, findQuery = {}, additional = []) {
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
processors.splice(fetchIndex, 1);
|
|
312
|
-
}
|
|
358
|
+
// Leave out 'fetch', since the UAG handles it with the fetch_external_data tool, and
|
|
359
|
+
// 'dereference', which expands a "Save as reference" select or a Data Table from the
|
|
360
|
+
// database the UAG does not have: it would throw for every form with one.
|
|
361
|
+
const processors = [...core_1.Processors, ...additional].filter((p) => p.name !== 'fetch' && p.name !== 'dereference');
|
|
313
362
|
// Return the processed submission data.
|
|
314
363
|
const context = await super.process(submission, auth, headers, findQuery, processors);
|
|
315
364
|
// Filter the "required" validation since the UAG retrieves required fields separately.
|
|
@@ -374,19 +423,19 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
374
423
|
return;
|
|
375
424
|
}
|
|
376
425
|
if (typeof within === 'string') {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
426
|
+
// Only paths below `within`: "customerNotes" is not within "customer".
|
|
427
|
+
const rest = path.slice(within.length);
|
|
428
|
+
if (path.startsWith(within) && (rest.startsWith('.') || rest.startsWith('['))) {
|
|
429
|
+
// The row of `within` itself, not of a table it sits in
|
|
430
|
+
// ("outer[0].inner[3].x" is row 3 of "outer[0].inner").
|
|
431
|
+
const indexMatch = rest.match(/^\[(\d+)\]/);
|
|
432
|
+
fieldInfo.rowIndex = indexMatch ? parseInt(indexMatch[1], 10) : 0;
|
|
381
433
|
}
|
|
382
434
|
else {
|
|
435
|
+
// If within is a string, then it is a single path to limit components within (non-inclusive).
|
|
383
436
|
return;
|
|
384
437
|
}
|
|
385
438
|
}
|
|
386
|
-
// If within is a string, then it is a single path to limit components within (non-inclusive).
|
|
387
|
-
if ((typeof within === 'string') && (!path.startsWith(within) || (path === within))) {
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
439
|
}
|
|
391
440
|
// If this is a component within the nested path, then skip it...
|
|
392
441
|
else if (nestedPath && path.startsWith(nestedPath) && (path !== nestedPath)) {
|
|
@@ -412,7 +461,13 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
412
461
|
}
|
|
413
462
|
// Add the component info to the appropriate list.
|
|
414
463
|
const criteria = component.validate?.required ? 'required' : 'optional';
|
|
415
|
-
|
|
464
|
+
// Rules are listed by type, but some depend on the component (its
|
|
465
|
+
// options source, multiple values, a survey's questions, a nested
|
|
466
|
+
// component's path): one that differs gets an entry of its own.
|
|
467
|
+
const rules = fieldInfo[criteria].rules;
|
|
468
|
+
const rule = this.getComponentValueRule(component, path);
|
|
469
|
+
const known = rules[component.type];
|
|
470
|
+
rules[(known === undefined || known === rule) ? component.type : `${component.type} (${path})`] = rule;
|
|
416
471
|
fieldInfo[criteria].components.push(this.getComponentInfo(component, path));
|
|
417
472
|
}
|
|
418
473
|
}
|
|
@@ -422,6 +477,86 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
422
477
|
}
|
|
423
478
|
return fieldInfo;
|
|
424
479
|
}
|
|
480
|
+
/** Whether the chat cannot collect this component yet, so the person finishes it in the form. */
|
|
481
|
+
isChatUnsupported(component) {
|
|
482
|
+
if (CHAT_UNSUPPORTED.has(component.type))
|
|
483
|
+
return true;
|
|
484
|
+
const dataSrc = component.dataSrc;
|
|
485
|
+
if (component.type === 'select' && (dataSrc === 'url' || dataSrc === 'resource'))
|
|
486
|
+
return true;
|
|
487
|
+
if (component.tree && component.type !== 'container')
|
|
488
|
+
return true;
|
|
489
|
+
return ['nestedArray', 'nestedDataArray', 'dataObject', 'map'].includes(core_1.Utils.getModelType(component));
|
|
490
|
+
}
|
|
491
|
+
/** Data paths of the fields a task fills: its decision and value fields, not its inputs. */
|
|
492
|
+
taskOwnedPaths() {
|
|
493
|
+
const owned = new Set();
|
|
494
|
+
for (const task of Object.values(this.uagFields || {})) {
|
|
495
|
+
Object.keys(task.components || {}).forEach((path) => owned.add(path));
|
|
496
|
+
}
|
|
497
|
+
return owned;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* The fields a chat may collect on this data: visible input fields that a
|
|
501
|
+
* person may enter, filled or not, with the rules the MCP tools give
|
|
502
|
+
* agents. Fields a task fills (and `uagAuthoritative` ones), hidden,
|
|
503
|
+
* disabled and calculated fields are left out; components the chat
|
|
504
|
+
* cannot collect yet are listed in `unsupported`, and their children are
|
|
505
|
+
* not visited.
|
|
506
|
+
*/
|
|
507
|
+
async chatFields(submission, authInfo) {
|
|
508
|
+
const result = { fields: [], unsupported: [], errors: [] };
|
|
509
|
+
const owned = this.taskOwnedPaths();
|
|
510
|
+
const skipped = [];
|
|
511
|
+
const within = (path, parent) => path.startsWith(`${parent}.`) || path.startsWith(`${parent}[`);
|
|
512
|
+
const context = await this.process(submission, authInfo, null, null, [
|
|
513
|
+
{
|
|
514
|
+
name: 'chatFields',
|
|
515
|
+
shouldProcess: () => true,
|
|
516
|
+
process: async (context) => {
|
|
517
|
+
const { component, path } = context;
|
|
518
|
+
if (this.isChatUnsupported(component) && !skipped.some((parent) => within(path, parent))) {
|
|
519
|
+
skipped.push(path);
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
postProcess: async (context) => {
|
|
523
|
+
const { component, path, value } = context;
|
|
524
|
+
if (skipped.some((parent) => within(path, parent)))
|
|
525
|
+
return;
|
|
526
|
+
if (skipped[skipped.length - 1] === path)
|
|
527
|
+
skipped.pop();
|
|
528
|
+
if (!this.inputComponent(component) || component.type === 'container')
|
|
529
|
+
return;
|
|
530
|
+
// A field inside a hidden panel or container is only marked `intentionallyHidden`.
|
|
531
|
+
if (component.scope?.conditionallyHidden || component.scope?.intentionallyHidden || component.hidden || component.type === 'hidden')
|
|
532
|
+
return;
|
|
533
|
+
const required = !!component.validate?.required;
|
|
534
|
+
const empty = !(0, core_1.componentHasValue)(component, value);
|
|
535
|
+
if (this.isChatUnsupported(component)) {
|
|
536
|
+
result.unsupported.push({ path, label: component.label || component.key || path, required, empty });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (component.disabled || component.persistent === false || component.persistent === 'client-only')
|
|
540
|
+
return;
|
|
541
|
+
if (component.calculateValue && !component.allowCalculateOverride)
|
|
542
|
+
return;
|
|
543
|
+
if (owned.has(path) || (0, formflow_1.flagOn)(component.properties?.uagAuthoritative))
|
|
544
|
+
return;
|
|
545
|
+
result.fields.push({
|
|
546
|
+
...this.getComponentInfo(component, path),
|
|
547
|
+
rule: this.getComponentValueRule(component, path),
|
|
548
|
+
required,
|
|
549
|
+
empty,
|
|
550
|
+
value
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
]);
|
|
555
|
+
if (context.scope?.errors?.length) {
|
|
556
|
+
result.errors = this.convertToFormFieldErrors((0, core_1.interpolateErrors)(context.scope.errors));
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
425
560
|
/**
|
|
426
561
|
* Return the component at the specified data path.
|
|
427
562
|
* @param path { string } - The data path of the component to retrieve.
|
|
@@ -466,7 +601,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
466
601
|
else if (value && this.isMultiple(comp) && typeof value === 'string') {
|
|
467
602
|
value = value.split(',').map((v) => v.trim());
|
|
468
603
|
}
|
|
469
|
-
(0, lodash_1.set)(submission.data, path, value);
|
|
604
|
+
(0, lodash_1.set)(submission.data, path, this.normalizeWrittenValue(path, value));
|
|
470
605
|
}
|
|
471
606
|
return submission;
|
|
472
607
|
}
|
|
@@ -491,7 +626,12 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
491
626
|
}
|
|
492
627
|
else if (this.inputComponent(component) && (0, core_1.componentHasValue)(component, value)) {
|
|
493
628
|
const modelType = core_1.Utils.getModelType(component);
|
|
494
|
-
if ((0,
|
|
629
|
+
if ((0, formflow_1.isBinaryComponent)(component)) {
|
|
630
|
+
// Files and signatures are described, never inlined: a base64 upload would fill the context.
|
|
631
|
+
const described = (0, formflow_1.describeBinary)(component, value);
|
|
632
|
+
value = (0, lodash_1.isObjectLike)(described) ? JSON.stringify(described) : described;
|
|
633
|
+
}
|
|
634
|
+
else if ((0, lodash_1.isObjectLike)(value)) {
|
|
495
635
|
value = JSON.stringify(value);
|
|
496
636
|
}
|
|
497
637
|
else if (modelType === 'string' && (0, lodash_1.isString)(value)) {
|
|
@@ -512,7 +652,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
512
652
|
}
|
|
513
653
|
}, false, false, undefined, undefined, undefined, (component, data) => {
|
|
514
654
|
if (this.isNestedComponent(component)) {
|
|
515
|
-
prefix = prefix.slice(0, -
|
|
655
|
+
prefix = prefix.slice(0, -2);
|
|
516
656
|
}
|
|
517
657
|
if (component.key === 'uag' || withinUAG) {
|
|
518
658
|
withinUAG = false;
|
|
@@ -528,6 +668,35 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
528
668
|
modified: submission.modified
|
|
529
669
|
};
|
|
530
670
|
}
|
|
671
|
+
/** The form's name in Agentic Formflow responses and messages. */
|
|
672
|
+
get formflowName() {
|
|
673
|
+
return this.uag?.name || this.form?.name || '';
|
|
674
|
+
}
|
|
675
|
+
/** The form's tasks, for Agentic Formflow. */
|
|
676
|
+
formflowTasks() {
|
|
677
|
+
return this.uagFields || {};
|
|
678
|
+
}
|
|
679
|
+
/** The submission's values in form order with their labels, for Agentic Formflow. */
|
|
680
|
+
formflowLabeledData(data = {}) {
|
|
681
|
+
return this.formatData(data).map(({ path, label }) => ({ path, label }));
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* A value an agent engine generated, before it is checked and written: HTML
|
|
685
|
+
* for a rich text field is reduced to the markup its editor keeps.
|
|
686
|
+
*/
|
|
687
|
+
formflowNormalizeValue(component, value) {
|
|
688
|
+
return (0, richText_1.isRichText)(component) && typeof value === 'string' ? (0, richText_1.sanitizeRichText)(value) : value;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* A value an MCP client writes at a data path (collect_field_data,
|
|
692
|
+
* confirm_form_submission, submit_completed_form, submission_update), as
|
|
693
|
+
* an engine's is: HTML for a rich text field is reduced to the markup its
|
|
694
|
+
* editor keeps. Row indices are ignored to find the component.
|
|
695
|
+
*/
|
|
696
|
+
normalizeWrittenValue(path, value) {
|
|
697
|
+
const component = this.getComponent(String(path).replace(/\[\d+\]/g, ''));
|
|
698
|
+
return component ? this.formflowNormalizeValue(component, value) : value;
|
|
699
|
+
}
|
|
531
700
|
htmlToMarkdown(html) {
|
|
532
701
|
return node_html_markdown_1.NodeHtmlMarkdown.translate(html, {
|
|
533
702
|
bulletMarker: '-',
|
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
import { ProjectInterface, SubmissionRequest } from "@formio/appserver";
|
|
2
|
+
import { EngineRegistry, FormflowConfig, FormflowFileReader, FormflowForm, FormflowHost, FormflowTool, ScopedTokenClaims } from "./formflow";
|
|
2
3
|
import { UAGConfig } from "./config";
|
|
3
4
|
import { Form } from "@formio/core";
|
|
4
5
|
import { NextFunction, Response, Router } from "express";
|
|
5
6
|
import { UAGFormInterface } from "./UAGFormInterface";
|
|
6
7
|
import { ResponseTemplate, UAGTemplate } from "./template";
|
|
7
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
-
|
|
9
|
+
import type { PluginRegistry } from "@formio/appserver";
|
|
10
|
+
export declare class UAGProjectInterface extends ProjectInterface implements FormflowHost {
|
|
9
11
|
user: any;
|
|
10
|
-
|
|
12
|
+
/** The names of the UAG forms the project currently has. */
|
|
13
|
+
get formNames(): string[];
|
|
11
14
|
uagTemplate: UAGTemplate | null;
|
|
15
|
+
/** Engines enabled at startup; empty until UAGServer.router() resolves them. */
|
|
16
|
+
engines: EngineRegistry;
|
|
12
17
|
mcpServer: McpServer;
|
|
13
18
|
get config(): UAGConfig;
|
|
14
19
|
constructor(endpoint?: string);
|
|
15
20
|
initialize(): Promise<void>;
|
|
16
|
-
buildMcpServer(): Promise<McpServer>;
|
|
21
|
+
buildMcpServer(scope?: Pick<ScopedTokenClaims, 'tools'>): Promise<McpServer>;
|
|
17
22
|
addForm(form: Form, machineName: string): UAGFormInterface | undefined;
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The UAG's routes: `/mcp`, and unless excluded, `/agent/capabilities`.
|
|
25
|
+
* UAGServer mounts capabilities separately, ahead of the project router. A
|
|
26
|
+
* form's agent operations are on the form's own routes (UAGFormInterface).
|
|
27
|
+
*/
|
|
28
|
+
uagRouter(includeAgentApi?: boolean): Router;
|
|
29
|
+
/** `/agent/capabilities`, behind its own authentication. */
|
|
30
|
+
agentRouter(): Router;
|
|
31
|
+
/** Whether runs after the save are recorded and survive a restart (the UAG's database). */
|
|
32
|
+
durableRuns(): boolean;
|
|
33
|
+
/** The engines enabled for Agentic Formflow. */
|
|
34
|
+
formflowEngines(): EngineRegistry;
|
|
35
|
+
/**
|
|
36
|
+
* Agentic Formflow's routing, Agent API and auth settings. The engine
|
|
37
|
+
* settings stay here: the UAG loads the engines, not core.
|
|
38
|
+
*/
|
|
39
|
+
formflowConfig(): FormflowConfig;
|
|
40
|
+
/** A form tagged "uag", or null. */
|
|
41
|
+
formflowForm(name: string): Promise<FormflowForm | null>;
|
|
42
|
+
/** The server's plugin registry, once its plugins are listed. */
|
|
43
|
+
plugins?: PluginRegistry;
|
|
44
|
+
/**
|
|
45
|
+
* Readers for uploaded documents: one built from the installed converters,
|
|
46
|
+
* which reads every type with a route to text.
|
|
47
|
+
*/
|
|
48
|
+
formflowFileReaders(): FormflowFileReader[];
|
|
49
|
+
/** Every MCP tool, as Agentic Formflow tools. */
|
|
50
|
+
formflowTools(): Promise<FormflowTool[]>;
|
|
51
|
+
authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<void | Response<any, Record<string, any>>>;
|
|
52
|
+
/**
|
|
53
|
+
* The user a Form.io token stands for, as the project's x-jwt-token
|
|
54
|
+
* authentication decides it: a token tied to a Form.io session is asked of
|
|
55
|
+
* Form.io; one signed with this UAG's secret is read from its claims, bound
|
|
56
|
+
* to this project; any other is asked of Form.io. Never an administrator:
|
|
57
|
+
* an administrator's research carries no user token.
|
|
58
|
+
*/
|
|
59
|
+
private formioTokenAuth;
|
|
20
60
|
mcpJSONResponse(data?: object, isError?: boolean): {
|
|
21
61
|
content: {
|
|
22
62
|
type: 'text';
|