@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.
Files changed (115) hide show
  1. package/README.md +237 -535
  2. package/bin/uag-eval.js +99 -0
  3. package/bin/uag.js +116 -0
  4. package/lib/UAGFormInterface.d.ts +72 -2
  5. package/lib/UAGFormInterface.js +226 -57
  6. package/lib/UAGProjectInterface.d.ts +45 -5
  7. package/lib/UAGProjectInterface.js +187 -36
  8. package/lib/actionLogs.d.ts +24 -0
  9. package/lib/actionLogs.js +149 -0
  10. package/lib/actions/runTask.d.ts +43 -0
  11. package/lib/actions/runTask.js +324 -0
  12. package/lib/agent/anonymous.d.ts +34 -0
  13. package/lib/agent/anonymous.js +99 -0
  14. package/lib/agent/chat.d.ts +114 -0
  15. package/lib/agent/chat.js +475 -0
  16. package/lib/agent/engines.d.ts +17 -0
  17. package/lib/agent/engines.js +46 -0
  18. package/lib/agent/errors.d.ts +25 -0
  19. package/lib/agent/errors.js +132 -0
  20. package/lib/agent/index.d.ts +4 -0
  21. package/lib/agent/index.js +19 -0
  22. package/lib/agent/remote.d.ts +71 -0
  23. package/lib/agent/remote.js +252 -0
  24. package/lib/agent/requestId.d.ts +4 -0
  25. package/lib/agent/requestId.js +19 -0
  26. package/lib/agent/router.d.ts +61 -0
  27. package/lib/agent/router.js +307 -0
  28. package/lib/agent/stubs.d.ts +37 -0
  29. package/lib/agent/stubs.js +39 -0
  30. package/lib/agent/tools.d.ts +31 -0
  31. package/lib/agent/tools.js +76 -0
  32. package/lib/config.d.ts +97 -3
  33. package/lib/config.js +19 -0
  34. package/lib/database.d.ts +70 -0
  35. package/lib/database.js +212 -0
  36. package/lib/documents/convert.d.ts +38 -0
  37. package/lib/documents/convert.js +151 -0
  38. package/lib/documents/converters.d.ts +28 -0
  39. package/lib/documents/converters.js +149 -0
  40. package/lib/documents/detect.d.ts +31 -0
  41. package/lib/documents/detect.js +169 -0
  42. package/lib/documents/index.d.ts +9 -0
  43. package/lib/documents/index.js +43 -0
  44. package/lib/documents/mime.d.ts +39 -0
  45. package/lib/documents/mime.js +100 -0
  46. package/lib/documents/plugins.d.ts +18 -0
  47. package/lib/documents/plugins.js +65 -0
  48. package/lib/documents/readers.d.ts +19 -0
  49. package/lib/documents/readers.js +64 -0
  50. package/lib/documents/routes.d.ts +52 -0
  51. package/lib/documents/routes.js +99 -0
  52. package/lib/documents/types.d.ts +126 -0
  53. package/lib/documents/types.js +2 -0
  54. package/lib/documents/uagJs.d.ts +17 -0
  55. package/lib/documents/uagJs.js +157 -0
  56. package/lib/formflow.d.ts +47 -0
  57. package/lib/formflow.js +28 -0
  58. package/lib/identity.d.ts +53 -0
  59. package/lib/identity.js +107 -0
  60. package/lib/index.d.ts +98 -1
  61. package/lib/index.js +382 -8
  62. package/lib/markdown.d.ts +1 -0
  63. package/lib/markdown.js +166 -0
  64. package/lib/modules/api.d.ts +25 -0
  65. package/lib/modules/api.js +98 -0
  66. package/lib/modules/index.d.ts +4 -0
  67. package/lib/modules/index.js +24 -0
  68. package/lib/modules/routes.d.ts +14 -0
  69. package/lib/modules/routes.js +21 -0
  70. package/lib/modules/types.d.ts +68 -0
  71. package/lib/modules/types.js +5 -0
  72. package/lib/pluginFiles.d.ts +28 -0
  73. package/lib/pluginFiles.js +101 -0
  74. package/lib/plugins.d.ts +17 -0
  75. package/lib/plugins.js +93 -0
  76. package/lib/richText.d.ts +20 -0
  77. package/lib/richText.js +99 -0
  78. package/lib/router.d.ts +9 -0
  79. package/lib/router.js +34 -25
  80. package/lib/runner.d.ts +149 -0
  81. package/lib/runner.js +353 -0
  82. package/lib/runs.d.ts +27 -0
  83. package/lib/runs.js +13 -0
  84. package/lib/template.js +7 -9
  85. package/lib/templates/uagComponentNotFound.md +5 -5
  86. package/lib/testing.d.ts +74 -0
  87. package/lib/testing.js +180 -0
  88. package/lib/tools/SchemaBuilder.d.ts +3 -3
  89. package/lib/tools/SchemaBuilder.js +4 -4
  90. package/lib/tools/agentDecide.d.ts +8 -0
  91. package/lib/tools/agentDecide.js +57 -0
  92. package/lib/tools/agentProvide.d.ts +8 -0
  93. package/lib/tools/agentProvide.js +97 -0
  94. package/lib/tools/collectData.js +3 -2
  95. package/lib/tools/confirmSubmission.js +1 -1
  96. package/lib/tools/fetchExternalData.js +8 -17
  97. package/lib/tools/findSubmission.js +62 -56
  98. package/lib/tools/getFieldInfo.js +1 -1
  99. package/lib/tools/getFormFields.js +1 -1
  100. package/lib/tools/getForms.js +1 -1
  101. package/lib/tools/index.d.ts +4 -1
  102. package/lib/tools/index.js +21 -14
  103. package/lib/tools/readDocument.d.ts +17 -0
  104. package/lib/tools/readDocument.js +99 -0
  105. package/lib/tools/submissionUpdate.js +4 -3
  106. package/lib/tools/submitForm.js +1 -1
  107. package/lib/tools/utils.d.ts +40 -0
  108. package/lib/tools/utils.js +16 -1
  109. package/lib/version.d.ts +2 -0
  110. package/lib/version.js +5 -0
  111. package/package.json +49 -19
  112. package/testing.d.ts +1 -0
  113. package/testing.js +2 -0
  114. package/lib/tools/agentProvideData.d.ts +0 -3
  115. package/lib/tools/agentProvideData.js +0 -62
