@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.
Files changed (168) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +30 -11
  17. package/lib/chat/base/loopElement.d.ts +53 -0
  18. package/lib/chat/base/loopElement.js +78 -0
  19. package/lib/chat/base/loopWindow.d.ts +31 -0
  20. package/lib/chat/base/loopWindow.js +74 -0
  21. package/lib/chat/display/EmptyState.js +3 -1
  22. package/lib/chat/index.d.ts +2 -0
  23. package/lib/chat/index.js +3 -0
  24. package/lib/chat/messages/ChatMessages.js +3 -1
  25. package/lib/chat/messages/TurnFooter.js +3 -1
  26. package/lib/chat/prompt/InputPrompt.js +3 -1
  27. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  28. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  29. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  30. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  31. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  32. package/lib/chat/words.d.ts +102 -0
  33. package/lib/chat/words.js +460 -0
  34. package/lib/components/a2ui/custom/index.d.ts +39 -0
  35. package/lib/components/a2ui/custom/index.js +95 -0
  36. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  37. package/lib/components/a2ui/custom/sandbox.js +188 -0
  38. package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
  39. package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
  40. package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
  41. package/lib/components/a2ui/datalayer/Form.js +9 -2
  42. package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
  43. package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
  44. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/index.js +1 -1
  46. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  47. package/lib/components/a2ui/datalayer/schema.js +12 -1
  48. package/lib/components/a2ui/index.d.ts +1 -0
  49. package/lib/components/a2ui/index.js +1 -0
  50. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  51. package/lib/components/a2ui/visibleWhen.js +24 -3
  52. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  53. package/lib/components/inspector/AgentInspector.js +27 -215
  54. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  55. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  56. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  57. package/lib/components/inspector/a2aSpans.js +373 -0
  58. package/lib/components/inspector/agentSpans.d.ts +120 -0
  59. package/lib/components/inspector/agentSpans.js +253 -0
  60. package/lib/components/inspector/chatSpans.d.ts +36 -0
  61. package/lib/components/inspector/chatSpans.js +50 -0
  62. package/lib/components/inspector/classify.d.ts +11 -8
  63. package/lib/components/inspector/classify.js +23 -24
  64. package/lib/components/inspector/index.d.ts +7 -7
  65. package/lib/components/inspector/index.js +7 -7
  66. package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
  67. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  68. package/lib/components/teams/SceneTranscript.js +54 -0
  69. package/lib/components/teams/SceneView.d.ts +41 -0
  70. package/lib/components/teams/SceneView.js +28 -0
  71. package/lib/components/teams/index.d.ts +3 -0
  72. package/lib/components/teams/index.js +3 -0
  73. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  74. package/lib/components/teams/sceneTranscript.js +294 -0
  75. package/lib/components/teams/teamConnections.d.ts +2 -2
  76. package/lib/components/teams/teamConnections.js +3 -3
  77. package/lib/components/teams/useA2ATeam.d.ts +4 -4
  78. package/lib/components/teams/useA2ATeam.js +50 -46
  79. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  80. package/lib/examples/AgentA2ATeamExample.js +70 -60
  81. package/lib/examples/AssistantExample.js +2 -2
  82. package/lib/loop/apps/AppComposer.d.ts +40 -4
  83. package/lib/loop/apps/AppComposer.js +194 -19
  84. package/lib/loop/apps/AppRenderer.d.ts +21 -3
  85. package/lib/loop/apps/AppRenderer.js +88 -48
  86. package/lib/loop/apps/appspec.d.ts +13 -1
  87. package/lib/loop/apps/appspec.js +305 -12
  88. package/lib/loop/apps/checks.d.ts +14 -0
  89. package/lib/loop/apps/checks.js +276 -5
  90. package/lib/loop/apps/composer.d.ts +21 -10
  91. package/lib/loop/apps/composer.js +41 -8
  92. package/lib/loop/apps/customComponents.d.ts +41 -0
  93. package/lib/loop/apps/customComponents.js +260 -0
  94. package/lib/loop/apps/hostTools.d.ts +8 -1
  95. package/lib/loop/apps/hostTools.js +9 -0
  96. package/lib/loop/apps/index.d.ts +1 -0
  97. package/lib/loop/apps/index.js +1 -0
  98. package/lib/loop/apps/language.d.ts +68 -0
  99. package/lib/loop/apps/language.js +275 -0
  100. package/lib/loop/apps/settingsInputs.d.ts +29 -0
  101. package/lib/loop/apps/settingsInputs.js +147 -0
  102. package/lib/loop/apps/uploads.d.ts +43 -0
  103. package/lib/loop/apps/uploads.js +66 -0
  104. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  105. package/lib/loop/core/a2uiComponents.js +15 -0
  106. package/lib/loop/core/index.d.ts +15 -1
  107. package/lib/loop/core/index.js +3 -0
  108. package/lib/loop/embed/AppEmbed.d.ts +25 -3
  109. package/lib/loop/embed/AppEmbed.js +106 -18
  110. package/lib/loop/embed/element.d.ts +5 -0
  111. package/lib/loop/embed/element.js +84 -5
  112. package/lib/loop/embed/embedConfig.d.ts +8 -0
  113. package/lib/loop/embed/embedConfig.js +18 -0
  114. package/lib/loop/embed/embedSession.d.ts +14 -0
  115. package/lib/loop/embed/embedSession.js +34 -0
  116. package/lib/loop/embed/hostBridge.d.ts +50 -3
  117. package/lib/loop/embed/hostBridge.js +22 -2
  118. package/lib/loop/index.d.ts +4 -3
  119. package/lib/loop/index.js +4 -3
  120. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  121. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  122. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  123. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  124. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  125. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  126. package/lib/loop/plugins/app-components/index.js +56 -0
  127. package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
  128. package/lib/loop/plugins/app-elements/AppElements.js +79 -0
  129. package/lib/loop/plugins/app-elements/index.d.ts +20 -0
  130. package/lib/loop/plugins/app-elements/index.js +43 -0
  131. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  132. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  133. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  134. package/lib/loop/plugins/app-page/appPageModel.js +122 -4
  135. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  136. package/lib/loop/plugins/app-page/index.js +1 -0
  137. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  138. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  139. package/lib/loop/plugins/assistant/index.js +8 -6
  140. package/lib/loop/plugins/chat/ChatView.js +16 -6
  141. package/lib/loop/plugins/chat/index.d.ts +1 -1
  142. package/lib/loop/plugins/chat/index.js +2 -1
  143. package/lib/models/PageTag.d.ts +1 -1
  144. package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
  145. package/lib/protocols/BrowserAgentAdapter.js +13 -10
  146. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  147. package/lib/runtimes/browser/protocol.d.ts +1 -1
  148. package/lib/specs/actions.js +78 -1
  149. package/lib/specs/apps.d.ts +5 -0
  150. package/lib/specs/apps.js +1518 -216
  151. package/lib/specs/appspecSchema.js +630 -3
  152. package/lib/specs/teams/teams.d.ts +3 -0
  153. package/lib/specs/teams/teams.js +249 -0
  154. package/lib/specs/uiPlugins.js +6 -1
  155. package/lib/types/agentspecs.d.ts +200 -1
  156. package/lib/types/chat.d.ts +6 -5
  157. package/lib/types/teams.d.ts +13 -2
  158. package/package.json +2 -2
  159. package/scripts/codegen/generate_apps.py +20 -2
  160. package/scripts/codegen/generate_teams.py +15 -2
  161. package/lib/components/inspector/JsonTree.d.ts +0 -24
  162. package/lib/components/inspector/JsonTree.js +0 -89
  163. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  164. package/lib/components/inspector/a2aInspect.js +0 -478
  165. package/lib/components/inspector/agentInspector.d.ts +0 -179
  166. package/lib/components/inspector/agentInspector.js +0 -267
  167. package/lib/components/inspector/chatInspect.d.ts +0 -38
  168. package/lib/components/inspector/chatInspect.js +0 -65
