@datalayer/agent-runtimes 1.3.83 → 1.3.85
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/lib/chat/ChatFloating.d.ts +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +30 -11
- package/lib/chat/base/loopElement.d.ts +53 -0
- package/lib/chat/base/loopElement.js +78 -0
- package/lib/chat/base/loopWindow.d.ts +31 -0
- package/lib/chat/base/loopWindow.js +74 -0
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
- package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
- package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
- package/lib/components/a2ui/datalayer/Form.js +9 -2
- package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
- package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +3 -0
- package/lib/components/teams/index.js +3 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/loop/apps/AppComposer.d.ts +40 -4
- package/lib/loop/apps/AppComposer.js +194 -19
- package/lib/loop/apps/AppRenderer.d.ts +21 -3
- package/lib/loop/apps/AppRenderer.js +88 -48
- package/lib/loop/apps/appspec.d.ts +13 -1
- package/lib/loop/apps/appspec.js +305 -12
- package/lib/loop/apps/checks.d.ts +14 -0
- package/lib/loop/apps/checks.js +276 -5
- package/lib/loop/apps/composer.d.ts +21 -10
- package/lib/loop/apps/composer.js +41 -8
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/apps/language.d.ts +68 -0
- package/lib/loop/apps/language.js +275 -0
- package/lib/loop/apps/settingsInputs.d.ts +29 -0
- package/lib/loop/apps/settingsInputs.js +147 -0
- package/lib/loop/apps/uploads.d.ts +43 -0
- package/lib/loop/apps/uploads.js +66 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +15 -1
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +25 -3
- package/lib/loop/embed/AppEmbed.js +106 -18
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +84 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/embedSession.d.ts +14 -0
- package/lib/loop/embed/embedSession.js +34 -0
- package/lib/loop/embed/hostBridge.d.ts +50 -3
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
- package/lib/loop/plugins/app-elements/AppElements.js +79 -0
- package/lib/loop/plugins/app-elements/index.d.ts +20 -0
- package/lib/loop/plugins/app-elements/index.js +43 -0
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +122 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +16 -6
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
- package/lib/protocols/BrowserAgentAdapter.js +13 -10
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/runtimes/browser/protocol.d.ts +1 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1518 -216
- package/lib/specs/appspecSchema.js +630 -3
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/specs/uiPlugins.js +6 -1
- package/lib/types/agentspecs.d.ts +200 -1
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +20 -2
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
package/lib/loop/apps/checks.js
CHANGED
|
@@ -39,10 +39,14 @@ import { getTeamSpec } from '../../specs/teams';
|
|
|
39
39
|
import { getBackendToolSpec } from '../../specs/backendTools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
41
|
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
42
|
-
import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, parseAppspec, } from './appspec';
|
|
42
|
+
import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, PAGE_OUTPUT_SHOWS, parseAppspec, } from './appspec';
|
|
43
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
44
44
|
import { COMMAND_INPUT, COMMAND_NAME, MODE_ID } from './composer';
|
|
45
|
-
import { HOST_NAME, hostToolsOf } from './hostTools';
|
|
45
|
+
import { HOST_NAME, HOST_USERS, hostToolsOf } from './hostTools';
|
|
46
|
+
import { MAX_UPLOAD_MB, UPLOAD_KIND } from './uploads';
|
|
47
|
+
import { formUiProblems } from './settingsInputs';
|
|
48
|
+
import { customComponentOf, customComponentsProblems, customNodeRefused, customPropsRefused, } from './customComponents';
|
|
49
|
+
import { translationProblems } from './language';
|
|
46
50
|
export const NOT_READY = 'Not ready';
|
|
47
51
|
export const NEEDS_ATTENTION = 'Needs attention';
|
|
48
52
|
export const PASSES = 'Passes the instant checks';
|
|
@@ -80,6 +84,130 @@ const matches = (name, pattern) => new RegExp('^' +
|
|
|
80
84
|
? '[\\s\\S]'
|
|
81
85
|
: c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('') +
|
|
82
86
|
'$').test(name);
|
|
87
|
+
/** The name of a function of an application's code (agentspecs' `CODE_NAME`). */
|
|
88
|
+
const CODE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
89
|
+
/**
|
|
90
|
+
* What its code declares that agentspecs refuses (LOOP P-06): its tools,
|
|
91
|
+
* its checks and the tests it decides, each by a Python name, each name
|
|
92
|
+
* once; a tool says what it does, by class, each class once.
|
|
93
|
+
*/
|
|
94
|
+
export function ownCodeProblems(app) {
|
|
95
|
+
const problems = [];
|
|
96
|
+
const tools = app.tools ?? [];
|
|
97
|
+
for (const tool of tools) {
|
|
98
|
+
if (!CODE_NAME.test(tool.name)) {
|
|
99
|
+
problems.push(`Cannot use “${tool.name}” as a tool's name: letters, digits and \`_\`, as in Python.`);
|
|
100
|
+
}
|
|
101
|
+
if (!tool.description.trim()) {
|
|
102
|
+
problems.push(`The tool “${tool.name}” says what it does, for the agent.`);
|
|
103
|
+
}
|
|
104
|
+
if (tool.does.length === 0) {
|
|
105
|
+
problems.push(`The tool “${tool.name}” says what it does: read, write, send, buy, delete or publish.`);
|
|
106
|
+
}
|
|
107
|
+
for (const action of tool.does) {
|
|
108
|
+
if (!CLASS_NAMES.has(action)) {
|
|
109
|
+
problems.push(`The tool “${tool.name}” does “${action}”, which is no class of action.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (new Set(tool.does).size !== tool.does.length) {
|
|
113
|
+
problems.push('A tool says each thing it does once.');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const names = tools.map(tool => tool.name);
|
|
117
|
+
if (new Set(names).size !== names.length) {
|
|
118
|
+
problems.push('Two of its tools have the same name.');
|
|
119
|
+
}
|
|
120
|
+
const checks = (app.checks.code ?? []).map(check => check.name);
|
|
121
|
+
for (const name of checks) {
|
|
122
|
+
if (!CODE_NAME.test(name)) {
|
|
123
|
+
problems.push(`Cannot use “${name}” as a check's name: letters, digits and \`_\`, as in Python.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (new Set(checks).size !== checks.length) {
|
|
127
|
+
problems.push('Two checks of its code have the same name.');
|
|
128
|
+
}
|
|
129
|
+
const decided = app.tests.cases.flatMap(testCase => testCase.code ? [testCase.code] : []);
|
|
130
|
+
if (new Set(decided).size !== decided.length) {
|
|
131
|
+
problems.push('Two tests are decided by the same function of its code.');
|
|
132
|
+
}
|
|
133
|
+
problems.push(...pageProblems(app));
|
|
134
|
+
// The components its developer wrote, reviewed as the catalog's (P-17).
|
|
135
|
+
problems.push(...customComponentsProblems(app));
|
|
136
|
+
return problems;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* What agentspecs refuses of a widget's page written in its code (LOOP
|
|
140
|
+
* P-05): a page that is not a widget's, inputs that are not a form, outputs
|
|
141
|
+
* of one name twice, drawn with another component, saying in their `props`
|
|
142
|
+
* what their value fills or lacking what their component needs, and an input
|
|
143
|
+
* named as a setting — both are on the page at `/inputs/<name>`.
|
|
144
|
+
*/
|
|
145
|
+
export function pageProblems(app) {
|
|
146
|
+
const page = app.interface.page;
|
|
147
|
+
if (!page) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const problems = [];
|
|
151
|
+
if (app.kind !== 'widget') {
|
|
152
|
+
problems.push(`A page of inputs and outputs is a widget's: a ${app.kind} application has none. Remove \`interface.page\`, or make it a widget.`);
|
|
153
|
+
}
|
|
154
|
+
if (!CODE_NAME.test(page.function)) {
|
|
155
|
+
problems.push(`Cannot use “${page.function}” as the function of its page: letters, digits and \`_\`, as in Python.`);
|
|
156
|
+
}
|
|
157
|
+
problems.push(...formProblems({
|
|
158
|
+
id: 'page inputs',
|
|
159
|
+
schema: page.inputs,
|
|
160
|
+
ui: page.inputsUi,
|
|
161
|
+
}));
|
|
162
|
+
if (page.outputs.length === 0) {
|
|
163
|
+
problems.push('Its page shows one output at least.');
|
|
164
|
+
}
|
|
165
|
+
const names = page.outputs.map(output => output.name);
|
|
166
|
+
if (new Set(names).size !== names.length) {
|
|
167
|
+
problems.push('Two outputs of its page have the same name.');
|
|
168
|
+
}
|
|
169
|
+
for (const output of page.outputs) {
|
|
170
|
+
if (!CODE_NAME.test(output.name)) {
|
|
171
|
+
problems.push(`Cannot use “${output.name}” as an output's name: letters, digits and \`_\`, as in Python.`);
|
|
172
|
+
}
|
|
173
|
+
// A component its developer wrote (P-17): its value what it shows first.
|
|
174
|
+
const custom = output.component in PAGE_OUTPUT_SHOWS
|
|
175
|
+
? undefined
|
|
176
|
+
: customComponentOf(app, output.component);
|
|
177
|
+
if (custom) {
|
|
178
|
+
if (custom.shows.length === 0) {
|
|
179
|
+
problems.push(`The output “${output.name}” is drawn with ${output.component}, which shows nothing: its value is what it shows first, under \`shows\`.`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const refused = customPropsRefused(custom, output.props);
|
|
183
|
+
if (refused.length > 0) {
|
|
184
|
+
problems.push(`The output “${output.name}” is a ${output.component} its schema refuses: ${refused.join('; ')}.`);
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const shows = PAGE_OUTPUT_SHOWS[output.component];
|
|
189
|
+
if (!shows) {
|
|
190
|
+
problems.push(`The output “${output.name}” is drawn with ${output.component}: an output is one of ${Object.keys(PAGE_OUTPUT_SHOWS).join(', ')} or a component of its own (\`custom_components\`).`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const said = [shows, 'id', 'component'].filter(key => key in output.props);
|
|
194
|
+
if (said.length > 0) {
|
|
195
|
+
problems.push(`The output “${output.name}” says ${said.join(', ')} in its \`props\`: its id is its name, and what it shows is its value.`);
|
|
196
|
+
}
|
|
197
|
+
const schema = getComponent(output.component)?.properties;
|
|
198
|
+
const missing = (schema?.required ?? []).filter(key => key !== shows && !(key in output.props));
|
|
199
|
+
if (missing.length > 0) {
|
|
200
|
+
problems.push(`The output “${output.name}” is a ${output.component} without ${missing.join(', ')}: say it in its \`props\`.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const settings = Object.keys(app.interface.settings?.properties ?? {});
|
|
204
|
+
const inputs = Object.keys(page.inputs?.properties ?? {});
|
|
205
|
+
const shared = settings.filter(name => inputs.includes(name));
|
|
206
|
+
if (shared.length > 0) {
|
|
207
|
+
problems.push(`${shared.map(name => `“${name}”`).join(', ')} is both a setting and an input of its page: name one otherwise.`);
|
|
208
|
+
}
|
|
209
|
+
return problems;
|
|
210
|
+
}
|
|
83
211
|
/** What the spec says of itself that the tolerant reader lets through. */
|
|
84
212
|
function shapeProblems(app) {
|
|
85
213
|
const problems = [];
|
|
@@ -120,6 +248,36 @@ function shapeProblems(app) {
|
|
|
120
248
|
else if (app.triggers.length > 0) {
|
|
121
249
|
problems.push(`A ${app.kind} application starts when somebody opens it: \`triggers\` are a worker's.`);
|
|
122
250
|
}
|
|
251
|
+
// Its profiles (LOOP P-20): two at least, or none; each id slash-safe and
|
|
252
|
+
// said once, each with a label.
|
|
253
|
+
const profiles = app.interface.profiles ?? [];
|
|
254
|
+
if (profiles.length === 1) {
|
|
255
|
+
problems.push('One profile is the application itself: say two at least, or put its instructions, model and starters on the application.');
|
|
256
|
+
}
|
|
257
|
+
const profileIds = new Set();
|
|
258
|
+
for (const profile of profiles) {
|
|
259
|
+
if (!MODE_ID.test(profile.id)) {
|
|
260
|
+
problems.push(`Cannot use “${profile.id}” as a profile's id: lower-case letters, digits, \`_\` and \`-\`, a letter first.`);
|
|
261
|
+
}
|
|
262
|
+
if (!profile.label.trim()) {
|
|
263
|
+
problems.push(`The profile “${profile.id}” has no label.`);
|
|
264
|
+
}
|
|
265
|
+
if (profileIds.has(profile.id)) {
|
|
266
|
+
problems.push(`Two profiles are “${profile.id}”.`);
|
|
267
|
+
}
|
|
268
|
+
profileIds.add(profile.id);
|
|
269
|
+
}
|
|
270
|
+
// How its settings' fields are drawn (P-20): by a widget that draws each.
|
|
271
|
+
if (app.interface.settingsUi) {
|
|
272
|
+
if (!app.interface.settings) {
|
|
273
|
+
problems.push('`settings_ui` draws the settings’ fields: say `settings` first.');
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
problems.push(...formUiProblems('The form “settings”', app.interface.settings, app.interface.settingsUi));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Its words in other languages (P-26): of languages, of what it says.
|
|
280
|
+
problems.push(...translationProblems(app.interface));
|
|
123
281
|
// What the host page passes and offers (D-10): named once each, in words
|
|
124
282
|
// its agent's tools can carry, each function saying what it does.
|
|
125
283
|
const host = app.deployment.embedded?.host;
|
|
@@ -147,6 +305,10 @@ function shapeProblems(app) {
|
|
|
147
305
|
if (new Set(names).size !== names.length) {
|
|
148
306
|
problems.push('The host’s functions are named once each.');
|
|
149
307
|
}
|
|
308
|
+
// Who its user is (D-21): what the page says, or what its server signed.
|
|
309
|
+
if (host.user !== undefined && !HOST_USERS.includes(host.user)) {
|
|
310
|
+
problems.push(`“${host.user}” is not how the host’s user is taken: claimed or signed.`);
|
|
311
|
+
}
|
|
150
312
|
const named = new Set(app.rules.flatMap(rule => rule.appliesTo));
|
|
151
313
|
for (const tool of hostToolsOf(host)) {
|
|
152
314
|
if (!named.has(tool)) {
|
|
@@ -154,6 +316,8 @@ function shapeProblems(app) {
|
|
|
154
316
|
}
|
|
155
317
|
}
|
|
156
318
|
}
|
|
319
|
+
// What its code declares (LOOP P-06), as agentspecs refuses it.
|
|
320
|
+
problems.push(...ownCodeProblems(app));
|
|
157
321
|
const servers = app.connections.map(connection => idOf(connection.server));
|
|
158
322
|
if (new Set(servers).size !== servers.length) {
|
|
159
323
|
problems.push('The application connects to the same server twice.');
|
|
@@ -212,6 +376,10 @@ export function formProblems(node) {
|
|
|
212
376
|
if (missing.length > 0) {
|
|
213
377
|
problems.push(`${said} requires ${missing.map(name => `“${String(name)}”`).join(', ')}, which it does not ask.`);
|
|
214
378
|
}
|
|
379
|
+
// How its fields are drawn (P-20): by a widget that draws each.
|
|
380
|
+
if (node.ui !== undefined && node.ui !== null) {
|
|
381
|
+
problems.push(...formUiProblems(said, schema, node.ui));
|
|
382
|
+
}
|
|
215
383
|
return problems;
|
|
216
384
|
}
|
|
217
385
|
const isPlainRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -278,6 +446,12 @@ function referenceProblems(app, organizationFrames) {
|
|
|
278
446
|
if (app.model && !getModel(app.model)) {
|
|
279
447
|
missing('model', app.model);
|
|
280
448
|
}
|
|
449
|
+
// The model a profile runs on is the catalogue's (LOOP P-20).
|
|
450
|
+
for (const profile of app.interface.profiles ?? []) {
|
|
451
|
+
if (profile.model && !getModel(profile.model)) {
|
|
452
|
+
problems.push(`The profile “${profile.id}” runs on “${profile.model}”, which is no model.`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
281
455
|
// The model a mode runs on is the catalogue's (LOOP P-19).
|
|
282
456
|
for (const mode of app.interface.modes ?? []) {
|
|
283
457
|
for (const option of mode.options) {
|
|
@@ -287,13 +461,21 @@ function referenceProblems(app, organizationFrames) {
|
|
|
287
461
|
}
|
|
288
462
|
}
|
|
289
463
|
// The components it may use, and those its surface uses, are the catalog's (C-13).
|
|
464
|
+
// Those its developer wrote are of the catalog for it alone (P-17).
|
|
290
465
|
for (const name of app.interface.components ?? []) {
|
|
291
|
-
if (!componentNamed(name)) {
|
|
466
|
+
if (!componentNamed(name) && !customComponentOf(app, name)) {
|
|
292
467
|
problems.push(`There is no component named “${name}” in the catalog.`);
|
|
293
468
|
}
|
|
294
469
|
}
|
|
295
470
|
for (const node of app.interface.surface?.components ?? []) {
|
|
296
|
-
|
|
471
|
+
const custom = customComponentOf(app, String(node.component));
|
|
472
|
+
if (custom) {
|
|
473
|
+
const refused = customNodeRefused(custom, node);
|
|
474
|
+
if (refused.length > 0) {
|
|
475
|
+
problems.push(`The surface's “${String(node.id)}” is a ${custom.name} its schema refuses: ${refused.join('; ')}.`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else if (!componentNamed(String(node.component))) {
|
|
297
479
|
problems.push(`The surface's “${String(node.id)}” is a “${String(node.component)}”, which the catalog does not have.`);
|
|
298
480
|
}
|
|
299
481
|
else if (node.component === 'Form') {
|
|
@@ -336,6 +518,9 @@ function referenceProblems(app, organizationFrames) {
|
|
|
336
518
|
if (server === undefined) {
|
|
337
519
|
if (hostTools.includes(target))
|
|
338
520
|
continue;
|
|
521
|
+
// A tool of its own, written in its code (LOOP P-06).
|
|
522
|
+
if ((app.tools ?? []).some(tool => tool.name === target))
|
|
523
|
+
continue;
|
|
339
524
|
if (!getBackendToolSpec(name)) {
|
|
340
525
|
problems.push(`The rule “${rule.action}” names the tool “${target}”, which the catalogue does not have.`);
|
|
341
526
|
}
|
|
@@ -646,9 +831,44 @@ export function documentShapeProblems(document) {
|
|
|
646
831
|
}
|
|
647
832
|
text(ui.welcome, 'interface.welcome');
|
|
648
833
|
texts(ui.components, 'interface.components');
|
|
649
|
-
|
|
834
|
+
const starter = (item, where) => {
|
|
650
835
|
required(item, 'label', where);
|
|
651
836
|
required(item, 'message', where);
|
|
837
|
+
text(item.category, `${where}.category`);
|
|
838
|
+
unknownKeys(item, ['label', 'message', 'category'], where, 'a starter');
|
|
839
|
+
};
|
|
840
|
+
records(ui.starters, 'interface.starters', starter);
|
|
841
|
+
// Its profiles (LOOP P-20), as agentspecs refuses them: no key unknown.
|
|
842
|
+
records(ui.profiles, 'interface.profiles', (profile, where) => {
|
|
843
|
+
required(profile, 'id', where);
|
|
844
|
+
required(profile, 'label', where);
|
|
845
|
+
for (const key of [
|
|
846
|
+
'id',
|
|
847
|
+
'label',
|
|
848
|
+
'description',
|
|
849
|
+
'instructions',
|
|
850
|
+
'model',
|
|
851
|
+
]) {
|
|
852
|
+
text(profile[key], `${where}.${key}`);
|
|
853
|
+
}
|
|
854
|
+
unknownKeys(profile, ['id', 'label', 'description', 'instructions', 'model', 'starters'], where, 'a profile');
|
|
855
|
+
records(profile.starters, `${where}.starters`, starter);
|
|
856
|
+
});
|
|
857
|
+
text(ui.language, 'interface.language');
|
|
858
|
+
mapping(ui.translations, 'interface.translations', translations => {
|
|
859
|
+
for (const [tag, words] of Object.entries(translations)) {
|
|
860
|
+
mapping(words, `interface.translations.${tag}`, said => unknownKeys(said, [
|
|
861
|
+
'name',
|
|
862
|
+
'description',
|
|
863
|
+
'welcome',
|
|
864
|
+
'starters',
|
|
865
|
+
'categories',
|
|
866
|
+
'settings',
|
|
867
|
+
'commands',
|
|
868
|
+
'modes',
|
|
869
|
+
'profiles',
|
|
870
|
+
], `interface.translations.${tag}`, 'a translation'));
|
|
871
|
+
}
|
|
652
872
|
});
|
|
653
873
|
// Its commands and modes in the composer (LOOP P-19), as agentspecs
|
|
654
874
|
// refuses them: names and ids slash-safe and said once, no key unknown.
|
|
@@ -738,11 +958,62 @@ export function documentShapeProblems(document) {
|
|
|
738
958
|
problems.push(...formProblems({ id: 'settings', schema: settings }));
|
|
739
959
|
});
|
|
740
960
|
}
|
|
961
|
+
// What a person may send without being asked (LOOP P-21), as agentspecs
|
|
962
|
+
// refuses it: a kind at least, each a type said once, sizes it takes.
|
|
963
|
+
mapping(ui.uploads, 'interface.uploads', uploads => {
|
|
964
|
+
unknownKeys(uploads, ['kinds', 'max_files'], 'interface.uploads', 'uploads');
|
|
965
|
+
required(uploads, 'kinds', 'interface.uploads');
|
|
966
|
+
const types = new Set();
|
|
967
|
+
records(uploads.kinds, 'interface.uploads.kinds', (kind, where) => {
|
|
968
|
+
required(kind, 'type', where);
|
|
969
|
+
unknownKeys(kind, ['type', 'max_mb'], where, 'a kind of upload');
|
|
970
|
+
if (kind.type !== undefined) {
|
|
971
|
+
if (typeof kind.type !== 'string' || !UPLOAD_KIND.test(kind.type)) {
|
|
972
|
+
at(`${where}.type`, 'is a media type (application/pdf), a family (image/*) or an extension (.csv), lowercase');
|
|
973
|
+
}
|
|
974
|
+
else if (types.has(kind.type)) {
|
|
975
|
+
at(`${where}.type`, `names ${kind.type} a second time`);
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
types.add(kind.type);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const size = kind.max_mb;
|
|
982
|
+
if (size !== undefined &&
|
|
983
|
+
(typeof size !== 'number' || size <= 0 || size > MAX_UPLOAD_MB)) {
|
|
984
|
+
at(`${where}.max_mb`, `is more than 0 and ${MAX_UPLOAD_MB} at most`);
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
if (Array.isArray(uploads.kinds) && uploads.kinds.length === 0) {
|
|
988
|
+
at('interface.uploads.kinds', 'are one at least');
|
|
989
|
+
}
|
|
990
|
+
const most = uploads.max_files;
|
|
991
|
+
if (most !== undefined &&
|
|
992
|
+
(typeof most !== 'number' ||
|
|
993
|
+
!Number.isInteger(most) ||
|
|
994
|
+
most < 1 ||
|
|
995
|
+
most > 20)) {
|
|
996
|
+
at('interface.uploads.max_files', 'is a whole number from 1 to 20');
|
|
997
|
+
}
|
|
998
|
+
});
|
|
741
999
|
mapping(ui.surface, 'interface.surface', surface => {
|
|
742
1000
|
records(surface.components, 'interface.surface.components', (item, where) => {
|
|
743
1001
|
required(item, 'id', where);
|
|
744
1002
|
required(item, 'component', where);
|
|
745
1003
|
});
|
|
1004
|
+
}); // A widget's page written in its code (LOOP P-05), as agentspecs reads it.
|
|
1005
|
+
mapping(ui.page, 'interface.page', page => {
|
|
1006
|
+
unknownKeys(page, ['function', 'inputs', 'inputs_ui', 'outputs', 'live'], 'interface.page', 'a page');
|
|
1007
|
+
required(page, 'function', 'interface.page');
|
|
1008
|
+
required(page, 'inputs', 'interface.page');
|
|
1009
|
+
required(page, 'outputs', 'interface.page');
|
|
1010
|
+
if (page.live !== undefined && typeof page.live !== 'boolean') {
|
|
1011
|
+
at('interface.page.live', 'is true or false');
|
|
1012
|
+
}
|
|
1013
|
+
records(page.outputs, 'interface.page.outputs', (output, where) => {
|
|
1014
|
+
required(output, 'name', where);
|
|
1015
|
+
unknownKeys(output, ['name', 'title', 'component', 'props'], where, 'an output');
|
|
1016
|
+
});
|
|
746
1017
|
});
|
|
747
1018
|
});
|
|
748
1019
|
mapping(d.tests, 'tests', tests => {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Commands and
|
|
3
|
-
* sends,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Commands, modes and profiles in the composer (LOOP P-19, P-20), as rules:
|
|
3
|
+
* what a command sends, which option of each mode a run is in, which profile
|
|
4
|
+
* a conversation is with and what it offers. agentspecs' own
|
|
5
|
+
* (`command_prompt`, `AppInterface.mode_choice`, `mode_effect`,
|
|
6
|
+
* `profile_choice`, `starters_for`), said again for the page.
|
|
6
7
|
*
|
|
7
8
|
* Pure: no React, no network.
|
|
8
9
|
*
|
|
9
10
|
* @module loop/apps/composer
|
|
10
11
|
*/
|
|
11
|
-
import type { AppCommandSpec, AppModeEffect, AppSpec } from '../../types/agentspecs';
|
|
12
|
+
import type { AppCommandSpec, AppModeEffect, AppProfileSpec, AppSpec, AppStarterSpec } from '../../types/agentspecs';
|
|
12
13
|
/** Where a command's prompt takes the words typed after it. */
|
|
13
14
|
export declare const COMMAND_INPUT = "{input}";
|
|
14
15
|
/** What follows the slash: lower-case letters, digits and hyphens, a letter first. */
|
|
@@ -29,9 +30,19 @@ export declare function modeDefaults(app: Pick<AppSpec, 'interface'>): Record<st
|
|
|
29
30
|
*/
|
|
30
31
|
export declare function modeChoice(app: Pick<AppSpec, 'name' | 'interface'>, chosen?: Record<string, string>): Record<string, string>;
|
|
31
32
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* {@link modeChoice} refuses.
|
|
33
|
+
* The profile a conversation is with (LOOP P-20): the one chosen, else the
|
|
34
|
+
* first; undefined for an application without profiles. One it does not have
|
|
35
|
+
* is refused, in a sentence, as the runtime refuses it.
|
|
36
36
|
*/
|
|
37
|
-
export declare function
|
|
37
|
+
export declare function profileChoice(app: Pick<AppSpec, 'name' | 'interface'>, chosen?: string): AppProfileSpec | undefined;
|
|
38
|
+
/** The starters offered with a profile: its own, else the application's (P-20). */
|
|
39
|
+
export declare function startersFor(app: Pick<AppSpec, 'name' | 'interface'>, profile?: string): AppStarterSpec[];
|
|
40
|
+
/**
|
|
41
|
+
* What a run in the modes chosen, with the profile chosen, is told, and the
|
|
42
|
+
* model it runs on — agentspecs' `mode_effect`, and the runtime's
|
|
43
|
+
* `run_effect`: the profile's instructions first, then those of every option
|
|
44
|
+
* chosen, in the order of the modes; the model of the first option that
|
|
45
|
+
* names one, else the profile's. Refused as {@link modeChoice} and
|
|
46
|
+
* {@link profileChoice} refuse.
|
|
47
|
+
*/
|
|
48
|
+
export declare function modeEffect(app: Pick<AppSpec, 'name' | 'interface'>, chosen?: Record<string, string>, profile?: string): AppModeEffect;
|
|
@@ -44,18 +44,51 @@ export function modeChoice(app, chosen = {}) {
|
|
|
44
44
|
return { ...modeDefaults(app), ...chosen };
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* {@link modeChoice} refuses.
|
|
47
|
+
* The profile a conversation is with (LOOP P-20): the one chosen, else the
|
|
48
|
+
* first; undefined for an application without profiles. One it does not have
|
|
49
|
+
* is refused, in a sentence, as the runtime refuses it.
|
|
51
50
|
*/
|
|
52
|
-
export function
|
|
51
|
+
export function profileChoice(app, chosen) {
|
|
52
|
+
const profiles = app.interface.profiles ?? [];
|
|
53
|
+
if (chosen) {
|
|
54
|
+
const profile = profiles.find(candidate => candidate.id === chosen);
|
|
55
|
+
if (!profile) {
|
|
56
|
+
throw new Error(profiles.length === 0
|
|
57
|
+
? `${app.name} has no profiles: “${chosen}” cannot be chosen.`
|
|
58
|
+
: `${app.name} has no profile “${chosen}”.`);
|
|
59
|
+
}
|
|
60
|
+
return profile;
|
|
61
|
+
}
|
|
62
|
+
return profiles[0];
|
|
63
|
+
}
|
|
64
|
+
/** The starters offered with a profile: its own, else the application's (P-20). */
|
|
65
|
+
export function startersFor(app, profile) {
|
|
66
|
+
const chosen = profileChoice(app, profile);
|
|
67
|
+
return chosen && chosen.starters.length > 0
|
|
68
|
+
? chosen.starters
|
|
69
|
+
: app.interface.starters;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What a run in the modes chosen, with the profile chosen, is told, and the
|
|
73
|
+
* model it runs on — agentspecs' `mode_effect`, and the runtime's
|
|
74
|
+
* `run_effect`: the profile's instructions first, then those of every option
|
|
75
|
+
* chosen, in the order of the modes; the model of the first option that
|
|
76
|
+
* names one, else the profile's. Refused as {@link modeChoice} and
|
|
77
|
+
* {@link profileChoice} refuse.
|
|
78
|
+
*/
|
|
79
|
+
export function modeEffect(app, chosen = {}, profile) {
|
|
53
80
|
const choice = modeChoice(app, chosen);
|
|
81
|
+
const withProfile = profileChoice(app, profile);
|
|
54
82
|
const options = (app.interface.modes ?? []).flatMap(mode => mode.options.filter(option => option.id === choice[mode.id]));
|
|
55
|
-
const instructions =
|
|
56
|
-
|
|
83
|
+
const instructions = [
|
|
84
|
+
withProfile?.instructions ?? '',
|
|
85
|
+
...options.map(option => option.instructions ?? ''),
|
|
86
|
+
]
|
|
87
|
+
.map(part => part.trim())
|
|
57
88
|
.filter(Boolean)
|
|
58
89
|
.join('\n\n');
|
|
59
|
-
const model = options.find(option => option.model)?.model ??
|
|
90
|
+
const model = options.find(option => option.model)?.model ??
|
|
91
|
+
withProfile?.model ??
|
|
92
|
+
undefined;
|
|
60
93
|
return model ? { instructions, model } : { instructions };
|
|
61
94
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppCustomComponentSpec, AppSpec, ComponentSpec } from '../../types/agentspecs';
|
|
2
|
+
/** Its name: a word starting with a capital letter, as the catalog's are named. */
|
|
3
|
+
export declare const CUSTOM_COMPONENT_NAME: RegExp;
|
|
4
|
+
/** What one of its properties may be typed: what the renderer tells apart. */
|
|
5
|
+
export declare const CUSTOM_PROPERTY_TYPES: readonly ["string", "integer", "number", "boolean", "array", "object"];
|
|
6
|
+
/** What every component of a surface already has, and a custom one cannot declare. */
|
|
7
|
+
export declare const CUSTOM_RESERVED_NAMES: readonly ["id", "component", "action", "weight", "visible_when", "children", "child"];
|
|
8
|
+
/**
|
|
9
|
+
* Where its module is loaded from: an address served over HTTPS, or this
|
|
10
|
+
* machine while it is written. A file of the application's folder waits for
|
|
11
|
+
* its packaging (P-29).
|
|
12
|
+
*/
|
|
13
|
+
export declare const CUSTOM_SOURCE: RegExp;
|
|
14
|
+
/** A Subresource Integrity hash: the module as it was reviewed. */
|
|
15
|
+
export declare const INTEGRITY: RegExp;
|
|
16
|
+
/**
|
|
17
|
+
* What its schema refuses of the properties given, in the words agentspecs
|
|
18
|
+
* says them: one it does not have, a value of another type, one it needs.
|
|
19
|
+
* `bound` are given elsewhere — bound to what the application publishes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function customPropsRefused(component: AppCustomComponentSpec, props: Record<string, unknown>, bound?: readonly string[]): string[];
|
|
22
|
+
/** What stops one component its developer wrote from being reviewed, in sentences. */
|
|
23
|
+
export declare function customComponentProblems(component: AppCustomComponentSpec): string[];
|
|
24
|
+
/** What stops the components an application's developer wrote, in sentences. */
|
|
25
|
+
export declare function customComponentsProblems(app: AppSpec): string[];
|
|
26
|
+
/** A component its developer wrote, by name, or undefined. */
|
|
27
|
+
export declare function customComponentOf(app: Pick<AppSpec, 'interface'>, name: string): AppCustomComponentSpec | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* It as the catalog lists a component (C-13): what the palette, the
|
|
30
|
+
* components page and the checks read — under the application's version, its
|
|
31
|
+
* category `custom`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function customComponentEntry(component: AppCustomComponentSpec, version: string): ComponentSpec;
|
|
34
|
+
/**
|
|
35
|
+
* The components an application may place: the catalog's own, given, and
|
|
36
|
+
* those its developer wrote, for it alone. A name of its own that the
|
|
37
|
+
* catalog has is not listed twice: the checks refuse it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function appComponents(app: Pick<AppSpec, 'interface' | 'version'>, catalog: readonly ComponentSpec[]): ComponentSpec[];
|
|
40
|
+
/** What its schema refuses of a node of a surface drawn by it, in sentences; empty when nothing. */
|
|
41
|
+
export declare function customNodeRefused(component: AppCustomComponentSpec, node: Record<string, unknown>): string[];
|