@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/plugins.js ADDED
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolPluginType = exports.enginePluginType = void 0;
4
+ exports.listEngines = listEngines;
5
+ exports.listTools = listTools;
6
+ const uag_sdk_1 = require("@formio/uag-sdk");
7
+ const tools_1 = require("./agent/tools");
8
+ const engines_1 = require("./agent/engines");
9
+ const version_1 = require("./version");
10
+ /**
11
+ * Engines and MCP tools, listed in the plugin registry (phase 1). The UAG keeps
12
+ * loading them its own way (UAG_ENGINES, config.tools); the registry only lists
13
+ * them, so they appear in GET {project}/plugins. Settings values are never listed.
14
+ */
15
+ exports.enginePluginType = {
16
+ type: 'engine',
17
+ title: 'Engine',
18
+ apiVersions: [1],
19
+ editForm: 'none',
20
+ // A misconfigured engine stops startup.
21
+ unavailable: 'error',
22
+ registration: 'internal',
23
+ stability: 'stable',
24
+ validate: (plugin) => uag_sdk_1.ENGINE_NAME.test(plugin.name) ? [] : [`engine name "${plugin.name}" must be lowercase letters, digits and "-", starting with a letter`],
25
+ manifest: (plugin) => ({
26
+ kinds: plugin.kinds,
27
+ accepts: plugin.accepts,
28
+ generate: plugin.generate,
29
+ instances: plugin.instances
30
+ })
31
+ };
32
+ exports.toolPluginType = {
33
+ type: 'tool',
34
+ title: 'MCP tool',
35
+ apiVersions: [1],
36
+ editForm: 'none',
37
+ unavailable: 'error',
38
+ registration: 'internal',
39
+ stability: 'stable',
40
+ validate: (plugin) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(plugin.name) ? [] : [`tool name "${plugin.name}" must start with a letter and hold only letters, digits, "_" and "-"`],
41
+ manifest: (plugin) => ({ annotations: plugin.annotations ?? null })
42
+ };
43
+ const semver = (version) => (version && /^\d+\.\d+\.\d+/.test(version)) ? version : version_1.UAG_VERSION;
44
+ /** List each loaded engine type: its instances, kinds, what agent instances read, and whether they generate. */
45
+ function listEngines(registry, project) {
46
+ const engines = project.formflowEngines();
47
+ const entries = (0, engines_1.engineTypesOf)(engines).map((type) => {
48
+ const instances = type.instances.map((name) => engines.get(name)).filter(Boolean);
49
+ const agents = instances.filter((engine) => engine.kind === 'agent');
50
+ const accepts = [...new Set(agents.flatMap((engine) => Array.isArray(engine.accepts) ? engine.accepts : ['text']))];
51
+ return {
52
+ type: 'engine',
53
+ name: type.name,
54
+ title: type.title,
55
+ version: semver(type.version),
56
+ apiVersion: 1,
57
+ kinds: type.kinds,
58
+ accepts,
59
+ generate: agents.some((engine) => typeof engine.generate === 'function'),
60
+ instances: type.instances
61
+ };
62
+ });
63
+ registry.addType(exports.enginePluginType, '@formio/uag');
64
+ registry.add(entries, 'UAG_ENGINES', { server: true });
65
+ }
66
+ /**
67
+ * List every MCP tool the project serves (built-in, then config.tools). A name
68
+ * served twice is listed once, with a warning: listing never refuses what the
69
+ * tool loader allows.
70
+ */
71
+ async function listTools(registry, project, log = console) {
72
+ const seen = new Set();
73
+ const entries = [];
74
+ for (const tool of await (0, tools_1.allTools)(project)) {
75
+ const name = String(tool.name);
76
+ if (seen.has(name)) {
77
+ log.warn(`[plugins] tool:${name} is defined more than once; it is listed once`);
78
+ continue;
79
+ }
80
+ seen.add(name);
81
+ entries.push({
82
+ type: 'tool',
83
+ name,
84
+ title: tool.title || name,
85
+ description: tool.description,
86
+ version: version_1.UAG_VERSION,
87
+ apiVersion: 1,
88
+ annotations: tool.annotations
89
+ });
90
+ }
91
+ registry.addType(exports.toolPluginType, '@formio/uag');
92
+ registry.add(entries, 'config.tools', { server: true });
93
+ }
@@ -0,0 +1,20 @@
1
+ import type { Component } from '@formio/core';
2
+ /**
3
+ * HTML for rich text fields (textareas with the CKEditor or Quill editor).
4
+ *
5
+ * What an agent engine writes into such a field is reduced to the markup the
6
+ * editors keep, because the engine's input can be steered (a document it reads
7
+ * is someone else's text), and the field is later rendered for reviewers. The
8
+ * HTML is parsed and written out again: allowed elements with their allowed
9
+ * attributes, text escaped, everything else dropped. An element that is not
10
+ * allowed is dropped and its children kept, except for elements whose content
11
+ * is never text to show (scripts, styles, frames, embedded documents).
12
+ */
13
+ /** Allowed elements, each with the attributes it may keep. */
14
+ export declare const RICH_TEXT_TAGS: Record<string, string[]>;
15
+ /** Whether a component is a rich text field, whose value is HTML. */
16
+ export declare function isRichText(component: Component | undefined): boolean;
17
+ /** Reduce HTML to the markup rich text editors keep (RICH_TEXT_TAGS). */
18
+ export declare function sanitizeRichText(html: string): string;
19
+ /** The rule agents are given for a rich text field's value. */
20
+ export declare const RICH_TEXT_RULE: string;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RICH_TEXT_RULE = exports.RICH_TEXT_TAGS = void 0;
4
+ exports.isRichText = isRichText;
5
+ exports.sanitizeRichText = sanitizeRichText;
6
+ const node_html_parser_1 = require("node-html-parser");
7
+ /**
8
+ * HTML for rich text fields (textareas with the CKEditor or Quill editor).
9
+ *
10
+ * What an agent engine writes into such a field is reduced to the markup the
11
+ * editors keep, because the engine's input can be steered (a document it reads
12
+ * is someone else's text), and the field is later rendered for reviewers. The
13
+ * HTML is parsed and written out again: allowed elements with their allowed
14
+ * attributes, text escaped, everything else dropped. An element that is not
15
+ * allowed is dropped and its children kept, except for elements whose content
16
+ * is never text to show (scripts, styles, frames, embedded documents).
17
+ */
18
+ /** Allowed elements, each with the attributes it may keep. */
19
+ exports.RICH_TEXT_TAGS = {
20
+ p: [], br: [], hr: [],
21
+ strong: [], b: [], em: [], i: [], u: [], s: [], sub: [], sup: [], code: [], pre: [],
22
+ h1: [], h2: [], h3: [], h4: [], h5: [], h6: [],
23
+ ul: [], ol: [], li: [],
24
+ blockquote: [],
25
+ a: ['href'],
26
+ figure: [], table: [], thead: [], tbody: [], tfoot: [], tr: [],
27
+ th: ['colspan', 'rowspan'], td: ['colspan', 'rowspan']
28
+ };
29
+ /** Elements dropped with everything inside them. */
30
+ const DROPPED_WITH_CONTENT = new Set(['script', 'style', 'noscript', 'template', 'iframe', 'frame', 'frameset', 'object', 'embed', 'svg', 'math', 'head', 'title', 'textarea', 'select']);
31
+ const VOID = new Set(['br', 'hr']);
32
+ /** Whether a component is a rich text field, whose value is HTML. */
33
+ function isRichText(component) {
34
+ const editor = component?.editor;
35
+ return component?.type === 'textarea' && (editor === 'ckeditor' || editor === 'quill');
36
+ }
37
+ function escapeText(text) {
38
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
39
+ }
40
+ function escapeAttribute(value) {
41
+ return escapeText(value).replace(/"/g, '&quot;');
42
+ }
43
+ /** Only links to web pages and mail addresses: never `javascript:`, `data:` or the like. */
44
+ function safeHref(value) {
45
+ const href = String(value || '').replace(/[\u0000-\u001F\u007F\s]+/g, '');
46
+ return /^(https?:\/\/|mailto:)/i.test(href) ? String(value).trim() : null;
47
+ }
48
+ function attributesOf(element, allowed) {
49
+ let out = '';
50
+ for (const name of allowed) {
51
+ const raw = element.getAttribute(name);
52
+ if (raw === undefined || raw === null)
53
+ continue;
54
+ let value = raw;
55
+ if (name === 'href')
56
+ value = safeHref(raw);
57
+ if ((name === 'colspan' || name === 'rowspan'))
58
+ value = /^\d{1,3}$/.test(raw.trim()) ? raw.trim() : null;
59
+ if (value === null)
60
+ continue;
61
+ out += ` ${name}="${escapeAttribute(value)}"`;
62
+ }
63
+ return out;
64
+ }
65
+ function write(node) {
66
+ if (node.nodeType === node_html_parser_1.NodeType.TEXT_NODE) {
67
+ // `text` is the decoded text; it is escaped once, here.
68
+ return escapeText(node.text);
69
+ }
70
+ if (node.nodeType !== node_html_parser_1.NodeType.ELEMENT_NODE)
71
+ return '';
72
+ const element = node;
73
+ const children = () => element.childNodes.map(write).join('');
74
+ const tag = String(element.rawTagName || '').toLowerCase();
75
+ if (!tag)
76
+ return children();
77
+ if (DROPPED_WITH_CONTENT.has(tag))
78
+ return '';
79
+ const allowed = exports.RICH_TEXT_TAGS[tag];
80
+ if (!allowed)
81
+ return children();
82
+ const open = `<${tag}${attributesOf(element, allowed)}>`;
83
+ return VOID.has(tag) ? open : `${open}${children()}</${tag}>`;
84
+ }
85
+ /** Reduce HTML to the markup rich text editors keep (RICH_TEXT_TAGS). */
86
+ function sanitizeRichText(html) {
87
+ const root = (0, node_html_parser_1.parse)(String(html ?? ''), {
88
+ comment: false,
89
+ lowerCaseTagName: true,
90
+ // Read as raw text, so their content is never parsed as markup; they are dropped.
91
+ blockTextElements: { script: true, style: true, noscript: true }
92
+ });
93
+ return root.childNodes.map(write).join('').trim();
94
+ }
95
+ /** The rule agents are given for a rich text field's value. */
96
+ exports.RICH_TEXT_RULE = 'The value is HTML for a rich text editor. Use only these elements: ' +
97
+ Object.keys(exports.RICH_TEXT_TAGS).join(', ') +
98
+ ', with no attributes except href on a (http, https or mailto links only) and colspan and rowspan on td and th. ' +
99
+ 'No other elements, styles, classes, images or scripts, which are removed. Write HTML, not Markdown, and do not wrap it in a code fence.';
package/lib/router.d.ts CHANGED
@@ -1,3 +1,12 @@
1
1
  import { Router } from 'express';
2
2
  import { UAGProjectInterface } from './UAGProjectInterface';
3
+ import type { ScopedTokenClaims } from './formflow';
4
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ /**
6
+ * Hold a scoped token to its tools whatever built the server: a project
7
+ * interface a module replaces may override buildMcpServer() and register tools
8
+ * without the scope it was given. Every tool the token does not list, and every
9
+ * tool not annotated read-only, is disabled, so it is neither listed nor run.
10
+ */
11
+ export declare function restrictToScope(server: McpServer, scope: Pick<ScopedTokenClaims, 'tools'>): void;
3
12
  export declare function UAGRouter(project: UAGProjectInterface): Router;
package/lib/router.js CHANGED
@@ -1,16 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.restrictToScope = restrictToScope;
3
4
  exports.UAGRouter = UAGRouter;
4
5
  const express_1 = require("express");
5
6
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
6
7
  // Helper function to create JSON RPC error responses
7
- const createJsonRpcErrorResponse = (code, message) => {
8
- return JSON.stringify({
9
- error: { code, message },
10
- id: null,
11
- jsonrpc: "2.0",
12
- });
13
- };
8
+ const createJsonRpcErrorResponse = (code, message) => ({
9
+ error: { code, message },
10
+ id: null,
11
+ jsonrpc: "2.0",
12
+ });
14
13
  /**
15
14
  * Handle errors that occur during response processing.
16
15
  * @param error The error that occurred.
@@ -29,16 +28,34 @@ const handleResponseError = (error, res) => {
29
28
  return true;
30
29
  }
31
30
  console.error("[formio-uag] error handling request", error);
32
- res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Internal Server Error")));
31
+ res.status(500).json(createJsonRpcErrorResponse(-32603, "Internal Server Error"));
33
32
  return false;
34
33
  };
34
+ /**
35
+ * Hold a scoped token to its tools whatever built the server: a project
36
+ * interface a module replaces may override buildMcpServer() and register tools
37
+ * without the scope it was given. Every tool the token does not list, and every
38
+ * tool not annotated read-only, is disabled, so it is neither listed nor run.
39
+ */
40
+ function restrictToScope(server, scope) {
41
+ const allowed = new Set(scope.tools || []);
42
+ // The SDK keeps registered tools here; it has no public list of them.
43
+ const registered = server._registeredTools || {};
44
+ for (const [name, tool] of Object.entries(registered)) {
45
+ if (!allowed.has(name) || tool?.annotations?.readOnlyHint !== true) {
46
+ tool.disable?.();
47
+ }
48
+ }
49
+ }
35
50
  /**
36
51
  * Build a per-request McpServer + transport (stateless mode) and tear both down
37
52
  * when the response closes. A fresh instance per request keeps concurrent requests
38
53
  * from overwriting each other's transport.
39
54
  */
40
- const connectMcpTransport = async (project, res) => {
41
- const server = await project.buildMcpServer();
55
+ const connectMcpTransport = async (project, res, scope) => {
56
+ const server = await project.buildMcpServer(scope);
57
+ if (scope)
58
+ restrictToScope(server, scope);
42
59
  const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
43
60
  sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
44
61
  });
@@ -51,26 +68,18 @@ const connectMcpTransport = async (project, res) => {
51
68
  };
52
69
  function UAGRouter(project) {
53
70
  const router = (0, express_1.Router)();
54
- // Handles the MCP post requests.
55
- router.post('/', async (req, res) => {
71
+ // Handles the MCP requests: posts carry a JSON-RPC body, gets do not (health checks, etc).
72
+ const handleMcpRequest = (withBody) => async (req, res) => {
56
73
  try {
57
- const transport = await connectMcpTransport(project, res);
58
- await transport.handleRequest(req, res, req.body);
74
+ const transport = await connectMcpTransport(project, res, req.uagScope);
75
+ await transport.handleRequest(req, res, withBody ? req.body : undefined);
59
76
  }
60
77
  catch (error) {
61
78
  handleResponseError(error, res);
62
79
  }
63
- });
64
- // Handles the MCP get requests (for health checks, etc).
65
- router.get('/', async (req, res) => {
66
- try {
67
- const transport = await connectMcpTransport(project, res);
68
- await transport.handleRequest(req, res);
69
- }
70
- catch (err) {
71
- handleResponseError(err, res);
72
- }
73
- });
80
+ };
81
+ router.post('/', handleMcpRequest(true));
82
+ router.get('/', handleMcpRequest(false));
74
83
  // Return the router
75
84
  return router;
76
85
  }
@@ -0,0 +1,149 @@
1
+ import { AuthRequest } from '@formio/appserver';
2
+ import type { CallerIdentity } from './formflow';
3
+ import { ActingFor } from './identity';
4
+ import { TaskRunStore } from './runs';
5
+ import type { UAGProjectInterface } from './UAGProjectInterface';
6
+ /** A Run task action's run after the save, on the submission Form.io stored. */
7
+ export type TaskRunJob = {
8
+ /** The form's name. */
9
+ form: string;
10
+ submission: string;
11
+ task: string;
12
+ answer: 'decisions' | 'task';
13
+ /** The user the run acts for: who sent the submission. Never a token. */
14
+ actingFor: ActingFor;
15
+ /**
16
+ * The role the agent acts with at Form.io, on the user's behalf. Such runs
17
+ * can be recorded and resumed: everything they need is in the job.
18
+ */
19
+ agentRole?: string;
20
+ /**
21
+ * Without an agent role, the caller's own identity (with their token), kept
22
+ * in memory only and never recorded.
23
+ */
24
+ caller?: AuthRequest;
25
+ /** Without an agent role, who the engines research as (the caller's bearer token, if any). */
26
+ identity?: CallerIdentity;
27
+ /**
28
+ * Without an agent role or the caller's token: act as the user, with their
29
+ * recorded roles and tokens the UAG signs. A run started by the save of a
30
+ * run that acted with an agent role, so it never inherits that role.
31
+ */
32
+ asUser?: boolean;
33
+ /** How many runner saves led to this run: none for a run a client's request started. */
34
+ chain?: number;
35
+ /** The action that started the run, for the record. */
36
+ action?: string;
37
+ /** For the record: the form's id, and the method that started the run. */
38
+ formId?: string;
39
+ method?: string;
40
+ /** The X-Request-Id of the request that started the run, passed on to the engines. */
41
+ requestId?: string;
42
+ };
43
+ export type TaskRunOutcome = {
44
+ status: 'done' | 'failed';
45
+ /** Paths written, with their values, and paths cleared. Empty when nothing changed. */
46
+ written: Record<string, any>;
47
+ cleared: string[];
48
+ /** The engines and models that answered. */
49
+ engines: {
50
+ kind: string;
51
+ engine: string | null;
52
+ model: string | null;
53
+ }[];
54
+ /** Whether the results were saved (nothing is saved when nothing changed). */
55
+ saved: boolean;
56
+ /** Answers the form's validation refused, left out: path to the form's message. */
57
+ refused?: Record<string, string>;
58
+ error?: string;
59
+ };
60
+ /**
61
+ * The request field that marks the runner's own save, so the form's Run task
62
+ * actions that run before the save skip it: its data is the agent's output.
63
+ */
64
+ export declare const RUN_TASK_SAVE = "uagRunTaskSave";
65
+ /**
66
+ * The request fields that carry, through the runner's own save, who the run
67
+ * acts for, so a Run task action that the save starts acts for the same user.
68
+ */
69
+ export declare const RUN_TASK_ACTING_FOR = "uagActingFor";
70
+ export declare const RUN_TASK_CALLER = "uagCaller";
71
+ export declare const RUN_TASK_REQUEST_ID = "uagRequestId";
72
+ export declare const RUN_TASK_IDENTITY = "uagIdentity";
73
+ export declare const RUN_TASK_AS_USER = "uagAsUser";
74
+ export declare const RUN_TASK_CHAIN = "uagChain";
75
+ /**
76
+ * Runs a runner's save may start in a row: a task whose save starts a task
77
+ * that changes something each time would otherwise never stop.
78
+ */
79
+ export declare const MAX_CHAIN = 5;
80
+ /** How often a replica looks for runs that are pending or lost their replica. */
81
+ export declare const SWEEP_MS: number;
82
+ /**
83
+ * Runs Run task actions' background runs: load the stored submission, run the
84
+ * task, and save only what changed, as an update through the UAG's own
85
+ * pipeline (so the form's update actions, including other Run task actions,
86
+ * run on it). If the submission changes while the task runs, the run starts
87
+ * again once on the new version, then gives up as a conflict.
88
+ *
89
+ * Without the UAG's database, runs are kept in memory, a few at a time, and
90
+ * are lost if the process stops. With it, every run is recorded; a run that
91
+ * acts with an agent role is recorded as pending before the caller's response
92
+ * and taken from the database by one replica at a time, under a lease it
93
+ * renews, so it survives a restart. A sweep at startup and every minute takes
94
+ * runs that are pending or whose replica stopped.
95
+ */
96
+ export declare class TaskRunner {
97
+ private project;
98
+ private concurrency;
99
+ private queue;
100
+ private active;
101
+ private waiting;
102
+ private store?;
103
+ private sweeper?;
104
+ private sweeping;
105
+ constructor(project: UAGProjectInterface, concurrency?: number);
106
+ /** Whether runs acting with an agent role are recorded and survive a restart. */
107
+ get durable(): boolean;
108
+ /** Record runs in the UAG's database, and take durable runs from it. */
109
+ useStore(store: TaskRunStore): void;
110
+ /** Stop sweeping (the runs in progress finish). */
111
+ stop(): void;
112
+ /** The project's run records, when there is a database. */
113
+ get records(): TaskRunStore | undefined;
114
+ /**
115
+ * Start a run. A durable run is recorded as pending before this resolves, so
116
+ * the caller's response is sent only once it can survive a restart.
117
+ */
118
+ enqueue(job: TaskRunJob): Promise<void>;
119
+ /** Record a run made before the save, once the save gave the submission its id. */
120
+ recordBefore(job: TaskRunJob, outcome: TaskRunOutcome): Promise<void>;
121
+ private newRecord;
122
+ /** Resolves once every queued and running job has finished (for tests and shutdown). */
123
+ idle(): Promise<void>;
124
+ private isIdle;
125
+ private settled;
126
+ private drain;
127
+ /** Take durable runs from the database while there is room. */
128
+ private sweep;
129
+ private runRecord;
130
+ /** Run a job, report it, and record its end. */
131
+ private track;
132
+ protected report(job: TaskRunJob, outcome: TaskRunOutcome): void;
133
+ /**
134
+ * Who the job acts as at Form.io (`act`: a new identity for each request,
135
+ * since signed tokens are short-lived), and who its engines research as.
136
+ * Never the project's key: the agent role's signed token, or the caller's own.
137
+ */
138
+ private identities;
139
+ /**
140
+ * Why Form.io refused the agent's signed token, when it did: asks Form.io who
141
+ * the token is, so a JWT_SECRET that is not Form.io's is told apart from
142
+ * other failures.
143
+ */
144
+ private refusal;
145
+ /** Run one job to completion. */
146
+ execute(job: TaskRunJob): Promise<TaskRunOutcome>;
147
+ private run;
148
+ }
149
+ export declare function taskRunner(project: UAGProjectInterface): TaskRunner;