@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
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConverterRoutes = exports.stepName = void 0;
4
+ const mime_1 = require("./mime");
5
+ const detect_1 = require("./detect");
6
+ /** A converter as `name@version`, as results record routes. */
7
+ const stepName = (converter) => `${converter.name}@${converter.version}`;
8
+ exports.stepName = stepName;
9
+ /**
10
+ * Routes from source types to representations over a set of converters.
11
+ * When several converters can do a step, the component's pin wins, then the
12
+ * operator's `documents.prefer`, then the highest rank, then the name.
13
+ */
14
+ class ConverterRoutes {
15
+ constructor(converters, prefer = {}) {
16
+ this.converters = converters;
17
+ this.prefer = prefer;
18
+ }
19
+ /** The converter for one step from `source` to `target`, or undefined when none can do it. */
20
+ choose(source, target, pins = {}) {
21
+ const candidates = this.converters.filter((converter) => converter.produces.includes(target) && converter.accepts.some((accept) => accept.type === source));
22
+ if (!candidates.length)
23
+ return undefined;
24
+ for (const preferred of [pins[source], this.prefer[source]]) {
25
+ const found = preferred && candidates.find((converter) => converter.name === preferred);
26
+ if (found)
27
+ return found;
28
+ }
29
+ return [...candidates].sort((a, b) => ((b.rank || 0) - (a.rank || 0)) || a.name.localeCompare(b.name))[0];
30
+ }
31
+ /** The steps from `source` to a PDF: [] for a PDF, one converter otherwise, or null. */
32
+ toPdf(source, pins = {}) {
33
+ if (source === mime_1.MIME.pdf)
34
+ return [];
35
+ const converter = this.choose(source, 'pdf', pins);
36
+ return converter ? [converter] : null;
37
+ }
38
+ /** The steps from `source` to text: one converter, or to a PDF and then its text; null when none. */
39
+ toText(source, pins = {}) {
40
+ const direct = this.choose(source, 'text', pins);
41
+ if (direct)
42
+ return [direct];
43
+ const pdf = this.toPdf(source, pins);
44
+ const fromPdf = pdf && this.choose(mime_1.MIME.pdf, 'text', pins);
45
+ return pdf && fromPdf ? [...pdf, fromPdf] : null;
46
+ }
47
+ /**
48
+ * The first of a plugin's input sets every representation of which the
49
+ * file can reach. With both PDF and text, the text is always that same
50
+ * PDF's text, so content made from the PDF is checked against it.
51
+ */
52
+ route(source, inputs, pins = {}) {
53
+ for (const set of inputs) {
54
+ if (set.includes('pdf')) {
55
+ const pdf = this.toPdf(source, pins);
56
+ if (!pdf)
57
+ continue;
58
+ if (!set.includes('text'))
59
+ return { inputs: set, pdf };
60
+ const fromPdf = this.choose(mime_1.MIME.pdf, 'text', pins);
61
+ if (!fromPdf)
62
+ continue;
63
+ return { inputs: set, pdf, text: [...pdf, fromPdf] };
64
+ }
65
+ if (set.includes('text')) {
66
+ const text = this.toText(source, pins);
67
+ if (text)
68
+ return { inputs: set, text };
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /** Every source type a converter reads, and PDF. */
74
+ sourceTypes() {
75
+ return [...new Set([mime_1.MIME.pdf, ...mime_1.TEXT_TYPES.filter((type) => this.converters.some((c) => c.accepts.some((a) => a.type === type))),
76
+ ...this.converters.flatMap((converter) => converter.accepts.map((accept) => accept.type))])];
77
+ }
78
+ /** The source types a plugin with these inputs can take, with their routes: its manifest's `accept`. */
79
+ accept(inputs) {
80
+ const entries = [];
81
+ for (const type of this.sourceTypes()) {
82
+ const route = this.route(type, inputs);
83
+ if (!route)
84
+ continue;
85
+ entries.push({
86
+ type,
87
+ ext: (0, detect_1.extensionsOf)(type, this.converters),
88
+ routes: {
89
+ inputs: route.inputs,
90
+ ...(route.pdf && { pdf: route.pdf.map((c) => c.name) }),
91
+ ...(route.text && { text: route.text.map((c) => c.name) })
92
+ },
93
+ converters: this.converters.filter((c) => c.accepts.some((a) => a.type === type)).map((c) => c.name)
94
+ });
95
+ }
96
+ return entries;
97
+ }
98
+ }
99
+ exports.ConverterRoutes = ConverterRoutes;
@@ -0,0 +1,126 @@
1
+ import type { Plugin, PluginContext } from '@formio/appserver';
2
+ import type { GenerateRequest, GenerateResponse } from '@formio/uag-sdk';
3
+ export type Representation = 'pdf' | 'text';
4
+ export type TextFormat = 'plain' | 'markdown' | 'csv';
5
+ /** Plugin type `converter` (apiVersion 1, experimental): a file into a PDF or text. */
6
+ export interface Converter extends Plugin {
7
+ type: 'converter';
8
+ /** MIME types (canonical) and extensions it reads. */
9
+ accepts: Array<{
10
+ type: string;
11
+ ext?: string[];
12
+ }>;
13
+ produces: Representation[];
14
+ /** Default 0; the higher wins when several converters can do a step. */
15
+ rank?: number;
16
+ /** A canonical type for formats the shared detection does not know, or null. */
17
+ detect?(head: Buffer, hint: {
18
+ name: string;
19
+ type: string;
20
+ }): string | null;
21
+ /** Defaults: 25 MB, 120000 ms, 2 at a time. */
22
+ limits?: {
23
+ maxBytes?: number;
24
+ timeoutMs?: number;
25
+ concurrency?: number;
26
+ };
27
+ convert(input: {
28
+ buffer: Buffer;
29
+ type: string;
30
+ name: string;
31
+ }, options: {
32
+ to: Representation;
33
+ settings: Record<string, any>;
34
+ signal: AbortSignal;
35
+ tmpdir: string;
36
+ log: PluginContext['log'];
37
+ }): Promise<ConverterOutput>;
38
+ }
39
+ export type ConverterOutput = {
40
+ pdf: Buffer;
41
+ pages?: number;
42
+ warnings?: string[];
43
+ } | {
44
+ text: string;
45
+ textFormat: TextFormat;
46
+ pages?: Array<{
47
+ page: number;
48
+ text: string;
49
+ }>;
50
+ warnings?: string[];
51
+ };
52
+ export type DocumentFile = {
53
+ name: string;
54
+ type: string;
55
+ size: number;
56
+ sha256: string;
57
+ document: {
58
+ pdf?: Buffer;
59
+ pages?: number;
60
+ text?: string;
61
+ textFormat?: string;
62
+ textPages?: Array<{
63
+ page: number;
64
+ text: string;
65
+ }>;
66
+ };
67
+ /** The converters it went through, as name@version. */
68
+ route: string[];
69
+ original(): Promise<Buffer>;
70
+ };
71
+ export type DocumentContext = {
72
+ files: DocumentFile[];
73
+ /** Markdown. */
74
+ instructions: string;
75
+ /** This component's plugin settings, defaults applied. */
76
+ settings: Record<string, any>;
77
+ /** Operator settings. */
78
+ config: Record<string, any>;
79
+ form: {
80
+ id: string;
81
+ path: string;
82
+ title: string;
83
+ };
84
+ component: {
85
+ key: string;
86
+ path: string;
87
+ label: string;
88
+ };
89
+ /** Submission data sent with the request: context only, untrusted. */
90
+ data: Record<string, any>;
91
+ agent: {
92
+ generate(request: Omit<GenerateRequest, 'signal'>): Promise<GenerateResponse>;
93
+ };
94
+ progress(fraction: number, message?: string): void;
95
+ signal: AbortSignal;
96
+ log: PluginContext['log'];
97
+ };
98
+ export type Issue = {
99
+ kind: string;
100
+ message: string;
101
+ output?: string;
102
+ document?: string;
103
+ page?: number;
104
+ file?: string;
105
+ };
106
+ export type DocumentResult = {
107
+ content: string;
108
+ status: 'ok' | 'flagged';
109
+ issues?: Issue[];
110
+ summary?: string;
111
+ };
112
+ /** Plugin type `document` (apiVersion 1, experimental): files and instructions into content. */
113
+ export interface DocumentPlugin extends Plugin {
114
+ type: 'document';
115
+ /** Acceptable representation sets, in order of preference. */
116
+ inputs: Representation[][];
117
+ /** JSON only; keys are relative to the plugin's settings object. */
118
+ editForm?: any[];
119
+ defaults?: Record<string, any>;
120
+ /** May people edit the content? Default false. */
121
+ editable?: boolean;
122
+ /** Default 'markdown'. */
123
+ output?: 'markdown' | 'text';
124
+ validateSettings?(settings: Record<string, any>): string[];
125
+ process(ctx: DocumentContext): Promise<DocumentResult>;
126
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import { Router } from 'express';
2
+ import type { PluginRegistry, ServedComponent } from '@formio/appserver';
3
+ /**
4
+ * `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
5
+ * version in the path: the portal remembers its one-time prompt by URL):
6
+ *
7
+ * - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
8
+ * - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
9
+ * - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
10
+ * - `/uag-js/uag-js.css`: the stylesheet.
11
+ *
12
+ * The modules' components come after: each script after the registration in
13
+ * `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
14
+ * `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
15
+ * They are read on every request, as the bundle is, so the ETag follows them.
16
+ */
17
+ export declare function uagJsRouter(registry: () => PluginRegistry | undefined, components?: () => ServedComponent[]): Router;
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uagJsRouter = uagJsRouter;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const vm_1 = __importDefault(require("vm"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const crypto_1 = require("crypto");
11
+ const express_1 = require("express");
12
+ /** Where @formio/uag-js is installed: as a package, or the UAG's own workspace copy. */
13
+ function uagJsDirectory() {
14
+ try {
15
+ return path_1.default.dirname(require.resolve('@formio/uag-js/package.json'));
16
+ }
17
+ catch {
18
+ const local = path_1.default.resolve(__dirname, '..', '..', 'packages', 'uag-js');
19
+ return fs_1.default.existsSync(path_1.default.join(local, 'index.js')) ? local : undefined;
20
+ }
21
+ }
22
+ const read = (file) => fs_1.default.readFileSync(path_1.default.join(uagJsDirectory() || '', file), 'utf8');
23
+ /** A chained script or stylesheet URL from `?chain=`, when it is an absolute URL of the allowed schemes. */
24
+ function chainOf(req, schemes) {
25
+ const chain = typeof req.query.chain === 'string' ? req.query.chain : '';
26
+ if (!chain)
27
+ return undefined;
28
+ try {
29
+ const url = new URL(chain);
30
+ return schemes.includes(url.protocol) ? url.href : undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ /**
37
+ * The script that registers the bundle once formio.js is on the page: with
38
+ * this UAG's URL (from the script's own src) and the manifest, and in portal
39
+ * mode for the portal script, then loads the chained script over https only.
40
+ */
41
+ function registration(manifest, options) {
42
+ return `
43
+ ;(function () {
44
+ var manifest = ${JSON.stringify(manifest).replace(/</g, '\\u003c')};
45
+ var script = document.currentScript;
46
+ var src = script && script.src ? new URL(script.src, location.href) : null;
47
+ var uagUrl = src ? src.href.slice(0, src.href.indexOf('/uag-js/')) : location.origin;
48
+ function register() {
49
+ var Formio = window.Formio;
50
+ var lib = window.FormioUAG;
51
+ if (!Formio || !lib || typeof Formio.use !== 'function') return false;
52
+ Formio.use(lib.UAG(uagUrl, { ${options.portal ? 'portal: true, ' : ''}manifest: manifest }));
53
+ return true;
54
+ }
55
+ if (!register()) {
56
+ var tries = 0;
57
+ var timer = setInterval(function () { if (register() || ++tries > 600) clearInterval(timer); }, 50);
58
+ }
59
+ ${options.chain ? `var chain = document.createElement('script');
60
+ chain.src = ${JSON.stringify(options.chain)};
61
+ document.head.appendChild(chain);` : ''}
62
+ })();
63
+ `;
64
+ }
65
+ /** Whether each component script parses as a classic browser script, by its content (checked once per content). */
66
+ const parsed = new Map();
67
+ function parsesAsScript(file, source) {
68
+ const hash = (0, crypto_1.createHash)('sha256').update(source).digest('base64url');
69
+ const known = parsed.get(file);
70
+ if (known?.hash === hash)
71
+ return known.ok;
72
+ let ok = true;
73
+ try {
74
+ // Compiled only, never run. Wrapped as the bundle wraps it, so the check matches what the browser parses.
75
+ new vm_1.default.Script(`try {\n${source}\n} catch (err) {}`, { filename: file });
76
+ }
77
+ catch {
78
+ ok = false;
79
+ console.warn(`[formio-uag] The component script ${file} is not a browser script (an ES module, or a syntax error); it is left out of uag.use.js and portal.js.`);
80
+ }
81
+ parsed.set(file, { hash, ok });
82
+ return ok;
83
+ }
84
+ /** Serve one file: public, revalidated every time (no-cache), with an ETag from its content and the manifest. */
85
+ function serve(req, res, type, body) {
86
+ const etag = `"${(0, crypto_1.createHash)('sha256').update(body).digest('base64url')}"`;
87
+ res.set({ 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-cache', ETag: etag, 'Access-Control-Allow-Origin': '*' });
88
+ if (req.headers['if-none-match'] === etag)
89
+ return res.status(304).end();
90
+ res.status(200).send(body);
91
+ }
92
+ /**
93
+ * `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
94
+ * version in the path: the portal remembers its one-time prompt by URL):
95
+ *
96
+ * - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
97
+ * - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
98
+ * - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
99
+ * - `/uag-js/uag-js.css`: the stylesheet.
100
+ *
101
+ * The modules' components come after: each script after the registration in
102
+ * `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
103
+ * `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
104
+ * They are read on every request, as the bundle is, so the ETag follows them.
105
+ */
106
+ function uagJsRouter(registry, components = () => []) {
107
+ const router = (0, express_1.Router)();
108
+ const manifest = () => {
109
+ const plugins = registry();
110
+ // Every plugin the modules' components may read (document, converter, module, and types
111
+ // modules define): not the UAG's own engines, tools and actions, which /plugins lists.
112
+ return plugins ? plugins.manifest().filter((plugin) => !['engine', 'tool', 'action'].includes(plugin.type)) : [];
113
+ };
114
+ // Each component's script in its own try/catch, so one that fails when it runs does not stop the others.
115
+ // A script that does not parse would break the whole bundle, which try/catch cannot stop: it is left out.
116
+ const scripts = () => components().map((component) => {
117
+ const label = `The ${component.type} component of the ${component.module} module`;
118
+ const source = fs_1.default.readFileSync(component.scriptPath, 'utf8');
119
+ if (!parsesAsScript(component.scriptPath, source)) {
120
+ return `\n;/* ${component.module}: ${component.type} */\nconsole.error(${JSON.stringify(`[uag-js] ${label} is not a browser script (an ES module, or a syntax error), so it was left out.`)});\n`;
121
+ }
122
+ return `\n;/* ${component.module}: ${component.type} */\ntry {\n${source}\n} catch (err) { console.error(${JSON.stringify(`[uag-js] ${label} could not load:`)}, err); }\n`;
123
+ }).join('');
124
+ const styles = () => components().filter((component) => component.stylePath).map((component) => `\n/* ${component.module}: ${component.type} */\n${fs_1.default.readFileSync(component.stylePath, 'utf8')}`).join('');
125
+ const component = (type) => components().find((candidate) => candidate.type === type);
126
+ router.get('/uag-js/components/:file', (req, res) => {
127
+ const match = /^([a-z][a-z0-9]*)\.(js|css)$/.exec(String(req.params.file));
128
+ const found = match ? component(match[1]) : undefined;
129
+ const file = found && (match[2] === 'js' ? found.scriptPath : found.stylePath);
130
+ if (!file)
131
+ return res.status(404).type('text/plain').send('No module of this UAG has that component.');
132
+ serve(req, res, match[2] === 'js' ? 'application/javascript' : 'text/css', fs_1.default.readFileSync(file, 'utf8'));
133
+ });
134
+ const missing = (res) => res.status(404).type('text/plain').send('@formio/uag-js is not installed with this UAG.');
135
+ router.get('/uag-js/uag.use.js', (req, res) => {
136
+ if (!uagJsDirectory())
137
+ return missing(res);
138
+ serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: false }) + scripts());
139
+ });
140
+ router.get('/uag-js/portal.js', (req, res) => {
141
+ if (!uagJsDirectory())
142
+ return missing(res);
143
+ serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: true, chain: chainOf(req, ['https:']) }) + scripts());
144
+ });
145
+ router.get('/uag-js/portal.css', (req, res) => {
146
+ if (!uagJsDirectory())
147
+ return missing(res);
148
+ const chain = chainOf(req, ['https:', 'http:']);
149
+ serve(req, res, 'text/css', (chain ? `@import url(${JSON.stringify(chain)});\n` : '') + read('uag-js.css') + styles());
150
+ });
151
+ router.get('/uag-js/uag-js.css', (req, res) => {
152
+ if (!uagJsDirectory())
153
+ return missing(res);
154
+ serve(req, res, 'text/css', read('uag-js.css') + styles());
155
+ });
156
+ return router;
157
+ }
@@ -0,0 +1,47 @@
1
+ import { Formflow } from '@formio/appserver';
2
+ /**
3
+ * Agentic Formflow from @formio/appserver-core, through @formio/appserver (see
4
+ * assertSingleCore), under the names the UAG uses. Internal to the UAG:
5
+ * applications reach Formflow as `Formflow` from this package's exports.
6
+ */
7
+ export declare const EngineConfigError: typeof Formflow.EngineConfigError, EngineRegistry: typeof Formflow.EngineRegistry, FormflowError: typeof Formflow.FormflowError, FormflowService: typeof Formflow.FormflowService, RateLimitError: typeof Formflow.RateLimitError, ScopedTokenError: typeof Formflow.ScopedTokenError, bearerOf: typeof Formflow.bearerOf, engineRegistry: typeof Formflow.engineRegistry, formflow: typeof Formflow.formflow, formflowAuthenticate: typeof Formflow.formflowAuthenticate, identityOf: typeof Formflow.identityOf, issueScopedToken: typeof Formflow.issueScopedToken, isScopedToken: typeof Formflow.isScopedToken, readScopedToken: typeof Formflow.readScopedToken, resultEvent: typeof Formflow.resultEvent, withoutOwnedValues: typeof Formflow.withoutOwnedValues, callerKey: typeof Formflow.callerKey, stubAgentEngine: typeof Formflow.stubAgentEngine, stubDecisionEngine: typeof Formflow.stubDecisionEngine, toolsForScope: typeof Formflow.toolsForScope, EngineError: typeof Formflow.EngineError, formatReport: typeof Formflow.formatReport, flagOn: typeof Formflow.flagOn, flagOff: typeof Formflow.flagOff, ruleWithConstraints: typeof Formflow.ruleWithConstraints, describeBinary: typeof Formflow.describeBinary, isBinaryComponent: typeof Formflow.isBinaryComponent, fileMeta: typeof Formflow.fileMeta, plainTextReader: Formflow.FormflowFileReader, useDatabase: typeof Formflow.useDatabase, startEngines: typeof Formflow.startEngines, CHAT_INSTRUCTIONS: string, CHAT_REPLY: string, CHAT_CONFIRMED: string, CHAT_CHECK_TOOL: Formflow.ToolDefinition, LEASE_MS: number, RENEW_MS: number, MAX_ATTEMPTS: number;
8
+ export type AgentEngine = Formflow.AgentEngine;
9
+ export type TaskRunStatus = Formflow.TaskRunStatus;
10
+ export type CoreTaskRunRecord<Id> = Formflow.TaskRunRecord<Id>;
11
+ export type CoreNewTaskRun<Id> = Formflow.NewTaskRun<Id>;
12
+ export type CoreTaskRunStore<Id> = Formflow.TaskRunStore<Id>;
13
+ export type CallerIdentity = Formflow.CallerIdentity;
14
+ export type DecisionEngine = Formflow.DecisionEngine;
15
+ export type DecisionRequest = Formflow.DecisionRequest;
16
+ export type EngineInstance = Formflow.EngineInstance;
17
+ export type EngineRegistry = Formflow.EngineRegistry;
18
+ export type ExtractRequest = Formflow.ExtractRequest;
19
+ export type ExtractResponse = Formflow.ExtractResponse;
20
+ export type FieldSpec = Formflow.FieldSpec;
21
+ export type ToolCallResult = Formflow.ToolCallResult;
22
+ export type ToolDefinition = Formflow.ToolDefinition;
23
+ export type FormflowAgentApiConfig = Formflow.FormflowAgentApiConfig;
24
+ export type FormflowConfig = Formflow.FormflowConfig;
25
+ export type FormflowForm = Formflow.FormflowForm;
26
+ export type FormflowHost = Formflow.FormflowHost;
27
+ export type FormflowRateLimit = Formflow.FormflowRateLimit;
28
+ export type FormflowRoutingConfig = Formflow.FormflowRoutingConfig;
29
+ export type FormflowService = Formflow.FormflowService;
30
+ export type FormflowTask = Formflow.FormflowTask;
31
+ export type FormflowTool = Formflow.FormflowTool;
32
+ export type FormflowFileReader = Formflow.FormflowFileReader;
33
+ export type FormflowFileMeta = Formflow.FormflowFileMeta;
34
+ export type FormflowFileContent = Formflow.FormflowFileContent;
35
+ export type OperationContext = Formflow.OperationContext;
36
+ export type OperationResult = Formflow.OperationResult;
37
+ export type ScopedTokenClaims = Formflow.ScopedTokenClaims;
38
+ /**
39
+ * Who an operation's research acts as: core's `identityOf`, and for a caller
40
+ * signed in with a Form.io token (`x-jwt-token`), that token, so a scoped token
41
+ * carries the user to /mcp as an `Authorization` bearer token would. A portal
42
+ * team member's remote token is never carried: research for them is refused,
43
+ * as it is for anonymous callers.
44
+ */
45
+ export declare function callerIdentity(auth: any, req: {
46
+ get?: (name: string) => string | undefined;
47
+ }): CallerIdentity;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_ATTEMPTS = exports.RENEW_MS = exports.LEASE_MS = exports.CHAT_CHECK_TOOL = exports.CHAT_CONFIRMED = exports.CHAT_REPLY = exports.CHAT_INSTRUCTIONS = exports.startEngines = exports.useDatabase = exports.plainTextReader = exports.fileMeta = exports.isBinaryComponent = exports.describeBinary = exports.ruleWithConstraints = exports.flagOff = exports.flagOn = exports.formatReport = exports.EngineError = exports.toolsForScope = exports.stubDecisionEngine = exports.stubAgentEngine = exports.callerKey = exports.withoutOwnedValues = exports.resultEvent = exports.readScopedToken = exports.isScopedToken = exports.issueScopedToken = exports.identityOf = exports.formflowAuthenticate = exports.formflow = exports.engineRegistry = exports.bearerOf = exports.ScopedTokenError = exports.RateLimitError = exports.FormflowService = exports.FormflowError = exports.EngineRegistry = exports.EngineConfigError = void 0;
4
+ exports.callerIdentity = callerIdentity;
5
+ const appserver_1 = require("@formio/appserver");
6
+ /**
7
+ * Agentic Formflow from @formio/appserver-core, through @formio/appserver (see
8
+ * assertSingleCore), under the names the UAG uses. Internal to the UAG:
9
+ * applications reach Formflow as `Formflow` from this package's exports.
10
+ */
11
+ ({ EngineConfigError: exports.EngineConfigError, EngineRegistry: exports.EngineRegistry, FormflowError: exports.FormflowError, FormflowService: exports.FormflowService, RateLimitError: exports.RateLimitError, ScopedTokenError: exports.ScopedTokenError, bearerOf: exports.bearerOf, engineRegistry: exports.engineRegistry, formflow: exports.formflow, formflowAuthenticate: exports.formflowAuthenticate, identityOf: exports.identityOf, issueScopedToken: exports.issueScopedToken, isScopedToken: exports.isScopedToken, readScopedToken: exports.readScopedToken, resultEvent: exports.resultEvent, withoutOwnedValues: exports.withoutOwnedValues, callerKey: exports.callerKey, stubAgentEngine: exports.stubAgentEngine, stubDecisionEngine: exports.stubDecisionEngine, toolsForScope: exports.toolsForScope, EngineError: exports.EngineError, formatReport: exports.formatReport, flagOn: exports.flagOn, flagOff: exports.flagOff, ruleWithConstraints: exports.ruleWithConstraints, describeBinary: exports.describeBinary, isBinaryComponent: exports.isBinaryComponent, fileMeta: exports.fileMeta, plainTextReader: exports.plainTextReader, useDatabase: exports.useDatabase, startEngines: exports.startEngines, CHAT_INSTRUCTIONS: exports.CHAT_INSTRUCTIONS, CHAT_REPLY: exports.CHAT_REPLY, CHAT_CONFIRMED: exports.CHAT_CONFIRMED, CHAT_CHECK_TOOL: exports.CHAT_CHECK_TOOL, LEASE_MS: exports.LEASE_MS, RENEW_MS: exports.RENEW_MS, MAX_ATTEMPTS: exports.MAX_ATTEMPTS } = appserver_1.Formflow);
12
+ /**
13
+ * Who an operation's research acts as: core's `identityOf`, and for a caller
14
+ * signed in with a Form.io token (`x-jwt-token`), that token, so a scoped token
15
+ * carries the user to /mcp as an `Authorization` bearer token would. A portal
16
+ * team member's remote token is never carried: research for them is refused,
17
+ * as it is for anonymous callers.
18
+ */
19
+ function callerIdentity(auth, req) {
20
+ const identity = (0, exports.identityOf)(auth, req.get?.('authorization'));
21
+ if (identity.admin || identity.bearer)
22
+ return identity;
23
+ const formioToken = req.get?.('x-jwt-token');
24
+ if (auth?.user && !auth.remoteToken && typeof formioToken === 'string' && formioToken) {
25
+ return { admin: false, bearer: formioToken };
26
+ }
27
+ return identity;
28
+ }
@@ -0,0 +1,53 @@
1
+ import { AuthRequest } from '@formio/appserver';
2
+ import { FormflowError } from './formflow';
3
+ import type { UAGProjectInterface } from './UAGProjectInterface';
4
+ /**
5
+ * Who the UAG acts as at Form.io when it runs a task for someone: the caller,
6
+ * with the token they sent, or a Run task action's agent role, acting on the
7
+ * caller's behalf with a Form.io token the UAG signs.
8
+ *
9
+ * The UAG signs Form.io "external" tokens with JWT_SECRET, which must be Form.io
10
+ * Enterprise's: Form.io then takes the user, and their roles, from the token. A
11
+ * token the UAG signs is short-lived, never marks an administrator, and never
12
+ * carries an admin role.
13
+ */
14
+ /** Seconds a token the UAG signs is valid for. */
15
+ export declare const SIGNED_TOKEN_TTL = 300;
16
+ /** The user the UAG acts for: who sent the submission. No token, ever. */
17
+ export type ActingFor = {
18
+ /** The user's id; none for an anonymous caller. */
19
+ _id?: string;
20
+ /** Their roles when they submitted. */
21
+ roles: string[];
22
+ email?: string;
23
+ };
24
+ /** What a token the UAG signs says about why it was signed (Form.io ignores it). */
25
+ export type AgentClaim = {
26
+ agent: boolean;
27
+ action?: string;
28
+ task?: string;
29
+ };
30
+ /** A token the UAG may not sign, or Form.io would not accept. */
31
+ export declare class AgentIdentityError extends FormflowError {
32
+ constructor(message: string);
33
+ }
34
+ /** The user a request comes from, as the UAG records it. */
35
+ export declare function actingForOf(auth: AuthRequest | undefined, project: UAGProjectInterface): ActingFor;
36
+ /**
37
+ * Sign a Form.io token for `user` with `roles`. Refuses an admin role, and never
38
+ * marks the holder an administrator.
39
+ */
40
+ export declare function signFormioToken(project: UAGProjectInterface, user: ActingFor, roles: string[], claim: AgentClaim): string;
41
+ /** An identity that acts as `role` for `user`, with a newly signed token. */
42
+ export declare function agentAuth(project: UAGProjectInterface, user: ActingFor, role: string, claim: Omit<AgentClaim, 'agent'>): Promise<AuthRequest>;
43
+ /**
44
+ * An identity that acts as `user`, with their recorded roles, for research and
45
+ * for the runs that act as them. A user's admin roles are left out: the UAG
46
+ * never signs a token with one, so the user acts with their other roles.
47
+ */
48
+ export declare function userAuth(project: UAGProjectInterface, user: ActingFor, claim: Omit<AgentClaim, 'agent'>): Promise<AuthRequest>;
49
+ /**
50
+ * Explain a Form.io refusal of a token the UAG signed: almost always a
51
+ * JWT_SECRET that is not Form.io Enterprise's.
52
+ */
53
+ export declare function refusedSignedToken(status: number | undefined): AgentIdentityError | null;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AgentIdentityError = exports.SIGNED_TOKEN_TTL = void 0;
7
+ exports.actingForOf = actingForOf;
8
+ exports.signFormioToken = signFormioToken;
9
+ exports.agentAuth = agentAuth;
10
+ exports.userAuth = userAuth;
11
+ exports.refusedSignedToken = refusedSignedToken;
12
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
13
+ const appserver_1 = require("@formio/appserver");
14
+ const formflow_1 = require("./formflow");
15
+ /**
16
+ * Who the UAG acts as at Form.io when it runs a task for someone: the caller,
17
+ * with the token they sent, or a Run task action's agent role, acting on the
18
+ * caller's behalf with a Form.io token the UAG signs.
19
+ *
20
+ * The UAG signs Form.io "external" tokens with JWT_SECRET, which must be Form.io
21
+ * Enterprise's: Form.io then takes the user, and their roles, from the token. A
22
+ * token the UAG signs is short-lived, never marks an administrator, and never
23
+ * carries an admin role.
24
+ */
25
+ /** Seconds a token the UAG signs is valid for. */
26
+ exports.SIGNED_TOKEN_TTL = 300;
27
+ /** A token the UAG may not sign, or Form.io would not accept. */
28
+ class AgentIdentityError extends formflow_1.FormflowError {
29
+ constructor(message) {
30
+ super(500, message, 'agent_identity');
31
+ this.name = 'AgentIdentityError';
32
+ }
33
+ }
34
+ exports.AgentIdentityError = AgentIdentityError;
35
+ /** The user a request comes from, as the UAG records it. */
36
+ function actingForOf(auth, project) {
37
+ const user = auth?.user;
38
+ if (!user?._id) {
39
+ return { roles: project.anonymousRoles };
40
+ }
41
+ const email = user.data?.email;
42
+ return {
43
+ _id: String(user._id),
44
+ roles: (user.roles || []).map(String),
45
+ ...(typeof email === 'string' && { email })
46
+ };
47
+ }
48
+ /** The project's admin role ids, from its access settings. */
49
+ function adminRoles(project) {
50
+ return new Set(Object.values(project.access?.roles || {})
51
+ .filter((role) => role?.admin)
52
+ .map((role) => String(role._id)));
53
+ }
54
+ /**
55
+ * Sign a Form.io token for `user` with `roles`. Refuses an admin role, and never
56
+ * marks the holder an administrator.
57
+ */
58
+ function signFormioToken(project, user, roles, claim) {
59
+ const secret = project.config?.auth?.secret;
60
+ if (!secret)
61
+ throw new AgentIdentityError('Acting on behalf of a user needs JWT_SECRET, the same as Form.io Enterprise\'s.');
62
+ const admin = adminRoles(project);
63
+ if (roles.some((role) => admin.has(String(role)))) {
64
+ throw new AgentIdentityError('An agent cannot act with an admin role. Choose a role without admin access.');
65
+ }
66
+ const projectId = String(project.project?._id || '');
67
+ if (!projectId)
68
+ throw new AgentIdentityError('Acting on behalf of a user needs the project to be loaded.');
69
+ const payload = {
70
+ external: true,
71
+ // Form.io names an external user without an id from its data.
72
+ user: user._id
73
+ ? { _id: user._id, data: { ...(user.email && { email: user.email }) }, roles: roles.map(String) }
74
+ : { _id: 'external', data: { id: 'uag-anonymous' }, roles: roles.map(String) },
75
+ project: { _id: projectId },
76
+ uag: claim
77
+ };
78
+ return jsonwebtoken_1.default.sign(payload, secret, { expiresIn: exports.SIGNED_TOKEN_TTL });
79
+ }
80
+ /** An identity that acts as `role` for `user`, with a newly signed token. */
81
+ async function agentAuth(project, user, role, claim) {
82
+ return authFor(project, user, [role], { agent: true, ...claim });
83
+ }
84
+ /**
85
+ * An identity that acts as `user`, with their recorded roles, for research and
86
+ * for the runs that act as them. A user's admin roles are left out: the UAG
87
+ * never signs a token with one, so the user acts with their other roles.
88
+ */
89
+ async function userAuth(project, user, claim) {
90
+ const admin = adminRoles(project);
91
+ return authFor(project, user, user.roles.filter((role) => !admin.has(String(role))), { agent: false, ...claim });
92
+ }
93
+ async function authFor(project, user, roles, claim) {
94
+ const token = signFormioToken(project, user, roles, claim);
95
+ const auth = new appserver_1.AuthRequest(project);
96
+ await auth.fromFormioUser({ _id: user._id || 'external', roles, data: user.email ? { email: user.email } : {} }, token, project);
97
+ return auth;
98
+ }
99
+ /**
100
+ * Explain a Form.io refusal of a token the UAG signed: almost always a
101
+ * JWT_SECRET that is not Form.io Enterprise's.
102
+ */
103
+ function refusedSignedToken(status) {
104
+ if (status !== 401 && status !== 440)
105
+ return null;
106
+ return new AgentIdentityError('Form.io refused the token the UAG signed to act on the user\'s behalf: JWT_SECRET must be the same as Form.io Enterprise\'s.');
107
+ }