@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/README.md
CHANGED
|
@@ -53,9 +53,9 @@ A person talks to Claude, ChatGPT or your own chat app, and the agent fills in a
|
|
|
53
53
|
|
|
54
54
|
### Agentic workflows
|
|
55
55
|
|
|
56
|
-
An application saves a submission through the UAG; an **Agent: Run task** action runs the form's task after the save and saves the result, which can start the next task. [Try it](https://ai.form.io/examples/workflow) · [source](./demo/
|
|
56
|
+
An application saves a submission through the UAG; an **Agent: Run task** action runs the form's task after the save and saves the result, which can start the next task. [Try it](https://ai.form.io/examples/workflow) · [source](./demo/public/apps/workflow/workflow.js) · build it with [Workflows](https://ai.form.io/developers/workflows) · [prompt for your coding agent](https://github.com/formio/ai/blob/main/examples/agents/admissions-workflow.md).
|
|
57
57
|
|
|
58
|
-

|
|
59
59
|
|
|
60
60
|
### Agentic Formflow
|
|
61
61
|
|
|
@@ -63,7 +63,7 @@ A person describes what happened, and the form fills itself in: decisions choose
|
|
|
63
63
|
|
|
64
64
|

|
|
65
65
|
|
|
66
|
-
One deployment can serve all three. Conversation forms run on Form.io Open Source with no license; the other two need a license or a hosted project (see [License](#license)). The whole demo site,
|
|
66
|
+
One deployment can serve all three. Conversation forms run on Form.io Open Source with no license; the other two need a license or a hosted project (see [License](#license)). The whole demo site, static pages that call the UAG straight from the browser, with a flow log of every call they make, is in [`demo/`](./demo); these recordings are made from it by [`demo/recordings`](./demo/recordings).
|
|
67
67
|
|
|
68
68
|
## Quickstart
|
|
69
69
|
|
|
@@ -90,7 +90,7 @@ services:
|
|
|
90
90
|
retries: 40
|
|
91
91
|
|
|
92
92
|
formio-uag:
|
|
93
|
-
image: formio/uag
|
|
93
|
+
image: formio/uag:rc
|
|
94
94
|
depends_on:
|
|
95
95
|
formio: { condition: service_healthy }
|
|
96
96
|
ports: ["3200:3200"]
|
|
@@ -243,17 +243,18 @@ Engines run inside the UAG, or on an [engine server](./packages/engine-server) (
|
|
|
243
243
|
| You run | You need |
|
|
244
244
|
|---|---|
|
|
245
245
|
| MCP and conversation forms (including a form's chat, `{form}/agent/chat`), on Form.io Open Source or a hosted project | Nothing |
|
|
246
|
-
| MCP and conversation forms, on a self-hosted Form.io Enterprise project | A Form.io license in `LICENSE_KEY` (development hostnames such as `localhost` excepted) |
|
|
246
|
+
| MCP and conversation forms, on a self-hosted Form.io Enterprise project | A Form.io license that includes the UAG, in `LICENSE_KEY` (development hostnames such as `localhost` excepted) |
|
|
247
247
|
| Agentic workflows and Agentic Formflow (the Agent API's `extract`, `decide` and `generate`, the **Agent: Run task** action, the `agent_decide` and `agent_provide` tools, `uag-eval`) on a self-hosted project, Open Source or Enterprise | A Form.io license that includes the UAG, in `LICENSE_KEY` |
|
|
248
248
|
| The same on a project on Form.io's hosted service (`https://*.form.io`) | Nothing, as the project runs under Form.io's license; except `uag-eval`, which runs offline and needs `LICENSE_KEY` |
|
|
249
|
-
| The UAG's own database (`MONGO`), and with it remote MCP sign-in (`MCP_OAUTH`), on any project | A Form.io license that includes the UAG, unless the project and the database
|
|
249
|
+
| The UAG's own database (`MONGO`), and with it remote MCP sign-in (`MCP_OAUTH`), on any project | A Form.io license that includes the UAG, unless the UAG, the project and the database all run on one machine (`localhost`, `*.localhost`, `lvh.me`) |
|
|
250
250
|
| The Agent API, Run task and `uag-eval` with only the stub engines (`UAG_ENGINES=stub-decision,stub-agent`), to try them out | Nothing. The `agent_decide` and `agent_provide` tools still need a license or a hosted project. |
|
|
251
251
|
|
|
252
|
-
A hosted project needs a license
|
|
252
|
+
A hosted project needs a license when `NODE_TLS_REJECT_UNAUTHORIZED=0`, `NODE_EXTRA_CA_CERTS` or `NODE_USE_SYSTEM_CA` is set. Without a license, `MONGO` is not used: the UAG starts with its database features off and says why. Unlicensed requests get `403` with `code: "license_required"`, and `GET /agent/capabilities` always reports the edition and why. `UAG_LICENSE` is deprecated in favour of `LICENSE_KEY`, and still read until UAG 3.0. Details: [Deployment → Licensing](https://ai.form.io/developers/deployment#licensing).
|
|
253
253
|
|
|
254
254
|
## Extending the UAG
|
|
255
255
|
|
|
256
|
-
- **Modules** add MCP tools, override the built-in tools and their response templates, add form actions (the UAG runs a module's own actions; Form.io runs its own), and ship a project template of forms and resources that the UAG installs. Mount one over `/app/module` in the container, or name it in `UAG_MODULE` for the `uag` command. See [Modules](https://ai.form.io/developers/modules), [`module/Readme.md`](./module/Readme.md) and the [Claude Desktop example](./demo/samples/custom-module).
|
|
256
|
+
- **Modules** add MCP tools, override the built-in tools and their response templates, add form actions (the UAG runs a module's own actions; Form.io runs its own), and ship a project template of forms and resources that the UAG installs. A module can also add routes on every form, formio.js components (served in the UAG's `/uag-js/uag.use.js`), plugins, and settings of its own. Mount one over `/app/module` in the container, or name it in `UAG_MODULE` for the `uag` command. See [Modules](https://ai.form.io/developers/modules), [`module/Readme.md`](./module/Readme.md) and the [Claude Desktop example](./demo/samples/custom-module).
|
|
257
|
+
- **Plugins** add capabilities of a known type, such as converters that read more file types and the document processes an AI Document runs. List them in a module, or in a plugins file: the one `UAG_PLUGINS` names, or in the image, one per folder under `/app/plugins`, where add-ons such as `formio/uag-documents` put theirs. Plugins and modules are trusted code: they run in the UAG process. See [Plugins](https://ai.form.io/developers/plugins) and [AI Document](https://ai.form.io/developers/ai-document).
|
|
257
258
|
- **Node.js.** Embed the UAG in an Express application, or run it with `npx uag`:
|
|
258
259
|
|
|
259
260
|
```js
|
|
@@ -267,7 +268,7 @@ app.use(await server.router());
|
|
|
267
268
|
app.listen(process.env.PORT || 3200);
|
|
268
269
|
```
|
|
269
270
|
|
|
270
|
-
Run Node 22 or newer with `--no-node-snapshot` (isolated-vm, which runs form logic, needs it), and install the engines you use next to the UAG (`npm install @formio/uag-claude`). See [Node.js API](https://ai.form.io/developers/node-api).
|
|
271
|
+
Run Node 22 or newer with `--no-node-snapshot` (isolated-vm, which runs form logic, needs it), and install the engines you use next to the UAG (`npm install @formio/uag-claude@rc`). See [Node.js API](https://ai.form.io/developers/node-api).
|
|
271
272
|
- **[`@formio/uag-js`](./packages/uag-js)** connects a formio.js application to the UAG. Register it once with `Formio.use(UAG('https://uag.example.com'))`: submissions of forms tagged `uag` are then saved through the UAG, and Agent Input fills those forms from what the user wrote, through `{form}/agent/extract`, when they leave the input, pause, move to the next page, or press a button. `Formio.createChat(element, formUrl)` renders any of them as a chat instead, templated, translated and themed like a formio.js form. See [JavaScript (formio.js)](https://ai.form.io/developers/renderer) and [Chat widget](https://ai.form.io/developers/chat).
|
|
272
273
|
|
|
273
274
|
## Deployment
|
|
@@ -288,7 +289,7 @@ A local client has no browser, so it exchanges a key for a token at the UAG's `/
|
|
|
288
289
|
| `PROJECT` | **Required.** How the UAG reaches Form.io: the Open Source server's URL, or an Enterprise or hosted project's endpoint. A container-to-container address is fine. |
|
|
289
290
|
| `ADMIN_KEY` or `PROJECT_KEY` | **Required.** `ADMIN_KEY` for Open Source; a project API key (**Project Settings → API Keys**) for Enterprise or hosted. Setting `PROJECT_KEY` is what makes the UAG treat `PROJECT` as an Enterprise project. |
|
|
290
291
|
| `JWT_SECRET` | **Required.** Signs the tokens the UAG issues. The same as the Form.io Enterprise server's lets an agent act on users' behalf (see [Project setup for agents](https://ai.form.io/developers/project-setup)). |
|
|
291
|
-
| `MONGO`, `UAG_DATABASE` | Optional. The UAG's own database (default name `uag`; it can share Form.io's MongoDB), for run records in Action Logs, runs that survive a restart, remote MCP sign-in
|
|
292
|
+
| `MONGO`, `UAG_DATABASE` | Optional. The UAG's own database (default name `uag`; it can share Form.io's MongoDB), for run records in Action Logs, runs that survive a restart, and remote MCP sign-in. It holds only `uag-*` collections, never submissions. Needs a license. |
|
|
292
293
|
| `MCP_OAUTH` | `true` turns on OAuth sign-in for remote MCP clients. Needs `MONGO`. |
|
|
293
294
|
| `TRUST_PROXY` | Optional. Set behind a reverse proxy, so the UAG reads its public URL from the forwarded headers. `BASE_URL`, also optional, overrides that URL. |
|
|
294
295
|
| `PORTAL_SECRET` | Enterprise. Lets a portal stage connect to the UAG, so you can add Run task actions there. |
|
|
@@ -319,11 +320,12 @@ The full table, with the Agent API's errors: [Troubleshooting](https://ai.form.i
|
|
|
319
320
|
| Package | Version | Works with UAG | What it is |
|
|
320
321
|
|---|---|---|---|
|
|
321
322
|
| [`@formio/uag`](https://www.npmjs.com/package/@formio/uag), image [`formio/uag`](https://hub.docker.com/r/formio/uag) | 2.x | — | The UAG. The image includes every official engine. |
|
|
322
|
-
| `@formio/uag-jev`, `-claude`, `-openai`, `-grok`, `-gemini` | 2.x | ≥ 2.0.0 | The [engines](./engines),
|
|
323
|
+
| `@formio/uag-jev`, `-claude`, `-openai`, `-grok`, `-gemini` | 2.x | ≥ 2.0.0 | The [engines](./engines), released with the UAG at the same version. |
|
|
323
324
|
| [`@formio/uag-sdk`](./packages/engine-sdk) | 2.x | ≥ 2.0.0 | Engine contract version 1, remote engine protocol version 1. |
|
|
324
325
|
| [`@formio/uag-engines`](./packages/engine-server), image `formio/uag-engines` | 2.x | ≥ 2.0.0 | The engine server. |
|
|
325
|
-
| [`@formio/uag-js`](./packages/uag-js) | 2.x | ≥ 2.0.0 | The formio.js module: saves `uag` forms through the UAG, adds Agent Input, and renders forms as a chat. Runs in the browser
|
|
326
|
-
| `@formio/uag-
|
|
326
|
+
| [`@formio/uag-js`](./packages/uag-js) | 2.x | ≥ 2.0.0 | The formio.js module: saves `uag` forms through the UAG, adds Agent Input, and renders forms as a chat. Runs in the browser; the UAG also serves it at `/uag-js/`. |
|
|
327
|
+
| [`@formio/uag-extras`](https://www.npmjs.com/package/@formio/uag-extras), image `formio/uag-documents` | 1.x | ≥ 2.0.0-rc.7 | Versioned on its own: the AI Document module, document plugins and converters (`npm install @formio/uag-extras@^1`). The image is an add-on that runs beside `formio/uag` and fills its plugins folder (`formio/uag-documents:1`): see [AI Document](https://ai.form.io/developers/ai-document#run-it-with-docker). |
|
|
328
|
+
| `@formio/uag-claude` | 1.3.x | 1.12.0 – 1.13.x | The retired Claude integration (against 2.0, only its direct commands). Security fixes only; see [MIGRATING-2.0.md](./MIGRATING-2.0.md). |
|
|
327
329
|
|
|
328
330
|
`@formio/uag-claude` 2.x is the Claude *engine*, not the 1.x integration; pin `@formio/uag-claude@^1` if you still install the integration. Upgrading from 1.x: [MIGRATING-2.0.md](./MIGRATING-2.0.md).
|
|
329
331
|
|
|
@@ -335,6 +337,6 @@ The developer documentation is published at [ai.form.io/developers](https://ai.f
|
|
|
335
337
|
|---|---|---|
|
|
336
338
|
| **Start here** | [How the UAG works](https://ai.form.io/developers) · [Agentic Coding Plugin](https://ai.form.io/developers/agentic-coding) · [Quickstart](https://ai.form.io/developers/quickstart) | [Demo site](./demo/README.md) |
|
|
337
339
|
| **Concepts** | [Concepts](https://ai.form.io/developers/concepts) · [Form properties](https://ai.form.io/developers/form-properties) | |
|
|
338
|
-
| **Build** | [MCP tools](https://ai.form.io/developers/mcp-tools) · [Agent API](https://ai.form.io/developers/agent-api) · [Workflows](https://ai.form.io/developers/workflows) · [JavaScript (formio.js)](https://ai.form.io/developers/renderer) · [Chat widget](https://ai.form.io/developers/chat) · [Connect Claude](https://ai.form.io/developers/connect-claude) | [`@formio/uag-js`](./packages/uag-js/README.md) · [Claude Desktop module](./demo/samples/custom-module/Readme.md) |
|
|
339
|
-
| **Extend** | [Node.js API](https://ai.form.io/developers/node-api) · [Modules](https://ai.form.io/developers/modules) · [Engines](https://ai.form.io/developers/engines) · [Engine SDK](https://ai.form.io/developers/engine-sdk) · [Engine server](https://ai.form.io/developers/engine-server) | [Modules](./module/Readme.md) · [Response templates](./demo/samples/custom-module/module/templates/Readme.md) · [Engines](./engines) · [Engine SDK](./packages/engine-sdk/README.md) · [Engine server](./packages/engine-server/README.md) · [Engine setups](./demo/samples/engines/Readme.md) |
|
|
340
|
+
| **Build** | [MCP tools](https://ai.form.io/developers/mcp-tools) · [Agent API](https://ai.form.io/developers/agent-api) · [Workflows](https://ai.form.io/developers/workflows) · [JavaScript (formio.js)](https://ai.form.io/developers/renderer) · [Chat widget](https://ai.form.io/developers/chat) · [AI Document](https://ai.form.io/developers/ai-document) · [Connect Claude](https://ai.form.io/developers/connect-claude) | [`@formio/uag-js`](./packages/uag-js/README.md) · [Claude Desktop module](./demo/samples/custom-module/Readme.md) |
|
|
341
|
+
| **Extend** | [Node.js API](https://ai.form.io/developers/node-api) · [Modules](https://ai.form.io/developers/modules) · [Plugins](https://ai.form.io/developers/plugins) · [Engines](https://ai.form.io/developers/engines) · [Engine SDK](https://ai.form.io/developers/engine-sdk) · [Engine server](https://ai.form.io/developers/engine-server) | [Modules](./module/Readme.md) · [Response templates](./demo/samples/custom-module/module/templates/Readme.md) · [Engines](./engines) · [Engine SDK](./packages/engine-sdk/README.md) · [Engine server](./packages/engine-server/README.md) · [Engine setups](./demo/samples/engines/Readme.md) |
|
|
340
342
|
| **Operate** | [Security](https://ai.form.io/developers/security) · [Deployment](https://ai.form.io/developers/deployment) · [Evaluation](https://ai.form.io/developers/evaluation) · [Troubleshooting](https://ai.form.io/developers/troubleshooting) | [Docker Hub](./DOCKERHUB.md) · [Migrating to 2.0](./MIGRATING-2.0.md) · [Security policy](./SECURITY.md) · [End-to-end tests](./test/e2e/Readme.md) |
|
package/bin/uag-eval.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* [--engine jev] [--json report.json]
|
|
7
7
|
*
|
|
8
8
|
* --form A Form.io form definition (JSON export).
|
|
9
|
-
* --task The task to evaluate (default: the form's first).
|
|
9
|
+
* --task The task to evaluate (default: the form's first).
|
|
10
10
|
* --examples A JSON array of { "id", "data": { ... }, "expected": { "<path>": <value or null> } }.
|
|
11
11
|
* null means "the input does not say", which expects the field to stay empty.
|
|
12
12
|
* --engine The decision engine to evaluate: an instance named in UAG_ENGINES, or an
|
|
@@ -25,7 +25,7 @@ require('dotenv/config');
|
|
|
25
25
|
const { readFileSync, writeFileSync } = require('fs');
|
|
26
26
|
const { parseInstances } = require('@formio/uag-sdk');
|
|
27
27
|
const { ProjectInterface, UAGProjectInterface, UAGFormInterface, loadUAGEngines } = require('../lib');
|
|
28
|
-
const { FormflowService, formatReport
|
|
28
|
+
const { FormflowService, formatReport } = require('../lib/formflow');
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* The instance to evaluate: the one UAG_ENGINES names `name`, so a deployed
|
|
@@ -51,7 +51,11 @@ async function main() {
|
|
|
51
51
|
console.error('Usage: uag-eval --form <form.json> --examples <examples.json> [--task <name>] [--engine <name>] [--json <out.json>] [--project <url>]');
|
|
52
52
|
process.exit(2);
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
if (opts.persona !== undefined) {
|
|
55
|
+
console.error('uag-eval: --persona was renamed --task in UAG 2.0.');
|
|
56
|
+
process.exit(2);
|
|
57
|
+
}
|
|
58
|
+
const task = opts.task || undefined;
|
|
55
59
|
const formDef = JSON.parse(readFileSync(opts.form, 'utf8'));
|
|
56
60
|
const examples = JSON.parse(readFileSync(opts.examples, 'utf8'));
|
|
57
61
|
const entry = engineEntry(opts.engine, process.env);
|
package/bin/uag.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Engines are found among the installed packages, so a custom image holds only
|
|
13
13
|
* the engines it installs:
|
|
14
14
|
*
|
|
15
|
-
* npm install @formio/uag @formio/uag-claude
|
|
15
|
+
* npm install @formio/uag@rc @formio/uag-claude@rc
|
|
16
16
|
* UAG_ENGINES=claude CLAUDE_API_KEY=... npx uag
|
|
17
17
|
*/
|
|
18
18
|
// Before anything else, so a module that reads the environment sees .env too.
|
|
@@ -37,9 +37,14 @@ function restartWithoutSnapshot() {
|
|
|
37
37
|
/** Start the UAG with a module, listening on PORT (default 3200). */
|
|
38
38
|
async function start(uagModule) {
|
|
39
39
|
const Express = require('express');
|
|
40
|
-
const { UAGServer, trustProxySetting } = require('../lib');
|
|
40
|
+
const { UAGServer, trustProxySetting, usePluginFiles } = require('../lib');
|
|
41
41
|
const server = new UAGServer();
|
|
42
|
-
|
|
42
|
+
// A module may be exported as a promise (an async module).
|
|
43
|
+
const resolved = await uagModule;
|
|
44
|
+
if (resolved) server.use(resolved);
|
|
45
|
+
// Then the plugins files: UAG_PLUGINS, or the image's /app/plugins (its
|
|
46
|
+
// index.mjs, or each add-on's folder under it).
|
|
47
|
+
await usePluginFiles(server);
|
|
43
48
|
const app = Express();
|
|
44
49
|
// Behind a reverse proxy, its forwarded headers give the public URL (TRUST_PROXY).
|
|
45
50
|
const trust = trustProxySetting(process.env.TRUST_PROXY);
|
|
@@ -48,11 +53,49 @@ async function start(uagModule) {
|
|
|
48
53
|
// names; a misconfigured engine stops startup with its name in the error.
|
|
49
54
|
app.use(await server.router());
|
|
50
55
|
const port = process.env.PORT || 3200;
|
|
51
|
-
await new Promise((resolve) =>
|
|
56
|
+
const listener = await new Promise((resolve) => {
|
|
57
|
+
const started = app.listen(port, () => resolve(started));
|
|
58
|
+
});
|
|
59
|
+
stopOnSignals(server, listener);
|
|
52
60
|
console.log(`Form.io UAG server running on port ${port}`);
|
|
53
61
|
console.log(`Visit http://localhost:${port} to access the application`);
|
|
54
62
|
}
|
|
55
63
|
|
|
64
|
+
/**
|
|
65
|
+
* On SIGTERM or SIGINT, stop taking connections and destroy the server, which
|
|
66
|
+
* calls each module's `close` and each plugin's, then exit. A shutdown that
|
|
67
|
+
* takes longer than `timeoutMs` exits with 1.
|
|
68
|
+
*/
|
|
69
|
+
function stopOnSignals(server, listener, { signals = ['SIGTERM', 'SIGINT'], exit = (code) => process.exit(code), on = process, timeoutMs = 10000 } = {}) {
|
|
70
|
+
let stopping = false;
|
|
71
|
+
const stop = async (signal) => {
|
|
72
|
+
if (stopping) return;
|
|
73
|
+
stopping = true;
|
|
74
|
+
console.log(`Received ${signal}: stopping the UAG.`);
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
console.error(`The UAG did not stop within ${timeoutMs} ms.`);
|
|
77
|
+
exit(1);
|
|
78
|
+
}, timeoutMs);
|
|
79
|
+
if (timer.unref) timer.unref();
|
|
80
|
+
if (listener) listener.close();
|
|
81
|
+
let code = 0;
|
|
82
|
+
try {
|
|
83
|
+
await server.destroy();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error('Failed to stop the UAG cleanly:', error);
|
|
87
|
+
code = 1;
|
|
88
|
+
}
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
exit(code);
|
|
91
|
+
};
|
|
92
|
+
// Not once: a repeated signal (a terminal's Ctrl-C reaches the restarted
|
|
93
|
+
// process twice, from the terminal and from its parent) must not fall back
|
|
94
|
+
// to Node's default handler and end the shutdown early.
|
|
95
|
+
for (const signal of signals) on.on(signal, () => stop(signal));
|
|
96
|
+
return stop;
|
|
97
|
+
}
|
|
98
|
+
|
|
56
99
|
function fail(error) {
|
|
57
100
|
console.error('Failed to start server:', error);
|
|
58
101
|
process.exit(1);
|
|
@@ -70,4 +113,4 @@ if (require.main === module) {
|
|
|
70
113
|
}
|
|
71
114
|
}
|
|
72
115
|
|
|
73
|
-
module.exports = { start, fail };
|
|
116
|
+
module.exports = { start, fail, stopOnSignals };
|
|
@@ -191,5 +191,17 @@ export declare class UAGFormInterface extends FormInterface implements FormflowF
|
|
|
191
191
|
path: string;
|
|
192
192
|
label: string;
|
|
193
193
|
}[];
|
|
194
|
+
/**
|
|
195
|
+
* A value an agent engine generated, before it is checked and written: HTML
|
|
196
|
+
* for a rich text field is reduced to the markup its editor keeps.
|
|
197
|
+
*/
|
|
198
|
+
formflowNormalizeValue(component: Component, value: any): any;
|
|
199
|
+
/**
|
|
200
|
+
* A value an MCP client writes at a data path (collect_field_data,
|
|
201
|
+
* confirm_form_submission, submit_completed_form, submission_update), as
|
|
202
|
+
* an engine's is: HTML for a rich text field is reduced to the markup its
|
|
203
|
+
* editor keeps. Row indices are ignored to find the component.
|
|
204
|
+
*/
|
|
205
|
+
normalizeWrittenValue(path: string, value: any): any;
|
|
194
206
|
htmlToMarkdown(html: string): string;
|
|
195
207
|
}
|
package/lib/UAGFormInterface.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UAGFormInterface = void 0;
|
|
4
4
|
const appserver_1 = require("@formio/appserver");
|
|
5
|
+
const formflow_1 = require("./formflow");
|
|
6
|
+
const richText_1 = require("./richText");
|
|
5
7
|
const core_1 = require("@formio/core");
|
|
6
8
|
const lodash_1 = require("lodash");
|
|
7
9
|
const node_html_markdown_1 = require("node-html-markdown");
|
|
@@ -11,6 +13,22 @@ const CHAT_UNSUPPORTED = new Set([
|
|
|
11
13
|
'datagrid', 'editgrid', 'datatable', 'dynamicWizard', 'tagpad', 'form', 'datamap', 'address',
|
|
12
14
|
'file', 'signature', 'sketchpad', 'datasource', 'password', 'captcha'
|
|
13
15
|
]);
|
|
16
|
+
/**
|
|
17
|
+
* The components inside a layout or container flagged with `uag`, which makes
|
|
18
|
+
* each input inside it one of the task's fields. Panels, fieldsets, wells, tabs
|
|
19
|
+
* and containers hold theirs in `components`; columns in each column's
|
|
20
|
+
* `components`; tables in each cell's. Null for a component that holds none.
|
|
21
|
+
*/
|
|
22
|
+
function layoutChildren(component) {
|
|
23
|
+
const comp = component;
|
|
24
|
+
if (Array.isArray(comp.components))
|
|
25
|
+
return comp.components;
|
|
26
|
+
if (Array.isArray(comp.columns))
|
|
27
|
+
return comp.columns.flatMap((column) => column?.components || []);
|
|
28
|
+
if (Array.isArray(comp.rows))
|
|
29
|
+
return comp.rows.flat().flatMap((cell) => cell?.components || []);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
14
32
|
class UAGFormInterface extends appserver_1.FormInterface {
|
|
15
33
|
/**
|
|
16
34
|
* The form's routes, with its agent operations (`agent/extract`, `agent/decide`,
|
|
@@ -67,8 +85,9 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
67
85
|
}
|
|
68
86
|
}
|
|
69
87
|
else {
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
const children = layoutChildren(component);
|
|
89
|
+
if (children) {
|
|
90
|
+
core_1.Utils.eachComponent(children, (subComp, subPath) => {
|
|
72
91
|
subPath = paths?.dataPath ? `${paths.dataPath}.${subPath}` : subPath;
|
|
73
92
|
const compInfo = this.getComponentInfo(subComp, subPath);
|
|
74
93
|
compInfo.rule = this.getComponentValueRule(subComp);
|
|
@@ -217,8 +236,12 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
217
236
|
case 'signature':
|
|
218
237
|
rule += 'Have the user draw their signature. The value will be a base64-encoded PNG image string of that signature.';
|
|
219
238
|
break;
|
|
220
|
-
case 'textfield':
|
|
221
239
|
case 'textarea':
|
|
240
|
+
rule += (0, richText_1.isRichText)(component)
|
|
241
|
+
? richText_1.RICH_TEXT_RULE
|
|
242
|
+
: 'The value can be any alphanumeric string, containing letters, numbers, white space characters, and common symbols.';
|
|
243
|
+
break;
|
|
244
|
+
case 'textfield':
|
|
222
245
|
case 'hidden':
|
|
223
246
|
rule += 'The value can be any alphanumeric string, containing letters, numbers, white space characters, and common symbols.';
|
|
224
247
|
break;
|
|
@@ -332,12 +355,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
332
355
|
* Override the process methods for the UAG.
|
|
333
356
|
*/
|
|
334
357
|
async process(submission, auth, headers = {}, findQuery = {}, additional = []) {
|
|
335
|
-
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
processors.splice(fetchIndex, 1);
|
|
340
|
-
}
|
|
358
|
+
// Leave out 'fetch', since the UAG handles it with the fetch_external_data tool, and
|
|
359
|
+
// 'dereference', which expands a "Save as reference" select or a Data Table from the
|
|
360
|
+
// database the UAG does not have: it would throw for every form with one.
|
|
361
|
+
const processors = [...core_1.Processors, ...additional].filter((p) => p.name !== 'fetch' && p.name !== 'dereference');
|
|
341
362
|
// Return the processed submission data.
|
|
342
363
|
const context = await super.process(submission, auth, headers, findQuery, processors);
|
|
343
364
|
// Filter the "required" validation since the UAG retrieves required fields separately.
|
|
@@ -519,7 +540,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
519
540
|
return;
|
|
520
541
|
if (component.calculateValue && !component.allowCalculateOverride)
|
|
521
542
|
return;
|
|
522
|
-
if (owned.has(path) || component.properties?.uagAuthoritative
|
|
543
|
+
if (owned.has(path) || (0, formflow_1.flagOn)(component.properties?.uagAuthoritative))
|
|
523
544
|
return;
|
|
524
545
|
result.fields.push({
|
|
525
546
|
...this.getComponentInfo(component, path),
|
|
@@ -580,7 +601,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
580
601
|
else if (value && this.isMultiple(comp) && typeof value === 'string') {
|
|
581
602
|
value = value.split(',').map((v) => v.trim());
|
|
582
603
|
}
|
|
583
|
-
(0, lodash_1.set)(submission.data, path, value);
|
|
604
|
+
(0, lodash_1.set)(submission.data, path, this.normalizeWrittenValue(path, value));
|
|
584
605
|
}
|
|
585
606
|
return submission;
|
|
586
607
|
}
|
|
@@ -605,7 +626,12 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
605
626
|
}
|
|
606
627
|
else if (this.inputComponent(component) && (0, core_1.componentHasValue)(component, value)) {
|
|
607
628
|
const modelType = core_1.Utils.getModelType(component);
|
|
608
|
-
if ((0,
|
|
629
|
+
if ((0, formflow_1.isBinaryComponent)(component)) {
|
|
630
|
+
// Files and signatures are described, never inlined: a base64 upload would fill the context.
|
|
631
|
+
const described = (0, formflow_1.describeBinary)(component, value);
|
|
632
|
+
value = (0, lodash_1.isObjectLike)(described) ? JSON.stringify(described) : described;
|
|
633
|
+
}
|
|
634
|
+
else if ((0, lodash_1.isObjectLike)(value)) {
|
|
609
635
|
value = JSON.stringify(value);
|
|
610
636
|
}
|
|
611
637
|
else if (modelType === 'string' && (0, lodash_1.isString)(value)) {
|
|
@@ -654,6 +680,23 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
654
680
|
formflowLabeledData(data = {}) {
|
|
655
681
|
return this.formatData(data).map(({ path, label }) => ({ path, label }));
|
|
656
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* A value an agent engine generated, before it is checked and written: HTML
|
|
685
|
+
* for a rich text field is reduced to the markup its editor keeps.
|
|
686
|
+
*/
|
|
687
|
+
formflowNormalizeValue(component, value) {
|
|
688
|
+
return (0, richText_1.isRichText)(component) && typeof value === 'string' ? (0, richText_1.sanitizeRichText)(value) : value;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* A value an MCP client writes at a data path (collect_field_data,
|
|
692
|
+
* confirm_form_submission, submit_completed_form, submission_update), as
|
|
693
|
+
* an engine's is: HTML for a rich text field is reduced to the markup its
|
|
694
|
+
* editor keeps. Row indices are ignored to find the component.
|
|
695
|
+
*/
|
|
696
|
+
normalizeWrittenValue(path, value) {
|
|
697
|
+
const component = this.getComponent(String(path).replace(/\[\d+\]/g, ''));
|
|
698
|
+
return component ? this.formflowNormalizeValue(component, value) : value;
|
|
699
|
+
}
|
|
657
700
|
htmlToMarkdown(html) {
|
|
658
701
|
return node_html_markdown_1.NodeHtmlMarkdown.translate(html, {
|
|
659
702
|
bulletMarker: '-',
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ProjectInterface, SubmissionRequest } from "@formio/appserver";
|
|
2
|
-
import { EngineRegistry, FormflowConfig, FormflowForm, FormflowHost, FormflowTool, ScopedTokenClaims } from "./formflow";
|
|
2
|
+
import { EngineRegistry, FormflowConfig, FormflowFileReader, FormflowForm, FormflowHost, FormflowTool, ScopedTokenClaims } from "./formflow";
|
|
3
3
|
import { UAGConfig } from "./config";
|
|
4
4
|
import { Form } from "@formio/core";
|
|
5
5
|
import { NextFunction, Response, Router } from "express";
|
|
6
6
|
import { UAGFormInterface } from "./UAGFormInterface";
|
|
7
7
|
import { ResponseTemplate, UAGTemplate } from "./template";
|
|
8
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
import type { PluginRegistry } from "@formio/appserver";
|
|
9
10
|
export declare class UAGProjectInterface extends ProjectInterface implements FormflowHost {
|
|
10
11
|
user: any;
|
|
11
12
|
/** The names of the UAG forms the project currently has. */
|
|
@@ -38,6 +39,13 @@ export declare class UAGProjectInterface extends ProjectInterface implements For
|
|
|
38
39
|
formflowConfig(): FormflowConfig;
|
|
39
40
|
/** A form tagged "uag", or null. */
|
|
40
41
|
formflowForm(name: string): Promise<FormflowForm | null>;
|
|
42
|
+
/** The server's plugin registry, once its plugins are listed. */
|
|
43
|
+
plugins?: PluginRegistry;
|
|
44
|
+
/**
|
|
45
|
+
* Readers for uploaded documents: one built from the installed converters,
|
|
46
|
+
* which reads every type with a route to text.
|
|
47
|
+
*/
|
|
48
|
+
formflowFileReaders(): FormflowFileReader[];
|
|
41
49
|
/** Every MCP tool, as Agentic Formflow tools. */
|
|
42
50
|
formflowTools(): Promise<FormflowTool[]>;
|
|
43
51
|
authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<void | Response<any, Record<string, any>>>;
|
|
@@ -12,8 +12,10 @@ const express_1 = require("express");
|
|
|
12
12
|
const router_1 = require("./router");
|
|
13
13
|
const template_1 = require("./template");
|
|
14
14
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
|
+
const version_1 = require("./version");
|
|
15
16
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
16
17
|
const router_2 = require("./agent/router");
|
|
18
|
+
const readers_1 = require("./documents/readers");
|
|
17
19
|
const tools_1 = require("./agent/tools");
|
|
18
20
|
const debug = require('debug')('formio:uag:UAGProjectInterface');
|
|
19
21
|
class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
@@ -28,7 +30,7 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
28
30
|
this.uagTemplate = null;
|
|
29
31
|
/** Engines enabled at startup; empty until UAGServer.router() resolves them. */
|
|
30
32
|
this.engines = new formflow_1.EngineRegistry();
|
|
31
|
-
this.mcpServer = new mcp_js_1.McpServer({ name: 'formio-uag', version:
|
|
33
|
+
this.mcpServer = new mcp_js_1.McpServer({ name: 'formio-uag', version: version_1.UAG_VERSION });
|
|
32
34
|
}
|
|
33
35
|
async initialize() {
|
|
34
36
|
await super.initialize();
|
|
@@ -40,7 +42,7 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
40
42
|
// A scoped token narrows the tools to its own list, and only ever to tools
|
|
41
43
|
// annotated read-only: no model may write through /mcp with one.
|
|
42
44
|
async buildMcpServer(scope) {
|
|
43
|
-
const server = new mcp_js_1.McpServer({ name: 'formio-uag', version:
|
|
45
|
+
const server = new mcp_js_1.McpServer({ name: 'formio-uag', version: version_1.UAG_VERSION });
|
|
44
46
|
// The standard UAG tools, plus the custom tools from config.
|
|
45
47
|
let tools = await (0, tools_1.allTools)(this);
|
|
46
48
|
if (scope) {
|
|
@@ -51,12 +53,15 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
51
53
|
server.registerTool(tool.name, {
|
|
52
54
|
title: tool.title,
|
|
53
55
|
description: tool.description,
|
|
54
|
-
|
|
56
|
+
// Always a schema, even an empty one: the MCP SDK calls a tool
|
|
57
|
+
// registered without one as `execute(extra)`, and every tool is
|
|
58
|
+
// `execute(args, extra)`, as Agent API research calls it.
|
|
59
|
+
inputSchema: tool.inputSchema || {},
|
|
55
60
|
// Carries readOnly/destructive/idempotent/openWorld through to the
|
|
56
61
|
// client. Tools from config.tools may omit them, so this stays
|
|
57
62
|
// conditional rather than sending `annotations: undefined`.
|
|
58
63
|
...(tool.annotations && { annotations: tool.annotations })
|
|
59
|
-
},
|
|
64
|
+
}, (args, extra) => (0, tools_1.runTool)(tool, this, args, extra));
|
|
60
65
|
}
|
|
61
66
|
// A scoped token that allows no tools must still answer tools/list, with
|
|
62
67
|
// an empty list: clients list tools first,
|
|
@@ -120,9 +125,16 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
120
125
|
const form = await this.getForm(name);
|
|
121
126
|
return form?.uag ? form : null;
|
|
122
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Readers for uploaded documents: one built from the installed converters,
|
|
130
|
+
* which reads every type with a route to text.
|
|
131
|
+
*/
|
|
132
|
+
formflowFileReaders() {
|
|
133
|
+
return this.plugins ? [(0, readers_1.converterFileReader)(this.plugins)] : [];
|
|
134
|
+
}
|
|
123
135
|
/** Every MCP tool, as Agentic Formflow tools. */
|
|
124
136
|
async formflowTools() {
|
|
125
|
-
return (await (0, tools_1.allTools)(this)).map(tools_1.formflowTool);
|
|
137
|
+
return (await (0, tools_1.allTools)(this)).map((tool) => (0, tools_1.formflowTool)(tool, this));
|
|
126
138
|
}
|
|
127
139
|
async authorizeRequest(req, res, next) {
|
|
128
140
|
try {
|
|
@@ -164,10 +176,16 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
164
176
|
// Point MCP clients at OAuth sign-in only when it is on (MCP_OAUTH, with the database).
|
|
165
177
|
const pkce = this.provider('auth.pkce');
|
|
166
178
|
const metadata = pkce?.oauthReady ? `, resource_metadata="${pkce.publicUrl(req)}/.well-known/oauth-protected-resource"` : '';
|
|
179
|
+
// RFC 6750 3.1: a request that presented a token is told it is invalid; one
|
|
180
|
+
// that presented none gets no error code, only the challenge.
|
|
181
|
+
const presented = !!(0, formflow_1.bearerOf)(req.get('authorization'));
|
|
182
|
+
const description = String(err?.message || err || 'Unauthorized').replace(/["\\]/g, '');
|
|
183
|
+
const challenge = presented ? `, error="invalid_token", error_description="${description}"` : '';
|
|
167
184
|
return res.status(401).set({
|
|
168
|
-
'WWW-Authenticate': `Bearer realm="uag-server"${metadata}`
|
|
185
|
+
'WWW-Authenticate': `Bearer realm="uag-server"${challenge}${metadata}`
|
|
169
186
|
}).json({
|
|
170
|
-
|
|
187
|
+
error: presented ? 'invalid_token' : 'unauthorized',
|
|
188
|
+
error_description: presented ? description : 'Send an access token: Authorization: Bearer <token>.'
|
|
171
189
|
});
|
|
172
190
|
}
|
|
173
191
|
}
|
package/lib/actionLogs.d.ts
CHANGED
|
@@ -17,16 +17,7 @@ export declare function actionItem(record: TaskRunRecord): {
|
|
|
17
17
|
info: string;
|
|
18
18
|
data: any;
|
|
19
19
|
}[];
|
|
20
|
-
data:
|
|
21
|
-
task: string;
|
|
22
|
-
answer: "decisions" | "task";
|
|
23
|
-
durable: boolean;
|
|
24
|
-
actingFor?: {
|
|
25
|
-
_id: string;
|
|
26
|
-
email?: string | undefined;
|
|
27
|
-
} | undefined;
|
|
28
|
-
agentRole?: string | undefined;
|
|
29
|
-
};
|
|
20
|
+
data: any;
|
|
30
21
|
created: string;
|
|
31
22
|
modified: string;
|
|
32
23
|
};
|
package/lib/actionLogs.js
CHANGED
|
@@ -35,12 +35,14 @@ function actionItem(record) {
|
|
|
35
35
|
if (record.finished) {
|
|
36
36
|
const written = record.written || [];
|
|
37
37
|
const cleared = record.cleared || [];
|
|
38
|
-
const
|
|
38
|
+
const refused = record.refused || [];
|
|
39
|
+
const left = refused.length ? ` Left out answers the form refused: ${refused.join(', ')}.` : '';
|
|
40
|
+
const info = (record.status === 'failed'
|
|
39
41
|
? `Failed: ${record.error || 'unknown error'}`
|
|
40
42
|
: record.saved
|
|
41
43
|
? `Saved ${written.length} field(s)${written.length ? `: ${written.join(', ')}` : ''}${cleared.length ? `; cleared ${cleared.join(', ')}` : ''}.`
|
|
42
|
-
: 'Nothing changed, so nothing was saved.';
|
|
43
|
-
messages.push({ datetime: record.finished.toISOString(), info, data: { engines: record.engines || [], written, cleared } });
|
|
44
|
+
: 'Nothing changed, so nothing was saved.') + left;
|
|
45
|
+
messages.push({ datetime: record.finished.toISOString(), info, data: { engines: record.engines || [], written, cleared, ...(refused.length && { refused }) } });
|
|
44
46
|
}
|
|
45
47
|
return {
|
|
46
48
|
_id: String(record._id),
|
package/lib/actions/runTask.js
CHANGED
|
@@ -9,6 +9,7 @@ const formflow_1 = require("../formflow");
|
|
|
9
9
|
const runner_1 = require("../runner");
|
|
10
10
|
const protocol_1 = require("@formio/uag-sdk/protocol");
|
|
11
11
|
const requestId_1 = require("../agent/requestId");
|
|
12
|
+
const errors_1 = require("../agent/errors");
|
|
12
13
|
const identity_1 = require("../identity");
|
|
13
14
|
const debug = require('debug')('formio:uag:actions:runTask');
|
|
14
15
|
exports.RUN_TASK = 'agentRunTask';
|
|
@@ -89,12 +90,12 @@ function onBehalfHook() {
|
|
|
89
90
|
if (roles.length > 1) {
|
|
90
91
|
console.error(`[formio-uag] The Run task actions of "${form.name}" that run on create name different agent roles ` +
|
|
91
92
|
`(${actions.map((action) => action.title || action.machineName).join(', ')}). Choose one agent role for them all.`);
|
|
92
|
-
return res.status(500).json({ name: 'ConfigurationError', message: `The form "${form.name}" is not configured correctly: its Run task actions name different agent roles
|
|
93
|
+
return res.status(500).json({ name: 'ConfigurationError', message: `The form "${form.name}" is not configured correctly: its Run task actions name different agent roles.`, code: 'configuration_error' });
|
|
93
94
|
}
|
|
94
95
|
const [role] = roles;
|
|
95
96
|
const submitters = actions.flatMap((action) => action.settings.submitterRoles || []).map(String);
|
|
96
97
|
if (!appserver_1.AuthRequest.hasRole([role], access[appserver_1.AuthorizedStateType.ALL], project) || !has(submitters)) {
|
|
97
|
-
return res.status(403).json({ name: 'ForbiddenError', message: 'You may not submit this form.' });
|
|
98
|
+
return res.status(403).json({ name: 'ForbiddenError', message: 'You may not submit this form.', code: 'forbidden' });
|
|
98
99
|
}
|
|
99
100
|
try {
|
|
100
101
|
req.forwardAuth = await (0, identity_1.agentAuth)(project, actingFor, role, { task: actions[0].settings.task });
|
|
@@ -127,7 +128,7 @@ function refuse(res, err) {
|
|
|
127
128
|
res.status(err.status).json({
|
|
128
129
|
name: err.name,
|
|
129
130
|
message: err.message,
|
|
130
|
-
|
|
131
|
+
code: err.code || (0, errors_1.errorCode)(err.status),
|
|
131
132
|
...(err.edition && { edition: err.edition })
|
|
132
133
|
});
|
|
133
134
|
}
|
package/lib/agent/anonymous.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AnonymousLimiter = void 0;
|
|
4
4
|
exports.anonymousLimits = anonymousLimits;
|
|
5
5
|
exports.anonymousGate = anonymousGate;
|
|
6
|
+
const formflow_1 = require("../formflow");
|
|
6
7
|
const debug = require('debug')('formio:uag:agent');
|
|
7
8
|
const WINDOW_MS = 60000;
|
|
8
9
|
/** A number from the configuration, else the environment, else the default. */
|
|
@@ -74,14 +75,14 @@ function anonymousGate(project) {
|
|
|
74
75
|
const request = req;
|
|
75
76
|
const form = request.form;
|
|
76
77
|
const limits = anonymousLimits(project);
|
|
77
|
-
if (limits.perAddress === 0 || limits.total === 0 ||
|
|
78
|
-
return res.status(401).json({
|
|
78
|
+
if (limits.perAddress === 0 || limits.total === 0 || !(0, formflow_1.flagOn)(form?.form?.properties?.uagAnonymous)) {
|
|
79
|
+
return res.status(401).json({ error: 'Sign in to use this form\'s agent operations and routes.', code: 'sign_in_required' });
|
|
79
80
|
}
|
|
80
81
|
const taken = limiterFor(project).take(String(req.ip || req.socket?.remoteAddress || ''), limits);
|
|
81
82
|
if (!taken.allowed) {
|
|
82
83
|
debug(`Refused an anonymous ${req.method} ${req.path}: over the anonymous rate limit.`);
|
|
83
84
|
res.set('Retry-After', String(taken.retryAfter));
|
|
84
|
-
return res.status(429).json({
|
|
85
|
+
return res.status(429).json({ error: 'Too many requests. Try again shortly.', code: 'rate_limited' });
|
|
85
86
|
}
|
|
86
87
|
let permissions = {};
|
|
87
88
|
try {
|
|
@@ -91,7 +92,7 @@ function anonymousGate(project) {
|
|
|
91
92
|
permissions = {};
|
|
92
93
|
}
|
|
93
94
|
if (!permissions.create) {
|
|
94
|
-
return res.status(403).json({
|
|
95
|
+
return res.status(403).json({ error: 'Anonymous visitors may not submit this form.', code: 'forbidden' });
|
|
95
96
|
}
|
|
96
97
|
next();
|
|
97
98
|
};
|
package/lib/agent/chat.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthRequest } from "@formio/appserver";
|
|
2
|
+
import { CHAT_INSTRUCTIONS } from "../formflow";
|
|
2
3
|
import type { AgentEngine } from "../formflow";
|
|
3
4
|
import type { UAGProjectInterface } from "../UAGProjectInterface";
|
|
4
5
|
import type { ChatField, ChatFields, ChatUnsupportedField, FormFieldError, UAGFormInterface } from "../UAGFormInterface";
|
|
@@ -94,8 +95,8 @@ export declare function readChatRequest(body: any, settings: ChatSettings): Chat
|
|
|
94
95
|
*/
|
|
95
96
|
export declare function trimTranscript(messages: ChatMessage[], maxChars: number): ChatMessage[];
|
|
96
97
|
export declare function transcriptText(messages: ChatMessage[]): string;
|
|
97
|
-
/** The instructions every turn runs with
|
|
98
|
-
export
|
|
98
|
+
/** The instructions every turn runs with (Agentic Formflow's, which builds each turn's request). */
|
|
99
|
+
export { CHAT_INSTRUCTIONS };
|
|
99
100
|
/** The JSON schema of a field's value, for structured extraction. */
|
|
100
101
|
export declare function chatValueSchema(form: UAGFormInterface, field: ChatField): Record<string, any>;
|
|
101
102
|
/** What the form still needs, from its fields on the current data. */
|