@formio/uag 2.0.0-rc.1 → 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 +17 -15
- package/bin/uag-eval.js +7 -3
- package/bin/uag.js +48 -5
- package/lib/UAGFormInterface.d.ts +12 -0
- package/lib/UAGFormInterface.js +55 -12
- package/lib/UAGProjectInterface.d.ts +9 -1
- package/lib/UAGProjectInterface.js +25 -7
- package/lib/actionLogs.d.ts +1 -10
- package/lib/actionLogs.js +5 -3
- package/lib/actions/runTask.js +4 -3
- package/lib/agent/anonymous.js +5 -4
- package/lib/agent/chat.d.ts +3 -2
- package/lib/agent/chat.js +35 -99
- package/lib/agent/engines.d.ts +10 -6
- package/lib/agent/engines.js +19 -10
- package/lib/agent/errors.d.ts +25 -0
- package/lib/agent/errors.js +132 -0
- package/lib/agent/index.d.ts +1 -1
- package/lib/agent/index.js +2 -1
- package/lib/agent/remote.d.ts +5 -1
- package/lib/agent/remote.js +25 -2
- package/lib/agent/router.d.ts +32 -2
- package/lib/agent/router.js +37 -25
- package/lib/agent/stubs.d.ts +13 -2
- package/lib/agent/stubs.js +18 -5
- package/lib/agent/tools.d.ts +24 -3
- package/lib/agent/tools.js +47 -4
- package/lib/config.d.ts +31 -4
- package/lib/database.d.ts +3 -8
- package/lib/database.js +2 -19
- 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 +11 -4
- package/lib/formflow.js +5 -5
- package/lib/index.d.ts +65 -9
- package/lib/index.js +228 -26
- 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 +19 -0
- package/lib/runner.d.ts +2 -0
- package/lib/runner.js +11 -3
- package/lib/runs.d.ts +17 -79
- package/lib/runs.js +10 -71
- package/lib/testing.d.ts +74 -0
- package/lib/testing.js +180 -0
- package/lib/tools/SchemaBuilder.d.ts +0 -5
- package/lib/tools/SchemaBuilder.js +0 -8
- package/lib/tools/agentDecide.js +2 -2
- package/lib/tools/agentProvide.d.ts +0 -5
- package/lib/tools/agentProvide.js +7 -40
- package/lib/tools/collectData.js +2 -1
- package/lib/tools/index.d.ts +3 -2
- package/lib/tools/index.js +5 -5
- package/lib/tools/readDocument.d.ts +17 -0
- package/lib/tools/readDocument.js +99 -0
- package/lib/tools/submissionUpdate.js +3 -2
- 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 +17 -10
- package/testing.d.ts +1 -0
- package/testing.js +2 -0
package/lib/index.d.ts
CHANGED
|
@@ -1,32 +1,57 @@
|
|
|
1
1
|
import { Server, ServerModule } from '@formio/appserver';
|
|
2
|
-
import {
|
|
2
|
+
import type { LoadedModule, ModuleApi, ModuleRoute, ModuleRouteHandlers, ProjectInterface } from '@formio/appserver';
|
|
3
|
+
import { Request, Response, Router } from 'express';
|
|
3
4
|
import { UAGProjectInterface } from './UAGProjectInterface';
|
|
4
5
|
import { UAGFormInterface } from './UAGFormInterface';
|
|
5
6
|
import { UAGConfig } from './config';
|
|
6
7
|
import { UAGDatabase } from './database';
|
|
7
|
-
|
|
8
|
+
import type { UAGModuleApi } from './modules';
|
|
9
|
+
export type UAGModule = ServerModule<UAGModuleApi> & {
|
|
8
10
|
config?: UAGConfig;
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
|
-
* Agentic Formflow keeps its license state inside @formio/appserver-core,
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Agentic Formflow keeps its license state inside @formio/appserver-core, and
|
|
14
|
+
* modules are built on its module framework. The UAG loads it only through
|
|
15
|
+
* @formio/appserver, so the two always share one copy, which must be 2.26.4 or
|
|
16
|
+
* later. An older @formio/appserver, or an old lockfile, can still resolve an
|
|
17
|
+
* older one, which would fail in confusing ways; say what to do instead.
|
|
15
18
|
*/
|
|
16
|
-
export declare function assertSingleCore(
|
|
19
|
+
export declare function assertSingleCore(version?: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* isolated-vm, which runs form logic, needs Node's startup snapshot off. Without
|
|
22
|
+
* it nothing fails at once: the process can crash later, in native code. So an
|
|
23
|
+
* embedding server that forgot the flag is stopped here, saying what to do.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertNodeSnapshotDisabled(execArgv?: string[], nodeOptions?: string): void;
|
|
17
26
|
export declare class UAGServer extends Server {
|
|
18
27
|
config: UAGConfig;
|
|
19
28
|
/** The UAG's own database, once it may be used. */
|
|
20
29
|
database?: UAGDatabase;
|
|
21
30
|
/** The database between connecting and the license check. */
|
|
22
31
|
private pendingDatabase?;
|
|
32
|
+
/**
|
|
33
|
+
* Use a module, or a list of modules (each in turn). A module used twice (by
|
|
34
|
+
* its `name`) is used once. `config.tools` adds up across modules; an MCP
|
|
35
|
+
* tool or engine type that two modules define stops startup, naming both.
|
|
36
|
+
* A route's `rateLimit` is the default for its name, unless the operator set one.
|
|
37
|
+
*/
|
|
38
|
+
use(module: any, source?: string): void;
|
|
39
|
+
/** MCP tools and engine types, which only one module may define. */
|
|
40
|
+
protected moduleClaims(definition: Record<string, any>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* MCP tools add up across modules (lodash would merge them index by index),
|
|
43
|
+
* and a named module's tools get its API as `extra.module`. Its routes'
|
|
44
|
+
* `rateLimit`s become the defaults for their names.
|
|
45
|
+
*/
|
|
46
|
+
protected mergeModuleConfig(config: any, module?: LoadedModule): void;
|
|
47
|
+
/** Route rate-limit defaults, for the names the operator left unset. */
|
|
48
|
+
private routeRateLimits;
|
|
23
49
|
constructor(config?: UAGConfig);
|
|
24
50
|
/**
|
|
25
51
|
* Connect the UAG's own database before the project loads, and check it
|
|
26
52
|
* without writing: a database the UAG may not use stops startup. It is used
|
|
27
53
|
* (its collections created, its stores handed out) only once the license is
|
|
28
|
-
* known; see enableDatabase. Until then
|
|
29
|
-
* it, to record a license server outage.
|
|
54
|
+
* known; see enableDatabase. Until then nothing writes to it.
|
|
30
55
|
*/
|
|
31
56
|
protected connectDatabase(): Promise<void>;
|
|
32
57
|
/**
|
|
@@ -36,6 +61,14 @@ export declare class UAGServer extends Server {
|
|
|
36
61
|
* otherwise leave it untouched, close it, and say why.
|
|
37
62
|
*/
|
|
38
63
|
protected enableDatabase(project: UAGProjectInterface): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* An Agent API request that failed before its handlers (a body that is not JSON,
|
|
66
|
+
* one over the size limit) is answered in the Agent API's error shape; every other
|
|
67
|
+
* route keeps Form.io's plain-text answer.
|
|
68
|
+
*/
|
|
69
|
+
protected sendError(req: Request, res: Response, status: number, message: string, err?: any): void;
|
|
70
|
+
/** Whether a request is for a module's form route: its method, and a path ending in the route's. */
|
|
71
|
+
private isModuleRouteRequest;
|
|
39
72
|
/** Browsers reading Agent API responses need the request id too. */
|
|
40
73
|
get exposedHeaders(): string[];
|
|
41
74
|
/** The loaded UAG project, once the project router has been built. */
|
|
@@ -47,12 +80,35 @@ export declare class UAGServer extends Server {
|
|
|
47
80
|
* on the form's own routes.
|
|
48
81
|
*/
|
|
49
82
|
protected extendRouter(router: Router): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* List the UAG's engines and MCP tools in the plugin registry, beside
|
|
85
|
+
* appserver-core's form actions. They keep loading their own way.
|
|
86
|
+
*/
|
|
87
|
+
protected listPlugins(): Promise<void>;
|
|
88
|
+
/** A form's own route segments, which module routes may not use: appserver's, and the agent operations. */
|
|
89
|
+
protected get reservedFormPaths(): string[];
|
|
90
|
+
/** Module routes may not take the names of the UAG's own rate-limited operations. */
|
|
91
|
+
protected get reservedRouteNames(): string[];
|
|
92
|
+
protected get hostName(): string;
|
|
93
|
+
protected get hostVersion(): string;
|
|
94
|
+
/** The module API: appserver's, with the UAG's license check, engines, converters and helpers. */
|
|
95
|
+
protected createModuleApi(module: LoadedModule): ModuleApi;
|
|
96
|
+
/** Module routes run behind what the agent operations run behind: the caller check, body limit, request id, rate limits and error shape. */
|
|
97
|
+
protected moduleRouteHandlers(project: ProjectInterface, route: ModuleRoute<any>, module: LoadedModule): ModuleRouteHandlers;
|
|
98
|
+
protected moduleErrorBody(req: Request, err: unknown): Record<string, any> & {
|
|
99
|
+
status: number;
|
|
100
|
+
};
|
|
50
101
|
router(): Promise<any>;
|
|
51
102
|
}
|
|
52
103
|
export * from '@formio/appserver';
|
|
53
104
|
export * from './config';
|
|
54
105
|
export * from './agent';
|
|
55
106
|
export * from './tools';
|
|
107
|
+
export * from './documents';
|
|
108
|
+
export * from './modules';
|
|
109
|
+
export { sanitizeMarkdown } from './markdown';
|
|
110
|
+
export { usePluginFiles, pluginFiles, pluginFilesIn, DEFAULT_PLUGINS_DIR } from './pluginFiles';
|
|
56
111
|
export * from './router';
|
|
57
112
|
export * from './template';
|
|
113
|
+
export { sanitizeRichText, isRichText, RICH_TEXT_TAGS, RICH_TEXT_RULE } from './richText';
|
|
58
114
|
export { UAGProjectInterface, UAGFormInterface };
|
package/lib/index.js
CHANGED
|
@@ -13,11 +13,16 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
13
13
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.UAGFormInterface = exports.UAGProjectInterface = exports.UAGServer = void 0;
|
|
20
|
+
exports.UAGFormInterface = exports.UAGProjectInterface = exports.RICH_TEXT_RULE = exports.RICH_TEXT_TAGS = exports.isRichText = exports.sanitizeRichText = exports.DEFAULT_PLUGINS_DIR = exports.pluginFilesIn = exports.pluginFiles = exports.usePluginFiles = exports.sanitizeMarkdown = exports.UAGServer = void 0;
|
|
18
21
|
exports.assertSingleCore = assertSingleCore;
|
|
22
|
+
exports.assertNodeSnapshotDisabled = assertNodeSnapshotDisabled;
|
|
19
23
|
const appserver_1 = require("@formio/appserver");
|
|
20
24
|
const formflow_1 = require("./formflow");
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
21
26
|
const UAGProjectInterface_1 = require("./UAGProjectInterface");
|
|
22
27
|
Object.defineProperty(exports, "UAGProjectInterface", { enumerable: true, get: function () { return UAGProjectInterface_1.UAGProjectInterface; } });
|
|
23
28
|
const UAGFormInterface_1 = require("./UAGFormInterface");
|
|
@@ -25,26 +30,71 @@ Object.defineProperty(exports, "UAGFormInterface", { enumerable: true, get: func
|
|
|
25
30
|
const lodash_1 = require("lodash");
|
|
26
31
|
const engines_1 = require("./agent/engines");
|
|
27
32
|
const router_1 = require("./agent/router");
|
|
33
|
+
const errors_1 = require("./agent/errors");
|
|
28
34
|
const database_1 = require("./database");
|
|
29
35
|
const runs_1 = require("./runs");
|
|
30
36
|
const actionLogs_1 = require("./actionLogs");
|
|
31
37
|
const runner_1 = require("./runner");
|
|
32
38
|
const runTask_1 = require("./actions/runTask");
|
|
39
|
+
const version_1 = require("./version");
|
|
40
|
+
const utils_1 = require("./tools/utils");
|
|
41
|
+
const plugins_1 = require("./plugins");
|
|
42
|
+
const plugins_2 = require("./documents/plugins");
|
|
43
|
+
const uagJs_1 = require("./documents/uagJs");
|
|
44
|
+
const modules_1 = require("./modules");
|
|
45
|
+
const tools_1 = require("./agent/tools");
|
|
46
|
+
/** Projects whose engines were loaded while the plugins were listed. */
|
|
47
|
+
const enginesLoaded = new WeakSet();
|
|
33
48
|
const debug = require('debug')('formio:uag');
|
|
34
49
|
/**
|
|
35
|
-
* Agentic Formflow keeps its license state inside @formio/appserver-core,
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
50
|
+
* Agentic Formflow keeps its license state inside @formio/appserver-core, and
|
|
51
|
+
* modules are built on its module framework. The UAG loads it only through
|
|
52
|
+
* @formio/appserver, so the two always share one copy, which must be 2.26.4 or
|
|
53
|
+
* later. An older @formio/appserver, or an old lockfile, can still resolve an
|
|
54
|
+
* older one, which would fail in confusing ways; say what to do instead.
|
|
39
55
|
*/
|
|
40
|
-
function assertSingleCore(
|
|
56
|
+
function assertSingleCore(version = appserverCoreVersion()) {
|
|
41
57
|
const [major, minor, patch] = version.split('.').map((part) => parseInt(part, 10) || 0);
|
|
42
|
-
if (major < 2 || (major === 2 && (minor <
|
|
43
|
-
throw new Error(`@formio/uag needs @formio/appserver-core 2.
|
|
58
|
+
if (major < 2 || (major === 2 && (minor < 26 || (minor === 26 && patch < 4)))) {
|
|
59
|
+
throw new Error(`@formio/uag needs @formio/appserver-core 2.26.4 or later, through @formio/appserver; ${version} is installed. Upgrade @formio/appserver.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The version of the @formio/appserver-core that @formio/appserver loads. */
|
|
63
|
+
function appserverCoreVersion() {
|
|
64
|
+
const appserver = path_1.default.dirname(require.resolve('@formio/appserver/package.json'));
|
|
65
|
+
return String(require(require.resolve('@formio/appserver-core/package.json', { paths: [appserver] })).version || '0.0.0');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* isolated-vm, which runs form logic, needs Node's startup snapshot off. Without
|
|
69
|
+
* it nothing fails at once: the process can crash later, in native code. So an
|
|
70
|
+
* embedding server that forgot the flag is stopped here, saying what to do.
|
|
71
|
+
*/
|
|
72
|
+
function assertNodeSnapshotDisabled(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS || '') {
|
|
73
|
+
if (execArgv.includes('--no-node-snapshot') || /(^|\s)--no-node-snapshot(\s|$)/.test(nodeOptions))
|
|
74
|
+
return;
|
|
75
|
+
throw new Error('@formio/uag needs Node started with --no-node-snapshot: isolated-vm, which runs form logic, can crash the process without it. ' +
|
|
76
|
+
'Start node with --no-node-snapshot, or set NODE_OPTIONS=--no-node-snapshot. The uag command and the formio/uag image do this for you.');
|
|
77
|
+
}
|
|
78
|
+
/** A host name a browser cannot reach: a container or service name, with no dot. */
|
|
79
|
+
function internalHost(url) {
|
|
80
|
+
try {
|
|
81
|
+
const { hostname } = new URL(url);
|
|
82
|
+
return !!hostname && hostname !== 'localhost' && !hostname.includes('.') && !hostname.includes(':');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Warnings for settings that start fine and fail later, in ways far from their cause. */
|
|
89
|
+
function warnAboutSettings(config, secretGiven) {
|
|
90
|
+
if (!secretGiven) {
|
|
91
|
+
console.warn('[formio-uag] JWT_SECRET is not set, so the UAG signs its tokens with a random secret that lasts only as long as this process: ' +
|
|
92
|
+
'every token stops working when it restarts, and other instances refuse it. Set JWT_SECRET, the same on every instance.');
|
|
44
93
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
94
|
+
const auth = config.auth || {};
|
|
95
|
+
if (auth.mcpOAuth && !auth.loginForm && internalHost(String(config.project || ''))) {
|
|
96
|
+
console.warn(`[formio-uag] MCP_OAUTH is on, but LOGIN_FORM is not set: the sign-in page loads ${String(config.project).replace(/\/+$/, '')}/user/login, ` +
|
|
97
|
+
'an address a person\'s browser cannot reach. Set LOGIN_FORM to the login form\'s public URL.');
|
|
48
98
|
}
|
|
49
99
|
}
|
|
50
100
|
/** The UAG's own database from the environment, when `MONGO` is set. */
|
|
@@ -61,9 +111,59 @@ function databaseFromEnv(env = process.env) {
|
|
|
61
111
|
};
|
|
62
112
|
}
|
|
63
113
|
class UAGServer extends appserver_1.Server {
|
|
114
|
+
/**
|
|
115
|
+
* Use a module, or a list of modules (each in turn). A module used twice (by
|
|
116
|
+
* its `name`) is used once. `config.tools` adds up across modules; an MCP
|
|
117
|
+
* tool or engine type that two modules define stops startup, naming both.
|
|
118
|
+
* A route's `rateLimit` is the default for its name, unless the operator set one.
|
|
119
|
+
*/
|
|
120
|
+
use(module, source) {
|
|
121
|
+
module = appserver_1.Server.defaultExport(module);
|
|
122
|
+
if (module && typeof module === 'object' && !Array.isArray(module) && module.uag !== undefined) {
|
|
123
|
+
const label = source || (typeof module.name === 'string' && module.name) || 'a module';
|
|
124
|
+
throw new Error(`The module ${label} has a "uag" key, which UAG modules no longer use: move its keys (name, apiVersion, settings, routes, components, initialize) onto the module itself.`);
|
|
125
|
+
}
|
|
126
|
+
super.use(module, source);
|
|
127
|
+
if (typeof module?.name === 'string' && this.modules.get(module.name)?.definition === module)
|
|
128
|
+
this.routeRateLimits(module);
|
|
129
|
+
}
|
|
130
|
+
/** MCP tools and engine types, which only one module may define. */
|
|
131
|
+
moduleClaims(definition) {
|
|
132
|
+
return [
|
|
133
|
+
...(definition.config?.tools || []).filter((tool) => tool?.name).map((tool) => `MCP tool "${tool.name}"`),
|
|
134
|
+
...Object.keys(definition.config?.engineTypes || {}).map((name) => `engine type "${name}"`)
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* MCP tools add up across modules (lodash would merge them index by index),
|
|
139
|
+
* and a named module's tools get its API as `extra.module`. Its routes'
|
|
140
|
+
* `rateLimit`s become the defaults for their names.
|
|
141
|
+
*/
|
|
142
|
+
mergeModuleConfig(config, module) {
|
|
143
|
+
const { tools, ...rest } = config;
|
|
144
|
+
super.mergeModuleConfig(rest, module);
|
|
145
|
+
if (Array.isArray(tools) && tools.length) {
|
|
146
|
+
if (module)
|
|
147
|
+
for (const tool of tools)
|
|
148
|
+
(0, tools_1.setToolModule)(tool, this.modules.apiFor(module));
|
|
149
|
+
this.config.tools = [...(this.config.tools || []), ...tools];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Route rate-limit defaults, for the names the operator left unset. */
|
|
153
|
+
routeRateLimits(module) {
|
|
154
|
+
for (const route of Object.values(module?.routes?.form || {})) {
|
|
155
|
+
const name = route?.name || module.name;
|
|
156
|
+
if (!route?.rateLimit || (0, lodash_1.get)(this.config, ['agentApi', 'rateLimits', 'caller', name]))
|
|
157
|
+
continue;
|
|
158
|
+
(0, lodash_1.set)(this.config, ['agentApi', 'rateLimits', 'caller', name], route.rateLimit);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
64
161
|
constructor(config) {
|
|
162
|
+
assertNodeSnapshotDisabled();
|
|
65
163
|
assertSingleCore();
|
|
66
164
|
const configuredDb = !!config?.db;
|
|
165
|
+
// Read before the server fills in a random one.
|
|
166
|
+
const secretGiven = !!(config?.auth?.secret || (0, lodash_1.get)(process.env, 'JWT_SECRET', '').toString());
|
|
67
167
|
const uagLicense = (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString();
|
|
68
168
|
if (uagLicense) {
|
|
69
169
|
console.warn('[formio-uag] UAG_LICENSE is deprecated. Put a Form.io license that includes the UAG in LICENSE_KEY instead; UAG_LICENSE keeps working until UAG 3.0.');
|
|
@@ -85,15 +185,28 @@ class UAGServer extends appserver_1.Server {
|
|
|
85
185
|
retryDelay: parseInt((0, lodash_1.get)(process.env, 'PROJECT_RETRY_DELAY', 5000).toString(), 10),
|
|
86
186
|
submissionProxy: true,
|
|
87
187
|
processAsClient: true,
|
|
188
|
+
// GET /status names the UAG's version beside the Form.io packages it runs on.
|
|
189
|
+
status: { '@formio/uag': version_1.UAG_VERSION },
|
|
190
|
+
// Bodies are parsed before authentication, so keep them to what Form.io accepts.
|
|
191
|
+
maxBodySize: (0, lodash_1.get)(process.env, 'MAX_BODY_SIZE', '16mb').toString(),
|
|
88
192
|
auth: { pkce: true, mcpOAuth: (0, lodash_1.get)(process.env, 'MCP_OAUTH', '').toString() === 'true' },
|
|
89
|
-
database: databaseFromEnv()
|
|
193
|
+
database: databaseFromEnv(),
|
|
194
|
+
// Modules an operator turns off, such as one an image includes.
|
|
195
|
+
modules: { disabled: (0, lodash_1.get)(process.env, 'MODULES_DISABLED', '').toString().split(',').map((name) => name.trim()).filter(Boolean) }
|
|
90
196
|
}));
|
|
197
|
+
// The server's own MCP tools and engine types: a module may not define them again.
|
|
198
|
+
for (const tool of (this.config.tools || []))
|
|
199
|
+
if (tool?.name)
|
|
200
|
+
this.modules.claim(`MCP tool "${tool.name}"`, "the server's configuration");
|
|
201
|
+
for (const name of Object.keys(this.config.engineTypes || {}))
|
|
202
|
+
this.modules.claim(`engine type "${name}"`, "the server's configuration");
|
|
91
203
|
// MONGO is the UAG's own database, not a database for the appserver to keep
|
|
92
204
|
// submissions in: Form.io stores those.
|
|
93
205
|
if (!configuredDb && this.module.db) {
|
|
94
206
|
delete this.module.db;
|
|
95
207
|
delete this.config.db;
|
|
96
208
|
}
|
|
209
|
+
warnAboutSettings(this.config, secretGiven);
|
|
97
210
|
this.use({
|
|
98
211
|
ProjectInterface: UAGProjectInterface_1.UAGProjectInterface,
|
|
99
212
|
FormInterface: UAGFormInterface_1.UAGFormInterface,
|
|
@@ -105,14 +218,12 @@ class UAGServer extends appserver_1.Server {
|
|
|
105
218
|
* Connect the UAG's own database before the project loads, and check it
|
|
106
219
|
* without writing: a database the UAG may not use stops startup. It is used
|
|
107
220
|
* (its collections created, its stores handed out) only once the license is
|
|
108
|
-
* known; see enableDatabase. Until then
|
|
109
|
-
* it, to record a license server outage.
|
|
221
|
+
* known; see enableDatabase. Until then nothing writes to it.
|
|
110
222
|
*/
|
|
111
223
|
async connectDatabase() {
|
|
112
224
|
const options = this.config.database;
|
|
113
225
|
if (!options?.url) {
|
|
114
|
-
console.log('[formio-uag] No database (MONGO): runs after the save are kept in memory, remote MCP sign-in (MCP_OAUTH) is unavailable
|
|
115
|
-
'and the licensed features turn off at once, with no grace period, whenever the license server cannot be reached.');
|
|
226
|
+
console.log('[formio-uag] No database (MONGO): runs after the save are kept in memory, and remote MCP sign-in (MCP_OAUTH) is unavailable.');
|
|
116
227
|
return;
|
|
117
228
|
}
|
|
118
229
|
if (this.database || this.pendingDatabase)
|
|
@@ -126,7 +237,6 @@ class UAGServer extends appserver_1.Server {
|
|
|
126
237
|
throw err;
|
|
127
238
|
}
|
|
128
239
|
this.pendingDatabase = database;
|
|
129
|
-
this.config.licenseGrace = database.licenseGrace();
|
|
130
240
|
}
|
|
131
241
|
/**
|
|
132
242
|
* Use the database once the project and its license are loaded: with a
|
|
@@ -139,22 +249,27 @@ class UAGServer extends appserver_1.Server {
|
|
|
139
249
|
this.pendingDatabase = undefined;
|
|
140
250
|
if (database) {
|
|
141
251
|
let refusal = '';
|
|
252
|
+
let grant;
|
|
142
253
|
try {
|
|
143
|
-
|
|
254
|
+
grant = await (0, formflow_1.useDatabase)(project, {
|
|
255
|
+
url: this.config.database.url,
|
|
256
|
+
authCodes: database.authCodeStore(),
|
|
257
|
+
taskRuns: { collection: database.taskRuns, ids: runs_1.OBJECT_IDS, project: String(project.project?._id || '') }
|
|
258
|
+
});
|
|
144
259
|
}
|
|
145
260
|
catch (err) {
|
|
146
261
|
refusal = err?.message || String(err);
|
|
147
262
|
}
|
|
148
|
-
if (
|
|
263
|
+
if (!grant) {
|
|
149
264
|
console.warn(`[formio-uag] The database (MONGO) is not used: ${refusal} Runs after the save are kept in memory, and remote MCP sign-in (MCP_OAUTH) is off.`);
|
|
150
|
-
this.config.licenseGrace = undefined;
|
|
151
265
|
await database.close().catch(() => { });
|
|
152
266
|
}
|
|
153
267
|
else {
|
|
154
268
|
await database.bootstrap();
|
|
155
269
|
this.database = database;
|
|
156
|
-
(0, lodash_1.set)(this.config, 'auth.authCodeStore',
|
|
157
|
-
|
|
270
|
+
(0, lodash_1.set)(this.config, 'auth.authCodeStore', grant.authCodeStore);
|
|
271
|
+
if (grant.taskRuns)
|
|
272
|
+
(0, runner_1.taskRunner)(project).useStore(grant.taskRuns);
|
|
158
273
|
console.log(`[formio-uag] Using the database "${database.name}": runs are recorded, and runs that act with an agent role survive a restart.` +
|
|
159
274
|
(this.config.auth?.mcpOAuth ? ' Remote MCP sign-in (MCP_OAUTH) is on.' : ''));
|
|
160
275
|
}
|
|
@@ -163,6 +278,26 @@ class UAGServer extends appserver_1.Server {
|
|
|
163
278
|
console.warn('[formio-uag] MCP_OAUTH is set, but remote MCP sign-in is off: it needs the UAG\'s database (MONGO), and a license that includes the UAG to use it.');
|
|
164
279
|
}
|
|
165
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* An Agent API request that failed before its handlers (a body that is not JSON,
|
|
283
|
+
* one over the size limit) is answered in the Agent API's error shape; every other
|
|
284
|
+
* route keeps Form.io's plain-text answer.
|
|
285
|
+
*/
|
|
286
|
+
sendError(req, res, status, message, err) {
|
|
287
|
+
if (!(0, errors_1.isAgentApiRequest)(req) && !this.isModuleRouteRequest(req))
|
|
288
|
+
return super.sendError(req, res, status, message, err);
|
|
289
|
+
const error = err?.type === 'entity.parse.failed' ? 'The request body is not valid JSON.' : message;
|
|
290
|
+
// Written past any shaping wrapper, which may not have run for this request.
|
|
291
|
+
res.status(status).type('application/json').end(JSON.stringify((0, errors_1.errorBody)((0, errors_1.requestIdOf)(req, res), status, { error })));
|
|
292
|
+
}
|
|
293
|
+
/** Whether a request is for a module's form route: its method, and a path ending in the route's. */
|
|
294
|
+
isModuleRouteRequest(req) {
|
|
295
|
+
const path = String(req.path || '').toLowerCase().replace(/\/+$/, '');
|
|
296
|
+
return this.modules.list.some((module) => Object.keys(module.definition.routes?.form || {}).some((key) => {
|
|
297
|
+
const [method, route] = key.split(' ');
|
|
298
|
+
return req.method === method && path.endsWith(String(route).toLowerCase().replace(/\/+$/, ''));
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
166
301
|
/** Browsers reading Agent API responses need the request id too. */
|
|
167
302
|
get exposedHeaders() {
|
|
168
303
|
return [...super.exposedHeaders, 'X-Request-Id'];
|
|
@@ -178,9 +313,15 @@ class UAGServer extends appserver_1.Server {
|
|
|
178
313
|
* on the form's own routes.
|
|
179
314
|
*/
|
|
180
315
|
async extendRouter(router) {
|
|
316
|
+
// A bare function in config.tools (the unreleased factory form) stops startup.
|
|
317
|
+
(0, utils_1.assertToolEntries)(this.config.tools);
|
|
181
318
|
await super.extendRouter(router);
|
|
319
|
+
// @formio/uag-js, the portal script and stylesheet: public, ahead of the project routes.
|
|
320
|
+
router.use((0, uagJs_1.uagJsRouter)(() => this.plugins, () => this.modules.components()));
|
|
182
321
|
const uagProject = this.uagProject;
|
|
183
322
|
if (uagProject) {
|
|
323
|
+
// First: the Agent API's request id and one error shape, for every refusal after it.
|
|
324
|
+
router.use((0, errors_1.agentErrorShape)());
|
|
184
325
|
router.use(uagProject.agentRouter());
|
|
185
326
|
// An agent operation of a form the project does not have is answered here, not passed to Form.io.
|
|
186
327
|
router.use((0, router_1.agentNotFound)(uagProject));
|
|
@@ -188,6 +329,52 @@ class UAGServer extends appserver_1.Server {
|
|
|
188
329
|
router.use((0, actionLogs_1.ActionLogRouter)(uagProject));
|
|
189
330
|
}
|
|
190
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* List the UAG's engines and MCP tools in the plugin registry, beside
|
|
334
|
+
* appserver-core's form actions. They keep loading their own way.
|
|
335
|
+
*/
|
|
336
|
+
async listPlugins() {
|
|
337
|
+
await super.listPlugins();
|
|
338
|
+
// Converters: the converter type and the built-in converters, for task file inputs, read_document and modules.
|
|
339
|
+
(0, plugins_2.registerDocuments)(this.plugins);
|
|
340
|
+
const project = this.uagProject;
|
|
341
|
+
if (project instanceof UAGProjectInterface_1.UAGProjectInterface)
|
|
342
|
+
project.plugins = this.plugins;
|
|
343
|
+
if (project instanceof UAGProjectInterface_1.UAGProjectInterface) {
|
|
344
|
+
// Decide which engines are on before serving anything, so a misconfigured
|
|
345
|
+
// engine stops startup with its name in the error instead of failing the
|
|
346
|
+
// first request.
|
|
347
|
+
project.engines = await (0, engines_1.loadUAGEngines)(project.config || {}, process.env);
|
|
348
|
+
enginesLoaded.add(project);
|
|
349
|
+
(0, plugins_1.listEngines)(this.plugins, project);
|
|
350
|
+
await (0, plugins_1.listTools)(this.plugins, project);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** A form's own route segments, which module routes may not use: appserver's, and the agent operations. */
|
|
354
|
+
get reservedFormPaths() {
|
|
355
|
+
return [...super.reservedFormPaths, 'agent'];
|
|
356
|
+
}
|
|
357
|
+
/** Module routes may not take the names of the UAG's own rate-limited operations. */
|
|
358
|
+
get reservedRouteNames() {
|
|
359
|
+
return modules_1.UAG_OPERATIONS;
|
|
360
|
+
}
|
|
361
|
+
get hostName() {
|
|
362
|
+
return 'uag';
|
|
363
|
+
}
|
|
364
|
+
get hostVersion() {
|
|
365
|
+
return version_1.UAG_VERSION;
|
|
366
|
+
}
|
|
367
|
+
/** The module API: appserver's, with the UAG's license check, engines, converters and helpers. */
|
|
368
|
+
createModuleApi(module) {
|
|
369
|
+
return (0, modules_1.uagModuleApi)(super.createModuleApi(module), (0, modules_1.serverDeps)(() => this.uagProject));
|
|
370
|
+
}
|
|
371
|
+
/** Module routes run behind what the agent operations run behind: the caller check, body limit, request id, rate limits and error shape. */
|
|
372
|
+
moduleRouteHandlers(project, route, module) {
|
|
373
|
+
return (0, modules_1.uagRouteHandlers)(project, route.name);
|
|
374
|
+
}
|
|
375
|
+
moduleErrorBody(req, err) {
|
|
376
|
+
return (0, modules_1.uagErrorBody)(req, err);
|
|
377
|
+
}
|
|
191
378
|
async router() {
|
|
192
379
|
await this.connectDatabase();
|
|
193
380
|
const router = await super.router();
|
|
@@ -205,10 +392,11 @@ class UAGServer extends appserver_1.Server {
|
|
|
205
392
|
}
|
|
206
393
|
// A database of the UAG's own needs the license, on every edition.
|
|
207
394
|
await this.enableDatabase(uagProject);
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
395
|
+
// The engines are loaded while the plugins are listed (listPlugins); a
|
|
396
|
+
// project interface that is not the UAG's own loads them here.
|
|
397
|
+
if (!enginesLoaded.has(uagProject)) {
|
|
398
|
+
uagProject.engines = await (0, engines_1.loadUAGEngines)(uagProject.config || {}, process.env);
|
|
399
|
+
}
|
|
212
400
|
// Say which edition this is, and whether Agentic Formflow will run, so an
|
|
213
401
|
// operator knows before the first request is refused.
|
|
214
402
|
// A failure here only loses the message; it never stops the UAG starting.
|
|
@@ -232,5 +420,19 @@ __exportStar(require("@formio/appserver"), exports);
|
|
|
232
420
|
__exportStar(require("./config"), exports);
|
|
233
421
|
__exportStar(require("./agent"), exports);
|
|
234
422
|
__exportStar(require("./tools"), exports);
|
|
423
|
+
__exportStar(require("./documents"), exports);
|
|
424
|
+
__exportStar(require("./modules"), exports);
|
|
425
|
+
var markdown_1 = require("./markdown");
|
|
426
|
+
Object.defineProperty(exports, "sanitizeMarkdown", { enumerable: true, get: function () { return markdown_1.sanitizeMarkdown; } });
|
|
427
|
+
var pluginFiles_1 = require("./pluginFiles");
|
|
428
|
+
Object.defineProperty(exports, "usePluginFiles", { enumerable: true, get: function () { return pluginFiles_1.usePluginFiles; } });
|
|
429
|
+
Object.defineProperty(exports, "pluginFiles", { enumerable: true, get: function () { return pluginFiles_1.pluginFiles; } });
|
|
430
|
+
Object.defineProperty(exports, "pluginFilesIn", { enumerable: true, get: function () { return pluginFiles_1.pluginFilesIn; } });
|
|
431
|
+
Object.defineProperty(exports, "DEFAULT_PLUGINS_DIR", { enumerable: true, get: function () { return pluginFiles_1.DEFAULT_PLUGINS_DIR; } });
|
|
235
432
|
__exportStar(require("./router"), exports);
|
|
236
433
|
__exportStar(require("./template"), exports);
|
|
434
|
+
var richText_1 = require("./richText");
|
|
435
|
+
Object.defineProperty(exports, "sanitizeRichText", { enumerable: true, get: function () { return richText_1.sanitizeRichText; } });
|
|
436
|
+
Object.defineProperty(exports, "isRichText", { enumerable: true, get: function () { return richText_1.isRichText; } });
|
|
437
|
+
Object.defineProperty(exports, "RICH_TEXT_TAGS", { enumerable: true, get: function () { return richText_1.RICH_TEXT_TAGS; } });
|
|
438
|
+
Object.defineProperty(exports, "RICH_TEXT_RULE", { enumerable: true, get: function () { return richText_1.RICH_TEXT_RULE; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeMarkdown(markdown: string): string;
|