@@ -0,0 +1,260 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Components a developer writes (LOOP P-17): an A2UI component of one
7
+ * application only, declared in its Appspec (`interface.custom_components`)
8
+ * with its name, its properties as a JSON Schema, what it shows and sends,
9
+ * and the address of the built ES module that draws it.
10
+ *
11
+ * Reviewed like any component of the catalog — what agentspecs refuses is
12
+ * refused here in the same sentences — and listed beside the catalog's for
13
+ * that application alone (`appComponents`): the catalog grows for it, it
14
+ * does not open. How it is drawn, in a sandboxed frame of no origin, is
15
+ * `components/a2ui/custom`.
16
+ *
17
+ * @module loop/apps/customComponents
18
+ */
19
+ import { getComponent } from '../../specs/uiPlugins';
20
+ /** Its name: a word starting with a capital letter, as the catalog's are named. */
21
+ export const CUSTOM_COMPONENT_NAME = /^[A-Z][A-Za-z0-9]{0,63}$/;
22
+ /** What one of its properties may be typed: what the renderer tells apart. */
23
+ export const CUSTOM_PROPERTY_TYPES = [
24
+ 'string',
25
+ 'integer',
26
+ 'number',
27
+ 'boolean',
28
+ 'array',
29
+ 'object',
30
+ ];
31
+ /** What every component of a surface already has, and a custom one cannot declare. */
32
+ export const CUSTOM_RESERVED_NAMES = [
33
+ 'id',
34
+ 'component',
35
+ 'action',
36
+ 'weight',
37
+ 'visible_when',
38
+ 'children',
39
+ 'child',
40
+ ];
41
+ /**
42
+ * Where its module is loaded from: an address served over HTTPS, or this
43
+ * machine while it is written. A file of the application's folder waits for
44
+ * its packaging (P-29).
45
+ */
46
+ export const CUSTOM_SOURCE = /^(?:https:\/\/[A-Za-z0-9.-]+(?::\d+)?|http:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?)\/\S*$/;
47
+ /** A Subresource Integrity hash: the module as it was reviewed. */
48
+ export const INTEGRITY = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
49
+ const PROPERTY_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
50
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ const isBound = (value) => isRecord(value) && 'path' in value;
52
+ const quoted = (name) => `'${name}'`;
53
+ /** Why a value is not one a property takes, or null. */
54
+ function valueRefused(name, field, value) {
55
+ if (Array.isArray(field.enum)) {
56
+ return field.enum.includes(value)
57
+ ? null
58
+ : `${quoted(name)} is one of ${field.enum.map(each => quoted(String(each))).join(', ')}`;
59
+ }
60
+ const kind = String(field.type);
61
+ const takes = {
62
+ string: v => typeof v === 'string',
63
+ integer: v => typeof v === 'number' && Number.isInteger(v),
64
+ number: v => typeof v === 'number',
65
+ boolean: v => typeof v === 'boolean',
66
+ array: v => Array.isArray(v),
67
+ object: isRecord,
68
+ };
69
+ if (typeof value === 'boolean' && kind !== 'boolean') {
70
+ return `${quoted(name)} is ${kind}, not true or false`;
71
+ }
72
+ return takes[kind]?.(value) ? null : `${quoted(name)} is ${kind}`;
73
+ }
74
+ /** Its properties, as its schema declares them. */
75
+ const fieldsOf = (component) => (isRecord(component.props.properties)
76
+ ? component.props.properties
77
+ : {});
78
+ /**
79
+ * What its schema refuses of the properties given, in the words agentspecs
80
+ * says them: one it does not have, a value of another type, one it needs.
81
+ * `bound` are given elsewhere — bound to what the application publishes.
82
+ */
83
+ export function customPropsRefused(component, props, bound = []) {
84
+ const fields = fieldsOf(component);
85
+ const problems = Object.keys(props)
86
+ .filter(name => !(name in fields))
87
+ .map(name => `it has no property ${quoted(name)}`);
88
+ for (const [name, value] of Object.entries(props)) {
89
+ if (name in fields && !isBound(value)) {
90
+ const refused = valueRefused(name, fields[name], value);
91
+ if (refused) {
92
+ problems.push(refused);
93
+ }
94
+ }
95
+ }
96
+ const required = Array.isArray(component.props.required)
97
+ ? component.props.required
98
+ : [];
99
+ for (const name of required) {
100
+ if (!(name in props) && !bound.includes(name)) {
101
+ problems.push(`it needs ${quoted(name)}`);
102
+ }
103
+ }
104
+ return problems;
105
+ }
106
+ /** What stops one component its developer wrote from being reviewed, in sentences. */
107
+ export function customComponentProblems(component) {
108
+ const said = `The component ${component.name || '(unnamed)'}`;
109
+ const problems = [];
110
+ if (!CUSTOM_COMPONENT_NAME.test(component.name)) {
111
+ problems.push(`Cannot use “${component.name}” as a component's name: a word starting with a capital letter, as Gauge.`);
112
+ }
113
+ else if (getComponent(component.name)) {
114
+ problems.push(`${said} is a component of the catalog: name yours otherwise.`);
115
+ }
116
+ if (!component.description.trim()) {
117
+ problems.push(`${said} says what it is for, under \`description\`.`);
118
+ }
119
+ if (!/^[a-z][a-z0-9+.-]*:/.test(component.source)) {
120
+ problems.push(`${said}'s module “${component.source}” is a file of the application's folder: it is drawn once the application is packaged with it (LOOP P-29); give the address of a built ES module.`);
121
+ }
122
+ else if (!CUSTOM_SOURCE.test(component.source)) {
123
+ problems.push(`${said}'s module “${component.source}” is loaded over \`https://\`, or from \`http://localhost\` while it is written.`);
124
+ }
125
+ if (component.integrity && !INTEGRITY.test(component.integrity)) {
126
+ problems.push(`${said}'s integrity “${component.integrity}” is no Subresource Integrity hash: \`sha384-\` and the hash in base64.`);
127
+ }
128
+ if (component.height < 40 || component.height > 2000) {
129
+ problems.push(`${said} is between 40 and 2000 pixels high.`);
130
+ }
131
+ const schema = component.props;
132
+ if (schema.type !== 'object' || !isRecord(schema.properties)) {
133
+ problems.push(`${said}'s props are the JSON Schema of an object, with its \`properties\`.`);
134
+ return problems;
135
+ }
136
+ const fields = fieldsOf(component);
137
+ for (const [name, field] of Object.entries(fields)) {
138
+ if (!PROPERTY_NAME.test(name) ||
139
+ CUSTOM_RESERVED_NAMES.includes(name)) {
140
+ problems.push(`${said} cannot have a property named ${quoted(name)}.`);
141
+ continue;
142
+ }
143
+ if (!isRecord(field)) {
144
+ problems.push(`${said}'s property ${quoted(name)} is not a schema.`);
145
+ continue;
146
+ }
147
+ if ('enum' in field) {
148
+ const values = field.enum;
149
+ if (!Array.isArray(values) ||
150
+ values.length === 0 ||
151
+ !values.every(value => typeof value === 'string')) {
152
+ problems.push(`${said}'s property ${quoted(name)} is an \`enum\` of words, one at least.`);
153
+ }
154
+ }
155
+ else if (!CUSTOM_PROPERTY_TYPES.includes(field.type)) {
156
+ problems.push(`${said}'s property ${quoted(name)} is typed ${quoted(String(field.type))}: one of ${CUSTOM_PROPERTY_TYPES.join(', ')}, or an \`enum\`.`);
157
+ continue;
158
+ }
159
+ if ('default' in field) {
160
+ const refused = valueRefused(name, field, field.default);
161
+ if (refused) {
162
+ problems.push(`${said}'s default for ${refused}.`);
163
+ }
164
+ }
165
+ }
166
+ const required = Array.isArray(schema.required)
167
+ ? schema.required
168
+ : [];
169
+ const missing = required.filter(name => !(name in fields));
170
+ if (missing.length > 0) {
171
+ problems.push(`${said} requires ${missing.map(quoted).join(', ')}, which its props do not have.`);
172
+ }
173
+ for (const name of [...component.shows, ...component.sends]) {
174
+ if (!PROPERTY_NAME.test(name) ||
175
+ CUSTOM_RESERVED_NAMES.includes(name)) {
176
+ problems.push(`${said} cannot bind through ${quoted(name)}.`);
177
+ }
178
+ else if (name in fields) {
179
+ problems.push(`${said} both has the property ${quoted(name)} and binds through it.`);
180
+ }
181
+ }
182
+ for (const [bindings, what] of [
183
+ [component.shows, 'shows'],
184
+ [component.sends, 'sends'],
185
+ ]) {
186
+ if (new Set(bindings).size !== bindings.length) {
187
+ problems.push(`${said} ${what} the same binding twice.`);
188
+ }
189
+ }
190
+ if (component.example) {
191
+ const bindings = new Set([...component.shows, ...component.sends]);
192
+ const given = Object.fromEntries(Object.entries(component.example).filter(([key]) => !bindings.has(key)));
193
+ const refused = customPropsRefused(component, given);
194
+ if (refused.length > 0) {
195
+ problems.push(`${said}'s example is refused: ${refused.join('; ')}.`);
196
+ }
197
+ }
198
+ return problems;
199
+ }
200
+ /** What stops the components an application's developer wrote, in sentences. */
201
+ export function customComponentsProblems(app) {
202
+ const own = app.interface.customComponents ?? [];
203
+ const problems = own.flatMap(customComponentProblems);
204
+ const names = own.map(component => component.name);
205
+ if (new Set(names).size !== names.length) {
206
+ problems.push('Two of its own components have the same name.');
207
+ }
208
+ return problems;
209
+ }
210
+ /** A component its developer wrote, by name, or undefined. */
211
+ export function customComponentOf(app, name) {
212
+ return (app.interface.customComponents ?? []).find(component => component.name === name);
213
+ }
214
+ /**
215
+ * It as the catalog lists a component (C-13): what the palette, the
216
+ * components page and the checks read — under the application's version, its
217
+ * category `custom`.
218
+ */
219
+ export function customComponentEntry(component, version) {
220
+ return {
221
+ id: component.name,
222
+ name: component.name,
223
+ description: component.description,
224
+ category: 'custom',
225
+ emoji: '🧩',
226
+ version,
227
+ standard: false,
228
+ properties: component.props,
229
+ bindings: { shows: [...component.shows], sends: [...component.sends] },
230
+ events: component.sends.length > 0 ? ['send'] : [],
231
+ ...(component.example ? { example: component.example } : {}),
232
+ };
233
+ }
234
+ /**
235
+ * The components an application may place: the catalog's own, given, and
236
+ * those its developer wrote, for it alone. A name of its own that the
237
+ * catalog has is not listed twice: the checks refuse it.
238
+ */
239
+ export function appComponents(app, catalog) {
240
+ const names = new Set(catalog.map(component => component.id));
241
+ return [
242
+ ...catalog,
243
+ ...(app.interface.customComponents ?? [])
244
+ .filter(component => !names.has(component.name))
245
+ .map(component => customComponentEntry(component, app.version)),
246
+ ];
247
+ }
248
+ /** What its schema refuses of a node of a surface drawn by it, in sentences; empty when nothing. */
249
+ export function customNodeRefused(component, node) {
250
+ const skip = new Set([
251
+ ...CUSTOM_RESERVED_NAMES,
252
+ ...component.shows,
253
+ ...component.sends,
254
+ ]);
255
+ const given = Object.fromEntries(Object.entries(node).filter(([key]) => !skip.has(key)));
256
+ const bound = Object.entries(given)
257
+ .filter(([, value]) => isBound(value))
258
+ .map(([key]) => key);
259
+ return customPropsRefused(component, given, bound);
260
+ }
@@ -6,12 +6,19 @@
6
6
  *
