@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
package/lib/index.d.ts CHANGED
@@ -1,17 +1,114 @@
1
1
  import { Server, ServerModule } from '@formio/appserver';
2
+ import type { LoadedModule, ModuleApi, ModuleRoute, ModuleRouteHandlers, ProjectInterface } from '@formio/appserver';
3
+ import { Request, Response, Router } from 'express';
2
4
  import { UAGProjectInterface } from './UAGProjectInterface';
3
5
  import { UAGFormInterface } from './UAGFormInterface';
4
6
  import { UAGConfig } from './config';
5
- export type UAGModule = ServerModule & {
7
+ import { UAGDatabase } from './database';
8
+ import type { UAGModuleApi } from './modules';
9
+ export type UAGModule = ServerModule<UAGModuleApi> & {
6
10
  config?: UAGConfig;
7
11
  };
12
+ /**
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.
18
+ */
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;
8
26
  export declare class UAGServer extends Server {
27
+ config: UAGConfig;
28
+ /** The UAG's own database, once it may be used. */
29
+ database?: UAGDatabase;
30
+ /** The database between connecting and the license check. */
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;
9
49
  constructor(config?: UAGConfig);
50
+ /**
51
+ * Connect the UAG's own database before the project loads, and check it
52
+ * without writing: a database the UAG may not use stops startup. It is used
53
+ * (its collections created, its stores handed out) only once the license is
54
+ * known; see enableDatabase. Until then nothing writes to it.
55
+ */
56
+ protected connectDatabase(): Promise<void>;
57
+ /**
58
+ * Use the database once the project and its license are loaded: with a
59
+ * license that includes the UAG (or the project and the database both on
60
+ * development hosts), create its collections and hand out its stores;
61
+ * otherwise leave it untouched, close it, and say why.
62
+ */
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;
72
+ /** Browsers reading Agent API responses need the request id too. */
73
+ get exposedHeaders(): string[];
74
+ /** The loaded UAG project, once the project router has been built. */
75
+ get uagProject(): UAGProjectInterface | undefined;
76
+ /**
77
+ * Mount `/agent/capabilities` ahead of the project routes. The project routes,
78
+ * and the admin passthrough after them, would otherwise answer for it and
79
+ * forward admin requests to the Form.io server. A form's agent operations are
80
+ * on the form's own routes.
81
+ */
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
+ };
10
101
  router(): Promise<any>;
11
102
  }
12
103
  export * from '@formio/appserver';
13
104
  export * from './config';
105
+ export * from './agent';
14
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';
15
111
  export * from './router';
16
112
  export * from './template';
113
+ export { sanitizeRichText, isRichText, RICH_TEXT_TAGS, RICH_TEXT_RULE } from './richText';
17
114
  export { UAGProjectInterface, UAGFormInterface };
package/lib/index.js CHANGED
@@ -13,35 +13,372 @@ 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;
21
+ exports.assertSingleCore = assertSingleCore;
22
+ exports.assertNodeSnapshotDisabled = assertNodeSnapshotDisabled;
18
23
  const appserver_1 = require("@formio/appserver");
24
+ const formflow_1 = require("./formflow");
25
+ const path_1 = __importDefault(require("path"));
19
26
  const UAGProjectInterface_1 = require("./UAGProjectInterface");
20
27
  Object.defineProperty(exports, "UAGProjectInterface", { enumerable: true, get: function () { return UAGProjectInterface_1.UAGProjectInterface; } });
21
28
  const UAGFormInterface_1 = require("./UAGFormInterface");
22
29
  Object.defineProperty(exports, "UAGFormInterface", { enumerable: true, get: function () { return UAGFormInterface_1.UAGFormInterface; } });
23
30
  const lodash_1 = require("lodash");
