@datalayer/agent-runtimes 1.3.84 → 1.3.86
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 +14 -11
- 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/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 +117 -22
- package/lib/components/teams/A2ATeamGraph.js +217 -97
- 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 +4 -0
- package/lib/components/teams/index.js +4 -0
- package/lib/components/teams/sceneStage.d.ts +120 -0
- package/lib/components/teams/sceneStage.js +170 -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 +94 -22
- package/lib/components/teams/useA2ATeam.js +356 -140
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/examples/ScenesExample.d.ts +26 -0
- package/lib/examples/ScenesExample.js +241 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/exampleGroups.js +3 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/examples/main.js +4 -0
- package/lib/loop/apps/AppComposer.d.ts +2 -1
- package/lib/loop/apps/AppComposer.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +2 -1
- package/lib/loop/apps/AppRenderer.js +34 -25
- package/lib/loop/apps/appspec.d.ts +11 -1
- package/lib/loop/apps/appspec.js +115 -1
- package/lib/loop/apps/checks.d.ts +8 -0
- package/lib/loop/apps/checks.js +106 -4
- 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/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +1 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +11 -3
- package/lib/loop/embed/AppEmbed.js +42 -16
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +61 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/hostBridge.d.ts +28 -2
- 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.js +4 -1
- 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 +120 -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 +6 -4
- 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/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1384 -231
- package/lib/specs/appspecSchema.js +191 -1
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/mcpServers.js +8 -2
- package/lib/specs/scenes.d.ts +21 -0
- package/lib/specs/scenes.js +1191 -0
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/types/agentspecs.d.ts +70 -0
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/scenes.d.ts +207 -0
- package/lib/types/scenes.js +5 -0
- package/lib/types/teams.d.ts +13 -2
- package/package.json +4 -4
- package/scripts/codegen/generate_agents.py +1 -0
- package/scripts/codegen/generate_apps.py +4 -2
- package/scripts/codegen/generate_scenes.py +301 -0
- 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,10 @@ 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`.
|
|
40
43
|
*
|
|
41
44
|
* Window messages (LOOP P-25), on the host's own server: the element raises
|
|
42
45
|
* `window-message` (`detail.data`) when the application's code tells the page
|
|
@@ -60,7 +63,7 @@ import { parseAppspec } from '../apps/appspec';
|
|
|
60
63
|
import { readAppspecYaml } from '../apps/yaml';
|
|
61
64
|
import { AppEmbed, embedAssistantCharacter } from './AppEmbed';
|
|
62
65
|
import { WINDOW_NOT_YET } from './embedSession';
|
|
63
|
-
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, resumeOf, } from './embedConfig';
|
|
66
|
+
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, languageOf, resumeOf, } from './embedConfig';
|
|
64
67
|
import { embedShadowCss } from './embedTheme';
|
|
65
68
|
/** What the framed run page and the element say to each other. */
|
|
66
69
|
export const EMBED_MESSAGES = {
|
|
@@ -122,6 +125,18 @@ export function frameSrcOf(origin, app, token = '') {
|
|
|
122
125
|
// and no access log.
|
|
123
126
|
return `${origin.replace(/\/+$/, '')}/public/apps/${encodeURIComponent(app)}/run?embed=1${token ? `#token=${encodeURIComponent(token)}` : ''}`;
|
|
124
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
|
+
}
|
|
125
140
|
/**
|
|
126
141
|
* An application on Datalayer, read with its embed token: what the space
|
|
127
142
|
* keeps of it (`model_s`, the stored definition), whose `spec` is the
|
|
@@ -204,6 +219,8 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
204
219
|
/** What the page gives the application, read at each call (D-10). */
|
|
205
220
|
host = {
|
|
206
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,
|
|
207
224
|
functions: this.offered,
|
|
208
225
|
onWindowPort: post => {
|
|
209
226
|
this.windowPort = post;
|
|
@@ -260,6 +277,22 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
260
277
|
this.removeAttribute('token');
|
|
261
278
|
}
|
|
262
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
|
+
}
|
|
263
296
|
connectedCallback() {
|
|
264
297
|
window.addEventListener('message', this.onMessage);
|
|
265
298
|
void this.load();
|
|
@@ -272,6 +305,11 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
272
305
|
if (!this.isConnected || before === after) {
|
|
273
306
|
return;
|
|
274
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
|
+
}
|
|
275
313
|
// A new token is handed to the framed page that is already there,
|
|
276
314
|
// which keeps what the person entered.
|
|
277
315
|
if (name === 'token' && this.frame?.contentWindow && after) {
|
|
@@ -279,7 +317,15 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
279
317
|
return;
|
|
280
318
|
}
|
|
281
319
|
// How it looks is drawn again; what it is, read again.
|
|
282
|
-
if ([
|
|
320
|
+
if ([
|
|
321
|
+
'mode',
|
|
322
|
+
'accent',
|
|
323
|
+
'theme',
|
|
324
|
+
'font',
|
|
325
|
+
'height',
|
|
326
|
+
'resume',
|
|
327
|
+
'language',
|
|
328
|
+
].includes(name)) {
|
|
283
329
|
this.draw();
|
|
284
330
|
return;
|
|
285
331
|
}
|
|
@@ -411,11 +457,19 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
411
457
|
return;
|
|
412
458
|
}
|
|
413
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
|
+
}
|
|
414
468
|
this.setAttribute('data-embed-mode', 'inline');
|
|
415
469
|
const shadow = this.skeleton();
|
|
416
470
|
const frame = document.createElement('iframe');
|
|
417
471
|
frame.className = 'datalayer-app-frame';
|
|
418
|
-
frame.src = source.src;
|
|
472
|
+
frame.src = frameInLanguage(source.src, framedLanguage);
|
|
419
473
|
frame.title = 'Datalayer app';
|
|
420
474
|
frame.style.height = `${inlineHeightOf(this.getAttribute('height'))}px`;
|
|
421
475
|
frame.setAttribute('allow', 'clipboard-write');
|
|
@@ -425,9 +479,11 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
425
479
|
}
|
|
426
480
|
let look;
|
|
427
481
|
let resume;
|
|
482
|
+
let language;
|
|
428
483
|
try {
|
|
429
484
|
look = embedLookOf(this.inputs(), source.app);
|
|
430
485
|
resume = resumeOf(this.getAttribute('resume'));
|
|
486
|
+
language = languageOf(this.getAttribute('language'));
|
|
431
487
|
}
|
|
432
488
|
catch (error) {
|
|
433
489
|
this.say(error.message);
|
|
@@ -454,7 +510,7 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
454
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,
|
|
455
511
|
// The visit's session kept in the page's storage, and picked
|
|
456
512
|
// up again after a reload, unless the host says not (D-13).
|
|
457
|
-
resume: resume }) }));
|
|
513
|
+
resume: resume, ...(language ? { language } : {}) }) }));
|
|
458
514
|
}
|
|
459
515
|
onMessage(event) {
|
|
460
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);
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
* the host's own server (`server`), and what it answers comes back as
|
|
17
17
|
* `window-message` events.
|
|
18
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
|
+
*
|
|
19
29
|
* Offered: the functions of the page its Appspec names
|
|
20
30
|
* (`deployment.embedded.host.functions`), each called by its agent as the
|
|
21
31
|
* tool `host_<name>`. Every one of these tools is decided in the page by
|
|
@@ -30,8 +40,9 @@
|
|
|
30
40
|
*/
|
|
31
41
|
import type { AppSpec } from '../../types/agentspecs';
|
|
32
42
|
import type { FrontendToolDefinition } from '../../types/tools';
|
|
33
|
-
import {
|
|
34
|
-
|
|
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 };
|
|
35
46
|
/** What the application tells the page it sits in. */
|
|
36
47
|
export type HostEvent = {
|
|
37
48
|
type: 'message';
|
|
@@ -64,6 +75,12 @@ export type HostFunction = (args: Record<string, unknown>) => unknown | Promise<
|
|
|
64
75
|
export type AppEmbedHost = {
|
|
65
76
|
/** What the page passes now: its visitor (`user`), itself (`page`), values of its own. */
|
|
66
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;
|
|
67
84
|
/** The page's functions, by the names the Appspec gives them. */
|
|
68
85
|
functions?: Record<string, HostFunction>;
|
|
69
86
|
/** Told what the application does: `message`, `action`. */
|
|
@@ -98,3 +115,12 @@ export declare const hostNotAllowed: (tool: string) => string;
|
|
|
98
115
|
* `host` is read at each call, so that what the page passes is the newest.
|
|
99
116
|
*/
|
|
100
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>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `app-components` — the components an application's developer wrote (LOOP
|
|
7
|
+
* P-17), registered as that application's A2UI contributions: each a block
|
|
8
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI plugin's components
|
|
9
|
+
* are) and a renderer of the catalog its page, its elements and its answers
|
|
10
|
+
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
11
|
+
* workspace's preset only: the catalog grows for it, it does not open.
|
|
12
|
+
*
|
|
13
|
+
* Its components are reviewed before they get here — `checkAppspec` refuses
|
|
14
|
+
* an invalid declaration — and drawn in a sandboxed frame of no origin
|
|
15
|
+
* (`components/a2ui/custom`).
|
|
16
|
+
*
|
|
17
|
+
* @module loop/plugins/app-components
|
|
18
|
+
*/
|
|
19
|
+
import { contribution, definePlugin, } from '@datalayer/reactor';
|
|
20
|
+
import { customImplementation } from '../../../components/a2ui/custom';
|
|
21
|
+
import { customComponentEntry } from '../../apps/customComponents';
|
|
22
|
+
import { LoopA2uiComponent } from '../../core/a2uiComponents';
|
|
23
|
+
import { LoopCanvasBlock } from '../../core/canvasBlocks';
|
|
24
|
+
/** The name of the plugin that contributes an application's own components. */
|
|
25
|
+
export const appComponentsPluginName = (appId) => `@datalayer/loop-plugin-app-components-${appId}`;
|
|
26
|
+
/** Whether an application's developer wrote components of its own. */
|
|
27
|
+
export const hasCustomComponents = (app) => (app.interface.customComponents ?? []).length > 0;
|
|
28
|
+
/**
|
|
29
|
+
* The plugin contributing the components an application's developer wrote:
|
|
30
|
+
* a block and a renderer for each. `fetcher` is how their modules are
|
|
31
|
+
* fetched; the page's `fetch` unless given (tests).
|
|
32
|
+
*/
|
|
33
|
+
export function defineAppComponentsPlugin(app, fetcher) {
|
|
34
|
+
const own = app.interface.customComponents ?? [];
|
|
35
|
+
if (own.length === 0) {
|
|
36
|
+
throw new Error(`“${app.name}” has no component of its own to contribute.`);
|
|
37
|
+
}
|
|
38
|
+
return definePlugin({
|
|
39
|
+
name: appComponentsPluginName(app.id),
|
|
40
|
+
displayName: `${app.name}'s components`,
|
|
41
|
+
description: `The components ${app.name}'s developer wrote: ${own.map(component => component.name).join(', ')}.`,
|
|
42
|
+
octicon: 'apps',
|
|
43
|
+
contributes: own.flatMap(component => [
|
|
44
|
+
contribution(LoopCanvasBlock, {
|
|
45
|
+
id: component.name,
|
|
46
|
+
uiPlugin: `app:${app.id}`,
|
|
47
|
+
component: customComponentEntry(component, app.version),
|
|
48
|
+
}, { id: component.name }),
|
|
49
|
+
contribution(LoopA2uiComponent, {
|
|
50
|
+
id: component.name,
|
|
51
|
+
app: app.id,
|
|
52
|
+
implementation: customImplementation(component, app.version, fetcher),
|
|
53
|
+
}, { id: component.name }),
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -6,6 +6,7 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
6
6
|
import { closeLoopElement, onLoopElements, openLoopElements, } from '../../../chat/base/loopElement';
|
|
7
7
|
import { InlineSurface, SURFACE_CATALOG_ID, } from '../a2ui-surface/InlineSurface';
|
|
8
8
|
import { answerAction, readA2uiToolResult } from '../a2ui-surface/toolResult';
|
|
9
|
+
import { useOwnCatalog } from '../a2ui-surface/ownComponents';
|
|
9
10
|
/** How wide the side panel is, at most. */
|
|
10
11
|
export const APP_PANEL_WIDTH = 380;
|
|
11
12
|
/** What is open now: the panels in the order they opened, and the last page. */
|
|
@@ -18,6 +19,8 @@ export function elementsByPlace(open) {
|
|
|
18
19
|
}
|
|
19
20
|
function ElementBody({ element, send, }) {
|
|
20
21
|
const parsed = useMemo(() => readA2uiToolResult(element.shows, SURFACE_CATALOG_ID), [element.shows]);
|
|
22
|
+
// With the components its developer wrote (P-17).
|
|
23
|
+
const catalog = useOwnCatalog();
|
|
21
24
|
const onAction = (action) => {
|
|
22
25
|
// A button of what its code shows: its action goes to the application,
|
|
23
26
|
// as a button of an answer does (LOOP P-04).
|
|
@@ -30,7 +33,7 @@ function ElementBody({ element, send, }) {
|
|
|
30
33
|
}
|
|
31
34
|
return (_jsx(InlineSurface
|
|
32
35
|
// Drawn again whole when the code changes it.
|
|
33
|
-
, { messages: parsed.messages, onAction: onAction }, JSON.stringify(element.shows)));
|
|
36
|
+
, { messages: parsed.messages, onAction: onAction, catalog: catalog }, JSON.stringify(element.shows)));
|
|
34
37
|
}
|
|
35
38
|
function ElementHeader({ element }) {
|
|
36
39
|
return (_jsxs(Box, { sx: {
|
|
@@ -9,12 +9,21 @@
|
|
|
9
9
|
* palette (`loop.canvas.block`, R-01b) and nothing else, so that what the
|
|
10
10
|
* Canvas can place and what the page can draw are one list: a page that
|
|
11
11
|
* uses a block no enabled plugin contributes says so, in place of the page.
|
|
12
|
+
* The components its developer wrote (LOOP P-17) are among them, contributed
|
|
13
|
+
* by its own plugin (`app-components`) with their renderers.
|
|
14
|
+
*
|
|
15
|
+
* A widget's page written in its code (LOOP P-05) runs in a session of its
|
|
16
|
+
* own, beside the conversation: as an input changes, the page sends them all
|
|
17
|
+
* to the session's `page` action and shows what its code answered at
|
|
18
|
+
* `/outputs/<name>`, in place — no message is sent (`./pageRun`).
|
|
12
19
|
*
|
|
13
20
|
* @module loop/plugins/app-page/AppPage
|
|
14
21
|
*/
|
|
15
22
|
import type { JSX } from 'react';
|
|
16
23
|
import type { AppSpec } from '../../../types/agentspecs';
|
|
17
24
|
import { type LoopWorkspaceContext } from '../../core';
|
|
25
|
+
/** How long the page waits after an input changed before it runs (P-05). */
|
|
26
|
+
export declare const PAGE_RUN_DELAY_MS = 300;
|
|
18
27
|
/** The components a page's messages place, each once. */
|
|
19
28
|
export declare function componentsOnPage(messages: readonly unknown[]): string[];
|
|
20
29
|
/**
|