7
7
  * @module loop/apps/hostTools
8
8
  */
9
- import type { AppHostBridgeSpec } from '../../types/agentspecs';
9
+ import type { AppHostBridgeSpec, AppHostUser } from '../../types/agentspecs';
10
10
  /** The tool its agent reads what the page passes it with. */
11
11
  export declare const HOST_CONTEXT_TOOL = "host_context";
12
12
  /** The tool its agent calls a function of the page with. */
13
13
  export declare const hostTool: (name: string) => string;
14
14
  /** How a value or a function of the host is named. */
15
15
  export declare const HOST_NAME: RegExp;
16
+ /** What the page's word on its visitor may be worth (LOOP D-21). */
17
+ export declare const HOST_USERS: readonly AppHostUser[];
18
+ /**
19
+ * Whether a session is opened only for a user the host's server signed
20
+ * (LOOP D-21): `deployment.embedded.host.user: signed`.
21
+ */
22
+ export declare function signedUser(bridge: AppHostBridgeSpec | undefined): boolean;
16
23
  /** The tools its agent is given for the host. */
17
24
  export declare function hostToolsOf(bridge: AppHostBridgeSpec | undefined): string[];
@@ -8,6 +8,15 @@ export const HOST_CONTEXT_TOOL = 'host_context';
8
8
  export const hostTool = (name) => `host_${name}`;