31
+ const engines_1 = require("./agent/engines");
32
+ const router_1 = require("./agent/router");
33
+ const errors_1 = require("./agent/errors");
34
+ const database_1 = require("./database");
35
+ const runs_1 = require("./runs");
36
+ const actionLogs_1 = require("./actionLogs");
37
+ const runner_1 = require("./runner");
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();
48
+ const debug = require('debug')('formio:uag');
49
+ /**
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.
55
+ */
56
+ function assertSingleCore(version = appserverCoreVersion()) {
57
+ const [major, minor, patch] = version.split('.').map((part) => parseInt(part, 10) || 0);
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.');
93
+ }
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.');
98
+ }
99
+ }
100
+ /** The UAG's own database from the environment, when `MONGO` is set. */
101
+ function databaseFromEnv(env = process.env) {
102
+ const url = (0, lodash_1.get)(env, 'MONGO', '').toString();
103
+ if (!url)
104
+ return undefined;
105
+ const retention = (0, lodash_1.get)(env, 'TASK_RUN_RETENTION_DAYS', '').toString();
106
+ return {
107
+ url,
108
+ name: (0, lodash_1.get)(env, 'UAG_DATABASE', '').toString() || 'uag',
109
+ config: (0, lodash_1.get)(env, 'MONGO_CONFIG', '').toString() || undefined,
110
+ ...(retention && { retentionDays: parseInt(retention, 10) })
111
+ };
112
+ }
24
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
+ }
25
161
  constructor(config) {
162
+ assertNodeSnapshotDisabled();
163
+ assertSingleCore();
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());
167
+ const uagLicense = (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString();
168
+ if (uagLicense) {
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.');
170
+ }
26
171
  super((0, lodash_1.defaultsDeep)(config || {}, {
27
172
  baseUrl: (0, lodash_1.get)(process.env, 'BASE_URL', '').toString(),
28
- license: (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString(),
173
+ // Deprecated: core also reads LICENSE_KEY, the Form.io license, and a
174
+ // feature is on when either key grants it.
175
+ license: uagLicense,
176
+ licenseName: 'UAG_LICENSE',
177
+ // The license server counts each UAG environment as a "uagServer", and the
178
+ // UAG runs on the "uag" feature: a key the license server accepts as a
179
+ // uagServer, or one whose terms carry the "uag" option.
180
+ licenseType: 'uagServer',
181
+ licenseFeature: 'uag',
29
182
  // A project that is still starting up (a Form.io server in the same compose
30
183
  // file, for example) is retried with backoff before the load is given up on.
31
184
  retries: parseInt((0, lodash_1.get)(process.env, 'PROJECT_RETRIES', 5).toString(), 10),
32
185
  retryDelay: parseInt((0, lodash_1.get)(process.env, 'PROJECT_RETRY_DELAY', 5000).toString(), 10),
33
186
  submissionProxy: true,
34
187
  processAsClient: true,
35
- auth: { pkce: 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(),
192
+ auth: { pkce: true, mcpOAuth: (0, lodash_1.get)(process.env, 'MCP_OAUTH', '').toString() === 'true' },
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) }
36
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");
203
+ // MONGO is the UAG's own database, not a database for the appserver to keep
204
+ // submissions in: Form.io stores those.
205
+ if (!configuredDb && this.module.db) {
206
+ delete this.module.db;
207
+ delete this.config.db;
208
+ }
209
+ warnAboutSettings(this.config, secretGiven);
37
210
  this.use({
38
211
  ProjectInterface: UAGProjectInterface_1.UAGProjectInterface,
39
- FormInterface: UAGFormInterface_1.UAGFormInterface
212
+ FormInterface: UAGFormInterface_1.UAGFormInterface,
213
+ actions: { agentRunTask: runTask_1.RunTaskAction },
214
+ hooks: (0, runTask_1.runTaskHooks)()
40
215
  });
41
216
  }
217
+ /**
218
+ * Connect the UAG's own database before the project loads, and check it
219
+ * without writing: a database the UAG may not use stops startup. It is used
220
+ * (its collections created, its stores handed out) only once the license is
221
+ * known; see enableDatabase. Until then nothing writes to it.
222
+ */
223
+ async connectDatabase() {
224
+ const options = this.config.database;
225
+ if (!options?.url) {
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.');
227
+ return;
228
+ }
229
+ if (this.database || this.pendingDatabase)
230
+ return;
231
+ const database = await database_1.UAGDatabase.connect(options);
232
+ try {
233
+ await database.inspect();
234
+ }
235
+ catch (err) {
236
+ await database.close().catch(() => { });
237
+ throw err;
238
+ }
239
+ this.pendingDatabase = database;
240
+ }
241
+ /**
242
+ * Use the database once the project and its license are loaded: with a
243
+ * license that includes the UAG (or the project and the database both on
244
+ * development hosts), create its collections and hand out its stores;
245
+ * otherwise leave it untouched, close it, and say why.
246
+ */
247
+ async enableDatabase(project) {
248
+ const database = this.pendingDatabase;
249
+ this.pendingDatabase = undefined;
250
+ if (database) {
251
+ let refusal = '';
252
+ let grant;
253
+ try {
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
+ });
259
+ }
260
+ catch (err) {
261
+ refusal = err?.message || String(err);
262
+ }
263
+ if (!grant) {
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.`);
265
+ await database.close().catch(() => { });
266
+ }
267
+ else {
268
+ await database.bootstrap();
269
+ this.database = database;
270
+ (0, lodash_1.set)(this.config, 'auth.authCodeStore', grant.authCodeStore);
271
+ if (grant.taskRuns)
272
+ (0, runner_1.taskRunner)(project).useStore(grant.taskRuns);
273
+ console.log(`[formio-uag] Using the database "${database.name}": runs are recorded, and runs that act with an agent role survive a restart.` +
274
+ (this.config.auth?.mcpOAuth ? ' Remote MCP sign-in (MCP_OAUTH) is on.' : ''));
275
+ }
276
+ }
277
+ if (this.config.auth?.mcpOAuth && !this.database) {
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.');
279
+ }
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
+ }
301
+ /** Browsers reading Agent API responses need the request id too. */
302
+ get exposedHeaders() {
303
+ return [...super.exposedHeaders, 'X-Request-Id'];
304
+ }
305
+ /** The loaded UAG project, once the project router has been built. */
306
+ get uagProject() {
307
+ return Object.values(this.projectRouter?.projects || {})[0];
308
+ }
309
+ /**
310
+ * Mount `/agent/capabilities` ahead of the project routes. The project routes,
311
+ * and the admin passthrough after them, would otherwise answer for it and
312
+ * forward admin requests to the Form.io server. A form's agent operations are
313
+ * on the form's own routes.
314
+ */
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);
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()));
321
+ const uagProject = this.uagProject;
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)());
325
+ router.use(uagProject.agentRouter());
326
+ // An agent operation of a form the project does not have is answered here, not passed to Form.io.
327
+ router.use((0, router_1.agentNotFound)(uagProject));
328
+ // Action Logs in a connected portal stage, with the UAG's run records.
329
+ router.use((0, actionLogs_1.ActionLogRouter)(uagProject));
330
+ }
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
+ }
42
378
  async router() {
379
+ await this.connectDatabase();
43
380
  const router = await super.router();
44
- const uagProject = Object.values(this.projectRouter?.projects || {})[0];
381
+ const uagProject = this.uagProject;
45
382
  // Without a project there are no forms to expose, so there is nothing to
46
383
  // serve. Say why: this used to throw "Cannot read properties of undefined
47
384
  // (reading 'uagRouter')", which told the operator nothing about the
@@ -49,16 +386,53 @@ class UAGServer extends appserver_1.Server {
49
386
  if (!uagProject) {
50
387
  throw new Error('No Form.io project could be loaded, so the UAG has nothing to serve. Check that PROJECT ' +
51
388
  'points at a reachable Form.io deployment, and that it is paired with the right ' +
52
- 'credential — ADMIN_KEY for an Open Source server, or PROJECT_KEY plus UAG_LICENSE for ' +
53
- 'an Enterprise project. Earlier log lines report why the project load failed.');
389
+ 'credential — ADMIN_KEY for an Open Source server, PROJECT_KEY for a project on Form.io\'s ' +
390
+ 'hosted service, or PROJECT_KEY plus a license (LICENSE_KEY) for a self-hosted Enterprise project. ' +
391
+ 'Earlier log lines report why the project load failed.');
392
+ }
393
+ // A database of the UAG's own needs the license, on every edition.
394
+ await this.enableDatabase(uagProject);
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
+ }
400
+ // Say which edition this is, and whether Agentic Formflow will run, so an
401
+ // operator knows before the first request is refused.
402
+ // A failure here only loses the message; it never stops the UAG starting.
403
+ try {
404
+ const formflowState = await (0, formflow_1.formflow)(uagProject).state();
405
+ // A project on Form.io's hosted service runs under Form.io's license.
406
+ const hosted = formflowState.reason === 'hosted' ? ', on Form.io\'s hosted service (no license needed)' : '';
407
+ const reason = formflowState.licensedBy ? `licensed by ${formflowState.licensedBy}` : formflowState.reason;
408
+ console.log(`[formio-uag] ${formflowState.edition === 'enterprise' ? 'Enterprise' : 'Open Source'} edition${hosted}. Agentic Formflow ` +
409
+ (formflowState.allowed ? `is enabled (${reason}).` : 'requires a license (LICENSE_KEY) that includes the UAG, or a project on Form.io\'s hosted service; MCP and conversation forms are unaffected.'));
410
+ }
411
+ catch (err) {
412
+ debug(`Could not report the edition: ${err?.message || err}`);
54
413
  }
55
- router.use(uagProject.uagRouter());
414
+ router.use(uagProject.uagRouter(false));
56
415
  return router;
57
416
  }
58
417
  }
59
418
  exports.UAGServer = UAGServer;
60
419
  __exportStar(require("@formio/appserver"), exports);
61
420
  __exportStar(require("./config"), exports);
421
+ __exportStar(require("./agent"), exports);
62
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; } });
63
432
  __exportStar(require("./router"), exports);
64
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;