@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
|
@@ -36,7 +36,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
36
36
|
* agent calls as `host_open_ticket`, each as the rule naming it decides; the
|
|
37
37
|
* element raises `message` when the application has answered and `action`
|
|
38
38
|
* when it called a function of the page, beside `decision` and
|
|
39
|
-
* `token-expired`.
|
|
39
|
+
* `token-expired`. Who its user is, when it matters (LOOP D-21): the page
|
|
40
|
+
* sets `user-token` (or `element.userToken`) to the token its server signed
|
|
41
|
+
* naming the user, sent with every run of an application that says
|
|
42
|
+
* `user: signed`.
|
|
43
|
+
*
|
|
44
|
+
* Window messages (LOOP P-25), on the host's own server: the element raises
|
|
45
|
+
* `window-message` (`detail.data`) when the application's code tells the page
|
|
46
|
+
* something outside the conversation (`session.send_window_message`), and
|
|
47
|
+
* `element.postWindowMessage(data)` hands the page's to its `@app.window`.
|
|
40
48
|
*
|
|
41
49
|
* On the host's server (`server`) with the visit's embed token, the
|
|
42
50
|
* session the application opens is kept in the page's `localStorage`, under
|
|
@@ -54,7 +62,8 @@ import { coreStore } from '@datalayer/core/lib/state/substates/CoreState';
|
|
|
54
62
|
import { parseAppspec } from '../apps/appspec';
|
|
55
63
|
import { readAppspecYaml } from '../apps/yaml';
|
|
56
64
|
import { AppEmbed, embedAssistantCharacter } from './AppEmbed';
|
|
57
|
-
import {
|
|
65
|
+
import { WINDOW_NOT_YET } from './embedSession';
|
|
66
|
+
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, languageOf, resumeOf, } from './embedConfig';
|
|
58
67
|
import { embedShadowCss } from './embedTheme';
|
|
59
68
|
/** What the framed run page and the element say to each other. */
|
|
60
69
|
export const EMBED_MESSAGES = {
|
|
@@ -116,6 +125,18 @@ export function frameSrcOf(origin, app, token = '') {
|
|
|
116
125
|
// and no access log.
|
|
117
126
|
return `${origin.replace(/\/+$/, '')}/public/apps/${encodeURIComponent(app)}/run?embed=1${token ? `#token=${encodeURIComponent(token)}` : ''}`;
|
|
118
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* A framed run page told the visitor's language (P-26): `language=` in its
|
|
130
|
+
* query, before the fragment that holds the token.
|
|
131
|
+
*/
|
|
132
|
+
export function frameInLanguage(src, language) {
|
|
133
|
+
if (!language) {
|
|
134
|
+
return src;
|
|
135
|
+
}
|
|
136
|
+
const at = src.indexOf('#');
|
|
137
|
+
const [page, fragment] = at < 0 ? [src, ''] : [src.slice(0, at), src.slice(at)];
|
|
138
|
+
return `${page}${page.includes('?') ? '&' : '?'}language=${encodeURIComponent(language)}${fragment}`;
|
|
139
|
+
}
|
|
119
140
|
/**
|
|
120
141
|
* An application on Datalayer, read with its embed token: what the space
|
|
121
142
|
* keeps of it (`model_s`, the stored definition), whose `spec` is the
|
|
@@ -193,10 +214,17 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
193
214
|
passed = {};
|
|
194
215
|
/** One object, its functions replaced in place: the host below holds it. */
|
|
195
216
|
offered = {};
|
|
217
|
+
/** How a window message reaches the application's session, once it can (P-25). */
|
|
218
|
+
windowPort = null;
|
|
196
219
|
/** What the page gives the application, read at each call (D-10). */
|
|
197
220
|
host = {
|
|
198
221
|
context: () => this.passed,
|
|
222
|
+
// Read as each run is sent (D-21): a renewed one goes with the next.
|
|
223
|
+
userToken: () => this.getAttribute('user-token') || undefined,
|
|
199
224
|
functions: this.offered,
|
|
225
|
+
onWindowPort: post => {
|
|
226
|
+
this.windowPort = post;
|
|
227
|
+
},
|
|
200
228
|
onEvent: event => this.dispatchEvent(new CustomEvent(event.type, {
|
|
201
229
|
detail: event.detail,
|
|
202
230
|
bubbles: true,
|
|
@@ -210,6 +238,18 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
210
238
|
set context(value) {
|
|
211
239
|
this.passed = { ...(value ?? {}) };
|
|
212
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Post a window message to the application (LOOP P-25): its code's
|
|
243
|
+
* `@app.window` reads it, and what it answers is raised as
|
|
244
|
+
* `window-message` events. Refused, in a sentence, before its
|
|
245
|
+
* conversation has started or off the host's own server.
|
|
246
|
+
*/
|
|
247
|
+
postWindowMessage(data) {
|
|
248
|
+
const post = this.windowPort;
|
|
249
|
+
return post
|
|
250
|
+
? post(data)
|
|
251
|
+
: Promise.reject(new Error(`datalayer-app: ${WINDOW_NOT_YET}`));
|
|
252
|
+
}
|
|
213
253
|
/** The page's functions the application may call, by name (D-10). */
|
|
214
254
|
get functions() {
|
|
215
255
|
return this.offered;
|
|
@@ -237,6 +277,22 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
237
277
|
this.removeAttribute('token');
|
|
238
278
|
}
|
|
239
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The token the host's server signed naming its user (LOOP D-21), the
|
|
282
|
+
* `user-token` attribute's: what an application that says `user: signed`
|
|
283
|
+
* opens its session with. Set it to hand a new one over.
|
|
284
|
+
*/
|
|
285
|
+
get userToken() {
|
|
286
|
+
return this.getAttribute('user-token') ?? '';
|
|
287
|
+
}
|
|
288
|
+
set userToken(value) {
|
|
289
|
+
if (value) {
|
|
290
|
+
this.setAttribute('user-token', String(value));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
this.removeAttribute('user-token');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
240
296
|
connectedCallback() {
|
|
241
297
|
window.addEventListener('message', this.onMessage);
|
|
242
298
|
void this.load();
|
|
@@ -249,6 +305,11 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
249
305
|
if (!this.isConnected || before === after) {
|
|
250
306
|
return;
|
|
251
307
|
}
|
|
308
|
+
// The user's signed token is read as each run is sent (D-21): nothing
|
|
309
|
+
// is drawn again for a new one.
|
|
310
|
+
if (name === 'user-token') {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
252
313
|
// A new token is handed to the framed page that is already there,
|
|
253
314
|
// which keeps what the person entered.
|
|
254
315
|
if (name === 'token' && this.frame?.contentWindow && after) {
|
|
@@ -256,7 +317,15 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
256
317
|
return;
|
|
257
318
|
}
|
|
258
319
|
// How it looks is drawn again; what it is, read again.
|
|
259
|
-
if ([
|
|
320
|
+
if ([
|
|
321
|
+
'mode',
|
|
322
|
+
'accent',
|
|
323
|
+
'theme',
|
|
324
|
+
'font',
|
|
325
|
+
'height',
|
|
326
|
+
'resume',
|
|
327
|
+
'language',
|
|
328
|
+
].includes(name)) {
|
|
260
329
|
this.draw();
|
|
261
330
|
return;
|
|
262
331
|
}
|
|
@@ -388,11 +457,19 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
388
457
|
return;
|
|
389
458
|
}
|
|
390
459
|
if (source.kind === 'frame') {
|
|
460
|
+
let framedLanguage;
|
|
461
|
+
try {
|
|
462
|
+
framedLanguage = languageOf(this.getAttribute('language'));
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
this.say(error.message);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
391
468
|
this.setAttribute('data-embed-mode', 'inline');
|
|
392
469
|
const shadow = this.skeleton();
|
|
393
470
|
const frame = document.createElement('iframe');
|
|
394
471
|
frame.className = 'datalayer-app-frame';
|
|
395
|
-
frame.src = source.src;
|
|
472
|
+
frame.src = frameInLanguage(source.src, framedLanguage);
|
|
396
473
|
frame.title = 'Datalayer app';
|
|
397
474
|
frame.style.height = `${inlineHeightOf(this.getAttribute('height'))}px`;
|
|
398
475
|
frame.setAttribute('allow', 'clipboard-write');
|
|
@@ -402,9 +479,11 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
402
479
|
}
|
|
403
480
|
let look;
|
|
404
481
|
let resume;
|
|
482
|
+
let language;
|
|
405
483
|
try {
|
|
406
484
|
look = embedLookOf(this.inputs(), source.app);
|
|
407
485
|
resume = resumeOf(this.getAttribute('resume'));
|
|
486
|
+
language = languageOf(this.getAttribute('language'));
|
|
408
487
|
}
|
|
409
488
|
catch (error) {
|
|
410
489
|
this.say(error.message);
|
|
@@ -431,7 +510,7 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
431
510
|
this.root.render(_jsx(StyleSheetManager, { target: shadow, children: _jsx(AppEmbed, { app: source.app, mode: look.mode, accent: look.accent, colorMode: look.colorMode, font: look.font, serverUrl: server, ...(server && this.token ? { embedToken: this.token } : {}), height: inlineHeightOf(this.getAttribute('height')), ownPortal: true, host: this.host,
|
|
432
511
|
// The visit's session kept in the page's storage, and picked
|
|
433
512
|
// up again after a reload, unless the host says not (D-13).
|
|
434
|
-
resume: resume }) }));
|
|
513
|
+
resume: resume, ...(language ? { language } : {}) }) }));
|
|
435
514
|
}
|
|
436
515
|
onMessage(event) {
|
|
437
516
|
if (!this.frame || event.source !== this.frame.contentWindow) {
|
|
@@ -34,6 +34,8 @@ export declare const EMBED_ATTRIBUTES: {
|
|
|
34
34
|
readonly api: "Datalayer’s API, where an application is read with its token; the platform’s by default.";
|
|
35
35
|
readonly height: "The height of an inline application, in pixels; 640 by default.";
|
|
36
36
|
readonly resume: "true or false: whether a visit’s conversation is picked up again after the page reloads, kept in the page’s storage; true by default.";
|
|
37
|
+
readonly language: "The language the visitor reads the application in, as BCP 47 tags it (fr, pt-BR): its translation into it, and the chat’s own words; the visitor’s browser’s by default.";
|
|
38
|
+
readonly 'user-token': "A token your server signed with the deployment’s secret, naming your user (HS256: sub, name, exp at most an hour away): what an application that says user: signed opens its session with.";
|
|
37
39
|
};
|
|
38
40
|
export type EmbedAttribute = keyof typeof EMBED_ATTRIBUTES;
|
|
39
41
|
/** Every attribute the element watches. */
|
|
@@ -103,6 +105,12 @@ export declare function embedLookOf(inputs: EmbedInputs, app?: Pick<AppSpec, 'in
|
|
|
103
105
|
* refused in a sentence.
|
|
104
106
|
*/
|
|
105
107
|
export declare function resumeOf(value: string | null | undefined): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The language a host says its visitor reads (LOOP P-26), checked: a BCP 47
|
|
110
|
+
* tag (`fr`, `pt-BR`), else refused in a sentence. Unsaid, none: the
|
|
111
|
+
* visitor's browser's.
|
|
112
|
+
*/
|
|
113
|
+
export declare function languageOf(value: string | null | undefined): string | undefined;
|
|
106
114
|
/** The height of an inline application, in pixels. */
|
|
107
115
|
export declare function inlineHeightOf(value: string | null | undefined): number;
|
|
108
116
|
/** What a snippet says beside the application's id. */
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { APP_ACCENTS, APP_EMBED_MODES } from '../apps/appspec';
|
|
6
|
+
import { LANGUAGE_TAG } from '../apps/language';
|
|
6
7
|
/** The element's name: the one a host writes. */
|
|
7
8
|
export const EMBED_TAG = 'datalayer-app';
|
|
8
9
|
/** Where the script is served on a Datalayer origin; the stylesheet beside it. */
|
|
@@ -25,6 +26,8 @@ export const EMBED_ATTRIBUTES = {
|
|
|
25
26
|
api: 'Datalayer’s API, where an application is read with its token; the platform’s by default.',
|
|
26
27
|
height: 'The height of an inline application, in pixels; 640 by default.',
|
|
27
28
|
resume: 'true or false: whether a visit’s conversation is picked up again after the page reloads, kept in the page’s storage; true by default.',
|
|
29
|
+
language: 'The language the visitor reads the application in, as BCP 47 tags it (fr, pt-BR): its translation into it, and the chat’s own words; the visitor’s browser’s by default.',
|
|
30
|
+
'user-token': 'A token your server signed with the deployment’s secret, naming your user (HS256: sub, name, exp at most an hour away): what an application that says user: signed opens its session with.',
|
|
28
31
|
};
|
|
29
32
|
/** Every attribute the element watches. */
|
|
30
33
|
export const EMBED_OBSERVED_ATTRIBUTES = Object.keys(EMBED_ATTRIBUTES);
|
|
@@ -120,6 +123,21 @@ export function embedLookOf(inputs, app) {
|
|
|
120
123
|
export function resumeOf(value) {
|
|
121
124
|
return (oneOf(value, ['true', 'false'], 'a value of resume') !== 'false');
|
|
122
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* The language a host says its visitor reads (LOOP P-26), checked: a BCP 47
|
|
128
|
+
* tag (`fr`, `pt-BR`), else refused in a sentence. Unsaid, none: the
|
|
129
|
+
* visitor's browser's.
|
|
130
|
+
*/
|
|
131
|
+
export function languageOf(value) {
|
|
132
|
+
const text = value?.trim();
|
|
133
|
+
if (!text) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
if (!LANGUAGE_TAG.test(text)) {
|
|
137
|
+
throw new EmbedAttributeError(`datalayer-app: "${text}" is not a language as BCP 47 tags it, such as fr or pt-BR.`);
|
|
138
|
+
}
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
123
141
|
/** The height of an inline application, in pixels. */
|
|
124
142
|
export function inlineHeightOf(value) {
|
|
125
143
|
const height = Number.parseInt(value ?? '', 10);
|
|
@@ -75,5 +75,19 @@ export declare function reattachSession({ serverUrl, uid, token, fetcher, }: {
|
|
|
75
75
|
token: string;
|
|
76
76
|
fetcher?: typeof fetch;
|
|
77
77
|
}): Promise<Reattached>;
|
|
78
|
+
/** Said when a window message is posted before the conversation has started. */
|
|
79
|
+
export declare const WINDOW_NOT_YET = "The application takes a window message once its conversation has started, on its own server (the \"server\" attribute).";
|
|
80
|
+
/**
|
|
81
|
+
* Post a window message to a session (LOOP P-25): its code's `@app.window`
|
|
82
|
+
* runs on it. Returns what the code told the page back; refused — the
|
|
83
|
+
* runtime's sentence — when the session or its code takes none.
|
|
84
|
+
*/
|
|
85
|
+
export declare function postWindowMessage({ serverUrl, uid, token, data, fetcher, }: {
|
|
86
|
+
serverUrl: string;
|
|
87
|
+
uid: string;
|
|
88
|
+
token?: string;
|
|
89
|
+
data: unknown;
|
|
90
|
+
fetcher?: typeof fetch;
|
|
91
|
+
}): Promise<unknown[]>;
|
|
78
92
|
/** What the page says, once, when the conversation it kept is gone. */
|
|
79
93
|
export declare const sessionGoneSentence: (name: string) => string;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
import { windowTurnOf } from '../../chat/base/loopWindow';
|
|
5
6
|
/** Where a visit's session is kept in the host's storage: `<prefix>:<app>:<visit>`. */
|
|
6
7
|
export const EMBED_SESSION_KEY_PREFIX = 'datalayer-app:session';
|
|
7
8
|
/**
|
|
@@ -132,5 +133,38 @@ export async function reattachSession({ serverUrl, uid, token, fetcher = fetch,
|
|
|
132
133
|
return { kind: 'gone', uid };
|
|
133
134
|
}
|
|
134
135
|
}
|
|
136
|
+
/** Said when a window message is posted before the conversation has started. */
|
|
137
|
+
export const WINDOW_NOT_YET = 'The application takes a window message once its conversation has started, on its own server (the "server" attribute).';
|
|
138
|
+
/**
|
|
139
|
+
* Post a window message to a session (LOOP P-25): its code's `@app.window`
|
|
140
|
+
* runs on it. Returns what the code told the page back; refused — the
|
|
141
|
+
* runtime's sentence — when the session or its code takes none.
|
|
142
|
+
*/
|
|
143
|
+
export async function postWindowMessage({ serverUrl, uid, token, data, fetcher = fetch, }) {
|
|
144
|
+
const response = await fetcher(`${serverUrl.replace(/\/+$/, '')}/api/v1/apps/sessions/${encodeURIComponent(uid)}/window`, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: {
|
|
147
|
+
'content-type': 'application/json',
|
|
148
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify({ data }),
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
let detail = '';
|
|
154
|
+
try {
|
|
155
|
+
const body = (await response.json());
|
|
156
|
+
detail = typeof body.detail === 'string' ? body.detail : '';
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// No sentence: the status says it.
|
|
160
|
+
}
|
|
161
|
+
throw new Error(detail || `The window message was refused (${response.status}).`);
|
|
162
|
+
}
|
|
163
|
+
const turn = windowTurnOf(await response.text());
|
|
164
|
+
if (turn.error) {
|
|
165
|
+
throw new Error(turn.error);
|
|
166
|
+
}
|
|
167
|
+
return turn.said;
|
|
168
|
+
}
|
|
135
169
|
/** What the page says, once, when the conversation it kept is gone. */
|
|
136
170
|
export const sessionGoneSentence = (name) => `Your earlier conversation with ${name} has ended, so this is a new one.`;
|
|
@@ -6,9 +6,26 @@
|
|
|
6
6
|
* its Appspec names (`deployment.embedded.host.context`).
|
|
7
7
|
*
|
|
8
8
|
* Out: what the application does, as events — `message` when it has said
|
|
9
|
-
* something, `action` when it called a function of the page
|
|
9
|
+
* something, `action` when it called a function of the page,
|
|
10
|
+
* `window-message` when its code told the page something outside the
|
|
11
|
+
* conversation (LOOP P-25, `session.send_window_message`) — beside the
|
|
10
12
|
* `decision` and `token-expired` the element raises already.
|
|
11
13
|
*
|
|
14
|
+
* Window messages, in (P-25): the page posts one with
|
|
15
|
+
* `element.postWindowMessage(data)`; its code's `@app.window` reads it, on
|
|
16
|
+
* the host's own server (`server`), and what it answers comes back as
|
|
17
|
+
* `window-message` events.
|
|
18
|
+
*
|
|
19
|
+
* Who its user is (LOOP D-21): what the page says as `user` is a claim.
|
|
20
|
+
* An application whose Appspec says `deployment.embedded.host.user: signed`
|
|
21
|
+
* takes only a token the host's **server** signed with the deployment's
|
|
22
|
+
* secret (HS256: `sub`, `name`, `exp` at most an hour away), which the page
|
|
23
|
+
* hands over as `userToken` (the element's `user-token`). It goes with every
|
|
24
|
+
* run as `forwardedProps.loop.user_token` (`hostUserRunProps`); the runtime
|
|
25
|
+
* verifies it where the session opens, refuses the session without it, and
|
|
26
|
+
* answers `host_context`'s `user` with the user it names. The secret never
|
|
27
|
+
* reaches the page.
|
|
28
|
+
*
|
|
12
29
|
* Offered: the functions of the page its Appspec names
|
|
13
30
|
* (`deployment.embedded.host.functions`), each called by its agent as the
|
|
14
31
|
* tool `host_<name>`. Every one of these tools is decided in the page by
|
|
@@ -23,8 +40,9 @@
|
|
|
23
40
|
*/
|
|
24
41
|
import type { AppSpec } from '../../types/agentspecs';
|
|
25
42
|
import type { FrontendToolDefinition } from '../../types/tools';
|
|
26
|
-
import {
|
|
27
|
-
|
|
43
|
+
import type { RunPropsContribution } from '../core';
|
|
44
|
+
import { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser } from '../apps/hostTools';
|
|
45
|
+
export { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser };
|
|
28
46
|
/** What the application tells the page it sits in. */
|
|
29
47
|
export type HostEvent = {
|
|
30
48
|
type: 'message';
|
|
@@ -42,13 +60,27 @@ export type HostEvent = {
|
|
|
42
60
|
result?: unknown;
|
|
43
61
|
error?: string;
|
|
44
62
|
};
|
|
63
|
+
} | {
|
|
64
|
+
type: 'window-message';
|
|
65
|
+
/** What its code told the page (LOOP P-25): anything JSON writes. */
|
|
66
|
+
detail: {
|
|
67
|
+
data: unknown;
|
|
68
|
+
};
|
|
45
69
|
};
|
|
70
|
+
/** Posts a window message to the application's session (LOOP P-25). */
|
|
71
|
+
export type WindowPost = (data: unknown) => Promise<void>;
|
|
46
72
|
/** A function of the page, as the page gives it. */
|
|
47
73
|
export type HostFunction = (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
48
74
|
/** What the page gives an embedded application (LOOP D-10). */
|
|
49
75
|
export type AppEmbedHost = {
|
|
50
76
|
/** What the page passes now: its visitor (`user`), itself (`page`), values of its own. */
|
|
51
77
|
context?: () => Record<string, unknown>;
|
|
78
|
+
/**
|
|
79
|
+
* The token the host's server signed naming its user (LOOP D-21), read as
|
|
80
|
+
* each run is sent: what an application that says `user: signed` opens its
|
|
81
|
+
* session with. Never the deployment's secret.
|
|
82
|
+
*/
|
|
83
|
+
userToken?: () => string | undefined;
|
|
52
84
|
/** The page's functions, by the names the Appspec gives them. */
|
|
53
85
|
functions?: Record<string, HostFunction>;
|
|
54
86
|
/** Told what the application does: `message`, `action`. */
|
|
@@ -58,6 +90,12 @@ export type AppEmbedHost = {
|
|
|
58
90
|
* may be made; the browser's `confirm` when the page gives none.
|
|
59
91
|
*/
|
|
60
92
|
ask?: (question: HostQuestion) => boolean | Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Handed how a window message is posted to the application's session once
|
|
95
|
+
* the embed can (LOOP P-25) — on the host's own server, its conversation
|
|
96
|
+
* started — and `null` when it no longer can.
|
|
97
|
+
*/
|
|
98
|
+
onWindowPort?: (post: WindowPost | null) => void;
|
|
61
99
|
};
|
|
62
100
|
/** What the person is asked before a call of the page is made. */
|
|
63
101
|
export type HostQuestion = {
|
|
@@ -77,3 +115,12 @@ export declare const hostNotAllowed: (tool: string) => string;
|
|
|
77
115
|
* `host` is read at each call, so that what the page passes is the newest.
|
|
78
116
|
*/
|
|
79
117
|
export declare function hostFrontendTools(app: Pick<AppSpec, 'connections' | 'rules' | 'deployment'>, host: () => AppEmbedHost): FrontendToolDefinition[];
|
|
118
|
+
/**
|
|
119
|
+
* What goes with every run for the host's user (LOOP D-21): the token the
|
|
120
|
+
* host's server signed, as `forwardedProps.loop.user_token`, for an
|
|
121
|
+
* application that says `user: signed` — read at each run, so that the
|
|
122
|
+
* newest is sent; nothing when the page gives none, and the runtime refuses
|
|
123
|
+
* the session in a sentence. `undefined` for an application that takes what
|
|
124
|
+
* the page says.
|
|
125
|
+
*/
|
|
126
|
+
export declare function hostUserRunProps(app: Pick<AppSpec, 'deployment'>, host: () => AppEmbedHost): RunPropsContribution | undefined;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { behaviourFor } from '../apps/rules';
|
|
6
|
-
import { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, } from '../apps/hostTools';
|
|
7
|
-
export { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf };
|
|
6
|
+
import { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser, } from '../apps/hostTools';
|
|
7
|
+
export { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser };
|
|
8
8
|
/** The sentence a call is refused with when no rule names its tool. */
|
|
9
9
|
export const hostRefused = (tool) => `No rule of this application names ${tool}: it is left to the person, and the page was not called.`;
|
|
10
10
|
/** The sentence a call is refused with when the person did not allow it. */
|
|
@@ -102,3 +102,23 @@ export function hostFrontendTools(app, host) {
|
|
|
102
102
|
}
|
|
103
103
|
return tools;
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* What goes with every run for the host's user (LOOP D-21): the token the
|
|
107
|
+
* host's server signed, as `forwardedProps.loop.user_token`, for an
|
|
108
|
+
* application that says `user: signed` — read at each run, so that the
|
|
109
|
+
* newest is sent; nothing when the page gives none, and the runtime refuses
|
|
110
|
+
* the session in a sentence. `undefined` for an application that takes what
|
|
111
|
+
* the page says.
|
|
112
|
+
*/
|
|
113
|
+
export function hostUserRunProps(app, host) {
|
|
114
|
+
if (!signedUser(app.deployment.embedded?.host)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
id: 'host-user',
|
|
119
|
+
props: () => {
|
|
120
|
+
const token = host().userToken?.()?.trim();
|
|
121
|
+
return token ? { loop: { user_token: token } } : {};
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
package/lib/loop/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export { AgentA2uiPlugin, AGENT_A2UI_PLUGIN_NAME } from './plugins/agent-a2ui';
|
|
|
51
51
|
export { AgentA2uiJupyterOutputPlugin, AGENT_A2UI_JUPYTER_OUTPUT_PLUGIN_NAME, } from './plugins/agent-a2ui-jupyter-output';
|
|
52
52
|
export { defineA2uiScenePlugin, type A2uiSceneOptions, } from './plugins/a2ui-scene';
|
|
53
53
|
export { APP_KIND_PATHS, APP_PAGE_PLUGIN_NAME, APP_PAGE_SURFACE, AppPage, appKindPaths, appPageAction, appPageData, defineAppPagePlugin, hasAppPage, surfaceUnshown, type AppKindPaths, type AppPageAction, type AppPageKind, type AppPagePath, } from './plugins/app-page';
|
|
54
|
+
export { appComponentsPluginName, defineAppComponentsPlugin, hasCustomComponents, } from './plugins/app-components';
|
|
54
55
|
export { APP_RULES_PLUGIN_NAME, APP_RULES_WORDS, AppRulesCard, defineAppRulesPlugin, } from './plugins/app-rules';
|
|
55
56
|
export { APP_ACTIVITY_PLUGIN_NAME, APP_ACTIVITY_WORDS, AppActivity, defineAppActivityPlugin, } from './plugins/app-activity';
|
|
56
57
|
export { APP_COMPUTER_PLUGIN_NAME, AppComputer, defineAppComputerPlugin, } from './plugins/app-computer';
|
|
@@ -69,10 +70,10 @@ export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
|
|
|
69
70
|
export { LoopEmbed, type LoopEmbedProps } from './embed/LoopEmbed';
|
|
70
71
|
export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, type EmbedSession, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
71
72
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
72
|
-
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
73
|
+
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
73
74
|
export { defineDatalayerAppElement, type DatalayerAppElementOptions, } from './embed/element';
|
|
74
|
-
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
75
|
+
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
75
76
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
76
77
|
export { loopPlugins, type LoopPresetOptions } from './presets';
|
|
77
78
|
export { DocumentExtension, NotebookExtension } from './extensions';
|
|
78
|
-
export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, type BehaviourOptions, type ToolArguments, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, isOrganizationFrame, ORGANIZATION_FRAME_PREFIX, type AppCheck, type CheckContext, defineAppPlugin, type AppRendererProps, type AppInstance, appLayoutOptions, readAppspecYaml, writeAppspecYaml, type ParsedAppspec, } from './apps';
|
|
79
|
+
export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, type BehaviourOptions, type ToolArguments, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, isOrganizationFrame, ORGANIZATION_FRAME_PREFIX, type AppCheck, type CheckContext, defineAppPlugin, type AppRendererProps, type AppInstance, appLayoutOptions, readAppspecYaml, writeAppspecYaml, type ParsedAppspec, appComponents, customComponentEntry, customComponentOf, customComponentProblems, customComponentsProblems, pageOutputShows, } from './apps';
|
package/lib/loop/index.js
CHANGED
|
@@ -64,6 +64,7 @@ export { AgentA2uiPlugin, AGENT_A2UI_PLUGIN_NAME } from './plugins/agent-a2ui';
|
|
|
64
64
|
export { AgentA2uiJupyterOutputPlugin, AGENT_A2UI_JUPYTER_OUTPUT_PLUGIN_NAME, } from './plugins/agent-a2ui-jupyter-output';
|
|
65
65
|
export { defineA2uiScenePlugin, } from './plugins/a2ui-scene';
|
|
66
66
|
export { APP_KIND_PATHS, APP_PAGE_PLUGIN_NAME, APP_PAGE_SURFACE, AppPage, appKindPaths, appPageAction, appPageData, defineAppPagePlugin, hasAppPage, surfaceUnshown, } from './plugins/app-page';
|
|
67
|
+
export { appComponentsPluginName, defineAppComponentsPlugin, hasCustomComponents, } from './plugins/app-components';
|
|
67
68
|
export { APP_RULES_PLUGIN_NAME, APP_RULES_WORDS, AppRulesCard, defineAppRulesPlugin, } from './plugins/app-rules';
|
|
68
69
|
export { APP_ACTIVITY_PLUGIN_NAME, APP_ACTIVITY_WORDS, AppActivity, defineAppActivityPlugin, } from './plugins/app-activity';
|
|
69
70
|
export { APP_COMPUTER_PLUGIN_NAME, AppComputer, defineAppComputerPlugin, } from './plugins/app-computer';
|
|
@@ -90,9 +91,9 @@ export { LoopEmbed } from './embed/LoopEmbed';
|
|
|
90
91
|
export { AppEmbed, AppFloating, embedAssistantCharacter, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
91
92
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
92
93
|
// The host page and an embedded application, talking (LOOP D-10).
|
|
93
|
-
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, } from './embed/hostBridge';
|
|
94
|
+
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, } from './embed/hostBridge';
|
|
94
95
|
export { defineDatalayerAppElement, } from './embed/element';
|
|
95
|
-
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, resumeOf, } from './embed/embedConfig';
|
|
96
|
+
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, } from './embed/embedConfig';
|
|
96
97
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
97
98
|
// The set a workspace normally has, so a host starts from a working Loop
|
|
98
99
|
// rather than assembling one — and never from the example.
|
|
@@ -102,4 +103,4 @@ export { loopPlugins } from './presets';
|
|
|
102
103
|
export { DocumentExtension, NotebookExtension } from './extensions';
|
|
103
104
|
// Applications: what one does when its agent calls a tool — the rules a
|
|
104
105
|
// person wrote, decided on what the tool does.
|
|
105
|
-
export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, isOrganizationFrame, ORGANIZATION_FRAME_PREFIX, defineAppPlugin, appLayoutOptions, readAppspecYaml, writeAppspecYaml, } from './apps';
|
|
106
|
+
export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, isOrganizationFrame, ORGANIZATION_FRAME_PREFIX, defineAppPlugin, appLayoutOptions, readAppspecYaml, writeAppspecYaml, appComponents, customComponentEntry, customComponentOf, customComponentProblems, customComponentsProblems, pageOutputShows, } from './apps';
|
|
@@ -28,7 +28,7 @@ type Surface = InlineSurfaceModel;
|
|
|
28
28
|
export declare const SURFACE_CATALOG_ID: string;
|
|
29
29
|
/** Values to publish into a surface, by path. */
|
|
30
30
|
export type InlineSurfaceData = Record<string, unknown>;
|
|
31
|
-
export declare function InlineSurface({ messages, onAction, validationError, data, catalog, }: {
|
|
31
|
+
export declare function InlineSurface({ messages, onAction, validationError, data, catalog, onSurface, }: {
|
|
32
32
|
messages: A2uiMessage[];
|
|
33
33
|
/** What it draws with, read once: Datalayer's catalog unless given. */
|
|
34
34
|
catalog?: Catalog<ReactComponentImplementation>;
|
|
@@ -41,5 +41,11 @@ export declare function InlineSurface({ messages, onAction, validationError, dat
|
|
|
41
41
|
* changed is written: what a person typed elsewhere in the model stays.
|
|
42
42
|
*/
|
|
43
43
|
data?: InlineSurfaceData;
|
|
44
|
+
/**
|
|
45
|
+
* Told of each surface once it is created, for a host that reads what a
|
|
46
|
+
* person writes as they write it — a widget's page run again as an input
|
|
47
|
+
* changes (LOOP P-05) — rather than when a button is pressed.
|
|
48
|
+
*/
|
|
49
|
+
onSurface?: (surface: Surface) => void;
|
|
44
50
|
}): JSX.Element | null;
|
|
45
51
|
export default InlineSurface;
|
|
@@ -11,7 +11,7 @@ const INHERIT_THEME = {
|
|
|
11
11
|
['--a2ui-color-primary']: 'var(--fgColor-accent)',
|
|
12
12
|
['--a2ui-color-outline']: 'var(--borderColor-default)',
|
|
13
13
|
};
|
|
14
|
-
export function InlineSurface({ messages, onAction, validationError, data, catalog = datalayerCatalog, }) {
|
|
14
|
+
export function InlineSurface({ messages, onAction, validationError, data, catalog = datalayerCatalog, onSurface, }) {
|
|
15
15
|
// Reached through a ref: the processor is built once, and the handler it
|
|
16
16
|
// was built with must not go stale when the host's does not.
|
|
17
17
|
const onActionRef = useRef(onAction);
|
|
@@ -23,8 +23,13 @@ export function InlineSurface({ messages, onAction, validationError, data, catal
|
|
|
23
23
|
const processorRef = useRef(undefined);
|
|
24
24
|
processorRef.current = processor;
|
|
25
25
|
const [surfaces, setSurfaces] = useState([]);
|
|
26
|
+
const onSurfaceRef = useRef(onSurface);
|
|
27
|
+
onSurfaceRef.current = onSurface;
|
|
26
28
|
useEffect(() => {
|
|
27
|
-
const created = processor.onSurfaceCreated(surface =>
|
|
29
|
+
const created = processor.onSurfaceCreated(surface => {
|
|
30
|
+
setSurfaces(previous => [...previous, surface]);
|
|
31
|
+
onSurfaceRef.current?.(surface);
|
|
32
|
+
});
|
|
28
33
|
const deleted = processor.onSurfaceDeleted(id => setSurfaces(previous => previous.filter(surface => surface.id !== id)));
|
|
29
34
|
return () => {
|
|
30
35
|
created.unsubscribe();
|
|
@@ -4,6 +4,7 @@ import { Spinner, Text } from '@primer/react';
|
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { encodeFormSubmission } from '../../../chat/messages/formSubmission';
|
|
6
6
|
import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
|
|
7
|
+
import { useOwnCatalog } from './ownComponents';
|
|
7
8
|
import { answerAction, readA2uiToolResult, validateA2uiSubmission, } from './toolResult';
|
|
8
9
|
export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
9
10
|
const parsed = useMemo(() => context.status === 'complete'
|
|
@@ -11,6 +12,8 @@ export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
|
11
12
|
: null, [context.status, context.result]);
|
|
12
13
|
const title = parsed?.title ?? 'the form';
|
|
13
14
|
const [validationError, setValidationError] = useState(null);
|
|
15
|
+
// With the components the application's developer wrote (LOOP P-17).
|
|
16
|
+
const catalog = useOwnCatalog();
|
|
14
17
|
// Told once per result, when it is there to be told about.
|
|
15
18
|
useEffect(() => {
|
|
16
19
|
if (parsed?.messages && parsed.messages.length > 0) {
|
|
@@ -65,6 +68,6 @@ export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
|
65
68
|
gap: 2,
|
|
66
69
|
}, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Rendering A2UI surface..." })] }));
|
|
67
70
|
}
|
|
68
|
-
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: title }), _jsx(InlineSurface, { messages: parsed.messages, onAction: handleAction, validationError: validationError })] }));
|
|
71
|
+
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: title }), _jsx(InlineSurface, { messages: parsed.messages, onAction: handleAction, validationError: validationError, catalog: catalog })] }));
|
|
69
72
|
}
|
|
70
73
|
export default SurfaceToolResult;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReactComponentImplementation } from '@a2ui/react/v0_9';
|
|
2
|
+
import type { Catalog } from '@a2ui/web_core/v0_9';
|
|
3
|
+
/** The renderers the workspace's application contributes, in contribution order. */
|
|
4
|
+
export declare function useOwnComponents(): ReactComponentImplementation[];
|
|
5
|
+
/** Datalayer's catalog with the workspace's application's own components. */
|
|
6
|
+
export declare function useOwnCatalog(): Catalog<ReactComponentImplementation>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The components the application of this workspace adds to the catalog it
|
|
7
|
+
* is drawn with (LOOP P-17, `loop.a2ui.component`): what its answers, its
|
|
8
|
+
* elements and its page draw beside Datalayer's catalog. None outside an
|
|
9
|
+
* application's workspace, or in one whose developer wrote none.
|
|
10
|
+
*
|
|
11
|
+
* @module loop/plugins/a2ui-surface/ownComponents
|
|
12
|
+
*/
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { useOptionalReactorPlatform } from '@datalayer/reactor/react';
|
|
15
|
+
import { catalogWithOwn } from '../../../components/a2ui';
|
|
16
|
+
import { LoopA2uiComponent } from '../../core/a2uiComponents';
|
|
17
|
+
/** The renderers the workspace's application contributes, in contribution order. */
|
|
18
|
+
export function useOwnComponents() {
|
|
19
|
+
const reactor = useOptionalReactorPlatform();
|
|
20
|
+
return useMemo(() => reactor
|
|
21
|
+
? reactor
|
|
22
|
+
.getContributions(LoopA2uiComponent)
|
|
23
|
+
.map(entry => entry.value.implementation)
|
|
24
|
+
: [], [reactor]);
|
|
25
|
+
}
|
|
26
|
+
/** Datalayer's catalog with the workspace's application's own components. */
|
|
27
|
+
export function useOwnCatalog() {
|
|
28
|
+
const own = useOwnComponents();
|
|
29
|
+
return useMemo(() => catalogWithOwn(own), [own]);
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `app-components` — the components an application's developer wrote (LOOP
|
|
3
|
+
* P-17), registered as that application's A2UI contributions: each a block
|
|
4
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI plugin's components
|
|
5
|
+
* are) and a renderer of the catalog its page, its elements and its answers
|
|
6
|
+
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
7
|
+
* workspace's preset only: the catalog grows for it, it does not open.
|
|
8
|
+
*
|
|
9
|
+
* Its components are reviewed before they get here — `checkAppspec` refuses
|
|
10
|
+
* an invalid declaration — and drawn in a sandboxed frame of no origin
|
|
11
|
+
* (`components/a2ui/custom`).
|
|
12
|
+
*
|
|
13
|
+
* @module loop/plugins/app-components
|
|
14
|
+
*/
|
|
15
|
+
import { type ReactorPlugin } from '@datalayer/reactor';
|
|
16
|
+
import type { AppSpec } from '../../../types/agentspecs';
|
|
17
|
+
/** The name of the plugin that contributes an application's own components. */
|
|
18
|
+
export declare const appComponentsPluginName: (appId: string) => string;
|
|
19
|
+
/** Whether an application's developer wrote components of its own. */
|
|
20
|
+
export declare const hasCustomComponents: (app: Pick<AppSpec, "interface">) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The plugin contributing the components an application's developer wrote:
|
|
23
|
+
* a block and a renderer for each. `fetcher` is how their modules are
|
|
24
|
+
* fetched; the page's `fetch` unless given (tests).
|
|
25
|
+
*/
|
|
26
|
+
export declare function defineAppComponentsPlugin(app: Pick<AppSpec, 'id' | 'name' | 'version' | 'interface'>, fetcher?: typeof fetch): ReactorPlugin<Record<string, never>, unknown, unknown>;
|