@@ -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
- constructor() {
10
- super(...arguments);
11
- this.uag = null;
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 persona = component.properties?.uag;
63
+ const task = component.properties?.uag;
17
64
  if (!this.uagFields) {
18
65
  this.uagFields = {};
19
66
  }
20
- let uagFields = this.uagFields[persona];
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 (component?.html) {
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
- if (component.components) {
32
- core_1.Utils.eachComponent(component.components, (subComp, subPath) => {
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[persona] = 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
- if (component.type === 'select' || component.type === 'selectboxes') {
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.reduce((acc, v) => {
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
- if (parent.tree ||
118
- parent.type === 'datagrid' ||
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
- return parentInfo;
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
- rule += '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.).';
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
- rule += '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`).';
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
- rule += '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`).';
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
- if (component.tree ||
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
- const processors = [...core_1.Processors, ...additional];
308
- // Remove the 'fetch' processor since UAG handles this with the fetch_external_data tool.
309
- const fetchIndex = processors.findIndex((p) => p.name === 'fetch');
310
- if (fetchIndex !== -1) {
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
- if (path.startsWith(within) && (path !== within)) {
378
- // Make sure to set the largest index for the nested component.
379
- const indexMatch = path.match(/\[(\d+)\]/);
380
- fieldInfo.rowIndex = indexMatch?.length ? parseInt(indexMatch[indexMatch.length - 1], 10) : 0;
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
- fieldInfo[criteria].rules[component.type] = this.getComponentValueRule(component, path);
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, lodash_1.isObjectLike)(value)) {
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, -3);
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
- export declare class UAGProjectInterface extends ProjectInterface {
9
+ import type { PluginRegistry } from "@formio/appserver";
10
+ export declare class UAGProjectInterface extends ProjectInterface implements FormflowHost {
9
11
  user: any;
10
- formNames: string[];
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
- uagRouter(): Router;
19
- authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<Response<any, Record<string, any>> | undefined>;
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';