9
9
  /** How a value or a function of the host is named. */
10
10
  export const HOST_NAME = /^[a-z][a-z0-9_]{0,62}$/;
11
+ /** What the page's word on its visitor may be worth (LOOP D-21). */
12
+ export const HOST_USERS = ['claimed', 'signed'];
13
+ /**
14
+ * Whether a session is opened only for a user the host's server signed
15
+ * (LOOP D-21): `deployment.embedded.host.user: signed`.
16
+ */
17
+ export function signedUser(bridge) {
18
+ return bridge?.user === 'signed';
19
+ }
11
20
  /** The tools its agent is given for the host. */
12
21
  export function hostToolsOf(bridge) {
13
22
  if (!bridge) {
@@ -18,3 +18,4 @@ export * from './records';
18
18
  export * from './rules';
19
19
  export * from './saved';
20
20
  export * from './yaml';
21
+ export * from './customComponents';
@@ -22,3 +22,4 @@ export * from './records';
22
22
  export * from './rules';
23
23
  export * from './saved';
24
24
  export * from './yaml';
25
+ export * from './customComponents';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * An application in the person's language (LOOP P-26).
3
+ *
4
+ * An Appspec says its words in its own language (`interface.language`, `en`
5
+ * unless said) and may say them in others (`interface.translations`, by BCP
6
+ * 47 tag): its name, description and welcome, its starters by label and
7
+ * their categories, its settings' fields, its commands' descriptions, its
8
+ * modes and its profiles. The page shows the first language the person
9
+ * prefers that the application has — the browser's, unless its host says
10
+ * another — and its own words otherwise; what is not translated stays in its
11
+ * own words. Only what is shown changes: ids, values and prompts are the same
12
+ * in every language, and a starter's message is sent in the person's.
13
+ *
14
+ * agentspecs' `LANGUAGE_TAG`, `pick_language`, `AppSpec.translated` and the
15
+ * translation checks, said again for the page. The interface's own words —
16
+ * the composer's, the profile picker's — are {@link interfaceWords}.
17
+ *
18
+ * Pure: no React, no network ({@link preferredLanguages} reads the browser's).
19
+ *
20
+ * @module loop/apps/language
21
+ */
22
+ import type { AppInterfaceSpec, AppSpec } from '../../types/agentspecs';
23
+ /**
24
+ * A language as BCP 47 tags it: two or three letters, then optionally a
25
+ * script, a region and variants — `fr`, `pt-BR`, `zh-Hant-TW`.
26
+ */
27
+ export declare const LANGUAGE_TAG: RegExp;
28
+ /**
29
+ * The first language a person prefers that is available, or null. Tags are
30
+ * compared without case; a preferred tag also takes one of its language
31
+ * alone, then one of the same language (`fr-CA` takes `fr`, then `fr-FR`).
32
+ */
33
+ export declare function pickLanguage(available: readonly string[], preferred: readonly string[]): string | null;
34
+ /** The languages the browser says the person prefers, in their order; none outside one. */
35
+ export declare function preferredLanguages(): string[];
36
+ /** The language an application is read in by a person who prefers these. */
37
+ export declare function appLanguage(app: Pick<AppSpec, 'interface'>, preferred: readonly string[]): string;
38
+ /**
39
+ * The application as a person who prefers these languages reads it: its
40
+ * name, description and interface in the first of them it is translated
41
+ * into, else itself. What it does — its agent, its rules, its ids, its
42
+ * values, its prompts — is the same.
43
+ */
44
+ export declare function translatedAppspec(app: AppSpec, preferred: readonly string[]): AppSpec;
45
+ /**
46
+ * What is wrong with an application's translations, in agentspecs'
47
+ * sentences: its own language, and each translation's, a BCP 47 tag; no
48
+ * translation into its own language, nor into one twice; nothing translated
49
+ * that it does not say.
50
+ */
51
+ export declare function translationProblems(ui: AppInterfaceSpec): string[];
52
+ /** The words the application's interface says itself, in one language. */
53
+ export type InterfaceWords = {
54
+ /** The profile picker's name. */
55
+ profile: string;
56
+ /** Beside the profile a conversation started with: it is kept. */
57
+ profileKept: string;
58
+ /** The paper clip's. */
59
+ attach: string;
60
+ };
61
+ /**
62
+ * The words the application's own interface says — the profile picker, the
63
+ * paper clip — in the languages it is said in. Another language reads
64
+ * English.
65
+ */
66
+ export declare const INTERFACE_WORDS: Record<string, InterfaceWords>;
67
+ /** The interface's own words in a language: its own, else its language's, else English. */
68
+ export declare function interfaceWords(language: string): InterfaceWords;
@@ -0,0 +1,275 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { DEFAULT_LANGUAGE } from './appspec';
6
+ /**
7
+ * A language as BCP 47 tags it: two or three letters, then optionally a
8
+ * script, a region and variants — `fr`, `pt-BR`, `zh-Hant-TW`.
9
+ */
10
+ export const LANGUAGE_TAG = /^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?(?:-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*$/;
11
+ /**
12
+ * The first language a person prefers that is available, or null. Tags are
13
+ * compared without case; a preferred tag also takes one of its language
14
+ * alone, then one of the same language (`fr-CA` takes `fr`, then `fr-FR`).
15
+ */
16
+ export function pickLanguage(available, preferred) {
17
+ const byTag = new Map(available.map(tag => [tag.toLowerCase(), tag]));
18
+ for (const wanted of preferred) {
19
+ const tag = wanted.trim().toLowerCase();
20
+ if (!tag) {
21
+ continue;
22
+ }
23
+ const exact = byTag.get(tag);
24
+ if (exact) {
25
+ return exact;
26
+ }
27
+ const language = tag.split('-')[0];
28
+ const alone = byTag.get(language);
29
+ if (alone) {
30
+ return alone;
31
+ }
32
+ for (const [candidate, original] of byTag) {
33
+ if (candidate.split('-')[0] === language) {
34
+ return original;
35
+ }
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ /** The languages the browser says the person prefers, in their order; none outside one. */
41
+ export function preferredLanguages() {
42
+ if (typeof navigator === 'undefined') {
43
+ return [];
44
+ }
45
+ const languages = navigator.languages?.length
46
+ ? [...navigator.languages]
47
+ : [navigator.language];
48
+ return languages.filter(Boolean);
49
+ }
50
+ /** The language an application is read in by a person who prefers these. */
51
+ export function appLanguage(app, preferred) {
52
+ const own = app.interface.language ?? DEFAULT_LANGUAGE;
53
+ return (pickLanguage([own, ...Object.keys(app.interface.translations ?? {})], preferred) ?? own);
54
+ }
55
+ function translatedStarter(starter, words) {
56
+ const said = words.starters[starter.label];
57
+ const category = starter.category
58
+ ? words.categories[starter.category] || starter.category
59
+ : undefined;
60
+ return {
61
+ label: said?.label || starter.label,
62
+ message: said?.message || starter.message,
63
+ ...(category ? { category } : {}),
64
+ };
65
+ }
66
+ /** The settings' uiSchema, a translated field's values named in the language. */
67
+ function namedOptions(ui, words, settings) {
68
+ const named = Object.entries(words.settings).filter(([, field]) => Object.keys(field.options).length > 0);
69
+ if (named.length === 0 || !settings) {
70
+ return ui;
71
+ }
72
+ const drawn = JSON.parse(JSON.stringify(ui ?? {}));
73
+ for (const [name, field] of named) {
74
+ const schema = (settings.properties?.[name] ?? {});
75
+ const items = (schema.items ?? {});
76
+ const values = (schema.enum ?? items.enum ?? []).map(String);
77
+ drawn[name] = {
78
+ ...(drawn[name] ?? {}),
79
+ 'ui:enumNames': values.map(value => field.options[value] ?? value),
80
+ };
81
+ }
82
+ return drawn;
83
+ }
84
+ /**
85
+ * The application as a person who prefers these languages reads it: its
86
+ * name, description and interface in the first of them it is translated
87
+ * into, else itself. What it does — its agent, its rules, its ids, its
88
+ * values, its prompts — is the same.
89
+ */
90
+ export function translatedAppspec(app, preferred) {
91
+ const tag = appLanguage(app, preferred);
92
+ const words = app.interface.translations?.[tag];
93
+ if (!words) {
94
+ return app;
95
+ }
96
+ const ui = app.interface;
97
+ let settings = ui.settings;
98
+ if (settings && Object.keys(words.settings).length > 0) {
99
+ settings = JSON.parse(JSON.stringify(settings));
100
+ for (const [name, field] of Object.entries(words.settings)) {
101
+ const target = settings.properties?.[name];
102
+ if (!target) {
103
+ continue;
104
+ }
105
+ if (field.title) {
106
+ target.title = field.title;
107
+ }
108
+ if (field.description) {
109
+ target.description = field.description;
110
+ }
111
+ }
112
+ }
113
+ const settingsUi = namedOptions(ui.settingsUi, words, settings);
114
+ return {
115
+ ...app,
116
+ name: words.name || app.name,
117
+ description: words.description || app.description,
118
+ interface: {
119
+ ...ui,
120
+ welcome: words.welcome || ui.welcome,
121
+ starters: ui.starters.map(starter => translatedStarter(starter, words)),
122
+ commands: ui.commands.map(command => ({
123
+ ...command,
124
+ description: words.commands[command.name] || command.description,
125
+ })),
126
+ modes: ui.modes.map(mode => {
127
+ const said = words.modes[mode.id];
128
+ return said
129
+ ? {
130
+ ...mode,
131
+ label: said.label || mode.label,
132
+ options: mode.options.map(option => ({
133
+ ...option,
134
+ label: said.options[option.id]?.label || option.label,
135
+ ...(said.options[option.id]?.description
136
+ ? { description: said.options[option.id].description }
137
+ : {}),
138
+ })),
139
+ }
140
+ : mode;
141
+ }),
142
+ profiles: (ui.profiles ?? []).map(profile => ({
143
+ ...profile,
144
+ label: words.profiles[profile.id]?.label || profile.label,
145
+ description: words.profiles[profile.id]?.description || profile.description,
146
+ starters: profile.starters.map(starter => translatedStarter(starter, words)),
147
+ })),
148
+ ...(settings ? { settings } : {}),
149
+ ...(settingsUi ? { settingsUi } : {}),
150
+ language: tag,
151
+ translations: {},
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * What is wrong with an application's translations, in agentspecs'
157
+ * sentences: its own language, and each translation's, a BCP 47 tag; no
158
+ * translation into its own language, nor into one twice; nothing translated
159
+ * that it does not say.
160
+ */
161
+ export function translationProblems(ui) {
162
+ const own = ui.language ?? DEFAULT_LANGUAGE;
163
+ const problems = [];
164
+ if (!LANGUAGE_TAG.test(own)) {
165
+ problems.push(`“${own}” is no language as BCP 47 tags it: \`en\`, \`fr\`, \`pt-BR\`.`);
166
+ }
167
+ const profiles = ui.profiles ?? [];
168
+ const starters = new Set([
169
+ ...ui.starters.map(starter => starter.label),
170
+ ...profiles.flatMap(profile => profile.starters.map(item => item.label)),
171
+ ]);
172
+ const categories = new Set([...ui.starters, ...profiles.flatMap(profile => profile.starters)]
173
+ .map(starter => starter.category)
174
+ .filter(Boolean));
175
+ const fields = (ui.settings?.properties ?? {});
176
+ const seen = new Map();
177
+ for (const [tag, words] of Object.entries(ui.translations ?? {})) {
178
+ if (!LANGUAGE_TAG.test(tag)) {
179
+ problems.push(`The translation “${tag}” is of no language as BCP 47 tags it: \`fr\`, \`pt-BR\`.`);
180
+ continue;
181
+ }
182
+ if (tag.toLowerCase() === own.toLowerCase()) {
183
+ problems.push(`Its own words are in “${own}”: no translation into it.`);
184
+ continue;
185
+ }
186
+ const first = seen.get(tag.toLowerCase());
187
+ if (first) {
188
+ problems.push(`“${first}” and “${tag}” are the same language.`);
189
+ continue;
190
+ }
191
+ seen.set(tag.toLowerCase(), tag);
192
+ const unknown = [
193
+ ...Object.keys(words.starters)
194
+ .filter(label => !starters.has(label))
195
+ .map(label => `the starter “${label}”`),
196
+ ...Object.keys(words.categories)
197
+ .filter(name => !categories.has(name))
198
+ .map(name => `the category “${name}”`),
199
+ ...Object.keys(words.settings)
200
+ .filter(name => !(name in fields))
201
+ .map(name => `the setting “${name}”`),
202
+ ...Object.keys(words.commands)
203
+ .filter(name => !ui.commands.some(command => command.name === name))
204
+ .map(name => `the command “${name}”`),
205
+ ...Object.entries(words.modes).flatMap(([id, mode]) => {
206
+ const known = ui.modes.find(item => item.id === id);
207
+ if (!known) {
208
+ return [`the mode “${id}”`];
209
+ }
210
+ return Object.keys(mode.options)
211
+ .filter(option => !known.options.some(item => item.id === option))
212
+ .map(option => `the option “${option}” of “${id}”`);
213
+ }),
214
+ ...Object.keys(words.profiles)
215
+ .filter(id => !profiles.some(profile => profile.id === id))
216
+ .map(id => `the profile “${id}”`),
217
+ ...Object.entries(words.settings).flatMap(([name, field]) => {
218
+ const schema = (fields[name] ?? {});
219
+ const items = (schema.items ?? {});
220
+ const values = (schema.enum ?? items.enum ?? []).map(String);
221
+ return name in fields
222
+ ? Object.keys(field.options)
223
+ .filter(value => !values.includes(value))
224
+ .map(value => `the value “${value}” of “${name}”`)
225
+ : [];
226
+ }),
227
+ ];
228
+ if (unknown.length > 0) {
229
+ problems.push(`The translation “${tag}” translates what the application does not say: ${unknown.join(', ')}.`);
230
+ }
231
+ }
232
+ return problems;
233
+ }
234
+ /**
235
+ * The words the application's own interface says — the profile picker, the
236
+ * paper clip — in the languages it is said in. Another language reads
237
+ * English.
238
+ */
239
+ export const INTERFACE_WORDS = {
240
+ en: {
241
+ profile: 'Profile',
242
+ profileKept: 'kept for this conversation',
243
+ attach: 'Attach a file',
244
+ },
245
+ fr: {
246
+ profile: 'Profil',
247
+ profileKept: 'gardé pour cette conversation',
248
+ attach: 'Joindre un fichier',
249
+ },
250
+ es: {
251
+ profile: 'Perfil',
252
+ profileKept: 'se mantiene en esta conversación',
253
+ attach: 'Adjuntar un archivo',
254
+ },
255
+ de: {
256
+ profile: 'Profil',
257
+ profileKept: 'für dieses Gespräch beibehalten',
258
+ attach: 'Datei anhängen',
259
+ },
260
+ it: {
261
+ profile: 'Profilo',
262
+ profileKept: 'mantenuto per questa conversazione',
263
+ attach: 'Allega un file',
264
+ },
265
+ pt: {
266
+ profile: 'Perfil',
267
+ profileKept: 'mantido nesta conversa',
268
+ attach: 'Anexar um arquivo',
269
+ },
270
+ };
271
+ /** The interface's own words in a language: its own, else its language's, else English. */
272
+ export function interfaceWords(language) {
273
+ const tag = pickLanguage(Object.keys(INTERFACE_WORDS), [language]);
274
+ return INTERFACE_WORDS[tag ?? 'en'];
275
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The nine inputs a setting is drawn with (LOOP P-20): each a JSON Schema
3
+ * field and, where the field's own widget is not the one wanted, the widget a
4
+ * form's uiSchema names for it (`ui:widget`) — what `@datalayer/primer-rjsf`
5
+ * draws. agentspecs' `SETTING_INPUTS`, `FORM_WIDGETS` and
6
+ * `form_ui_problems`, said again for the page, in its sentences.
7
+ *
8
+ * Pure: no React, no network.
9
+ *
10
+ * @module loop/apps/settingsInputs
11
+ */
12
+ /** The nine inputs: the field each is, and the widget it is drawn with. */
13
+ export declare const SETTING_INPUTS: Record<string, {
14
+ field: string;
15
+ widget: string;
16
+ }>;
17
+ /** The widgets a form's `ui` may name (`ui:widget`). */
18
+ export declare const FORM_WIDGETS: readonly ["select", "radio", "range", "updown", "switch", "checkbox", "text", "textarea", "date", "checkboxes", "tags"];
19
+ type Data = Record<string, unknown>;
20
+ /** Why a widget cannot draw a field, in words; null when it can. */
21
+ export declare function widgetRefusal(widget: string, field: Data): string | null;
22
+ /**
23
+ * What is wrong with how a form's fields are drawn, in agentspecs'
24
+ * `form_ui_problems` sentences: by field name, the `ui:` options of each —
25
+ * `ui:widget` one of {@link FORM_WIDGETS} that draws the field — and
26
+ * `ui:order` at its top.
27
+ */
28
+ export declare function formUiProblems(said: string, schema: Data, ui: unknown): string[];
29
+ export {};