@datalayer/agent-runtimes 1.3.83 → 1.3.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +30 -11
  17. package/lib/chat/base/loopElement.d.ts +53 -0
  18. package/lib/chat/base/loopElement.js +78 -0
  19. package/lib/chat/base/loopWindow.d.ts +31 -0
  20. package/lib/chat/base/loopWindow.js +74 -0
  21. package/lib/chat/display/EmptyState.js +3 -1
  22. package/lib/chat/index.d.ts +2 -0
  23. package/lib/chat/index.js +3 -0
  24. package/lib/chat/messages/ChatMessages.js +3 -1
  25. package/lib/chat/messages/TurnFooter.js +3 -1
  26. package/lib/chat/prompt/InputPrompt.js +3 -1
  27. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  28. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  29. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  30. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  31. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  32. package/lib/chat/words.d.ts +102 -0
  33. package/lib/chat/words.js +460 -0
  34. package/lib/components/a2ui/custom/index.d.ts +39 -0
  35. package/lib/components/a2ui/custom/index.js +95 -0
  36. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  37. package/lib/components/a2ui/custom/sandbox.js +188 -0
  38. package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
  39. package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
  40. package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
  41. package/lib/components/a2ui/datalayer/Form.js +9 -2
  42. package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
  43. package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
  44. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/index.js +1 -1
  46. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  47. package/lib/components/a2ui/datalayer/schema.js +12 -1
  48. package/lib/components/a2ui/index.d.ts +1 -0
  49. package/lib/components/a2ui/index.js +1 -0
  50. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  51. package/lib/components/a2ui/visibleWhen.js +24 -3
  52. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  53. package/lib/components/inspector/AgentInspector.js +27 -215
  54. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  55. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  56. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  57. package/lib/components/inspector/a2aSpans.js +373 -0
  58. package/lib/components/inspector/agentSpans.d.ts +120 -0
  59. package/lib/components/inspector/agentSpans.js +253 -0
  60. package/lib/components/inspector/chatSpans.d.ts +36 -0
  61. package/lib/components/inspector/chatSpans.js +50 -0
  62. package/lib/components/inspector/classify.d.ts +11 -8
  63. package/lib/components/inspector/classify.js +23 -24
  64. package/lib/components/inspector/index.d.ts +7 -7
  65. package/lib/components/inspector/index.js +7 -7
  66. package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
  67. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  68. package/lib/components/teams/SceneTranscript.js +54 -0
  69. package/lib/components/teams/SceneView.d.ts +41 -0
  70. package/lib/components/teams/SceneView.js +28 -0
  71. package/lib/components/teams/index.d.ts +3 -0
  72. package/lib/components/teams/index.js +3 -0
  73. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  74. package/lib/components/teams/sceneTranscript.js +294 -0
  75. package/lib/components/teams/teamConnections.d.ts +2 -2
  76. package/lib/components/teams/teamConnections.js +3 -3
  77. package/lib/components/teams/useA2ATeam.d.ts +4 -4
  78. package/lib/components/teams/useA2ATeam.js +50 -46
  79. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  80. package/lib/examples/AgentA2ATeamExample.js +70 -60
  81. package/lib/examples/AssistantExample.js +2 -2
  82. package/lib/loop/apps/AppComposer.d.ts +40 -4
  83. package/lib/loop/apps/AppComposer.js +194 -19
  84. package/lib/loop/apps/AppRenderer.d.ts +21 -3
  85. package/lib/loop/apps/AppRenderer.js +88 -48
  86. package/lib/loop/apps/appspec.d.ts +13 -1
  87. package/lib/loop/apps/appspec.js +305 -12
  88. package/lib/loop/apps/checks.d.ts +14 -0
  89. package/lib/loop/apps/checks.js +276 -5
  90. package/lib/loop/apps/composer.d.ts +21 -10
  91. package/lib/loop/apps/composer.js +41 -8
  92. package/lib/loop/apps/customComponents.d.ts +41 -0
  93. package/lib/loop/apps/customComponents.js +260 -0
  94. package/lib/loop/apps/hostTools.d.ts +8 -1
  95. package/lib/loop/apps/hostTools.js +9 -0
  96. package/lib/loop/apps/index.d.ts +1 -0
  97. package/lib/loop/apps/index.js +1 -0
  98. package/lib/loop/apps/language.d.ts +68 -0
  99. package/lib/loop/apps/language.js +275 -0
  100. package/lib/loop/apps/settingsInputs.d.ts +29 -0
  101. package/lib/loop/apps/settingsInputs.js +147 -0
  102. package/lib/loop/apps/uploads.d.ts +43 -0
  103. package/lib/loop/apps/uploads.js +66 -0
  104. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  105. package/lib/loop/core/a2uiComponents.js +15 -0
  106. package/lib/loop/core/index.d.ts +15 -1
  107. package/lib/loop/core/index.js +3 -0
  108. package/lib/loop/embed/AppEmbed.d.ts +25 -3
  109. package/lib/loop/embed/AppEmbed.js +106 -18
  110. package/lib/loop/embed/element.d.ts +5 -0
  111. package/lib/loop/embed/element.js +84 -5
  112. package/lib/loop/embed/embedConfig.d.ts +8 -0
  113. package/lib/loop/embed/embedConfig.js +18 -0
  114. package/lib/loop/embed/embedSession.d.ts +14 -0
  115. package/lib/loop/embed/embedSession.js +34 -0
  116. package/lib/loop/embed/hostBridge.d.ts +50 -3
  117. package/lib/loop/embed/hostBridge.js +22 -2
  118. package/lib/loop/index.d.ts +4 -3
  119. package/lib/loop/index.js +4 -3
  120. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  121. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  122. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  123. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  124. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  125. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  126. package/lib/loop/plugins/app-components/index.js +56 -0
  127. package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
  128. package/lib/loop/plugins/app-elements/AppElements.js +79 -0
  129. package/lib/loop/plugins/app-elements/index.d.ts +20 -0
  130. package/lib/loop/plugins/app-elements/index.js +43 -0
  131. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  132. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  133. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  134. package/lib/loop/plugins/app-page/appPageModel.js +122 -4
  135. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  136. package/lib/loop/plugins/app-page/index.js +1 -0
  137. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  138. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  139. package/lib/loop/plugins/assistant/index.js +8 -6
  140. package/lib/loop/plugins/chat/ChatView.js +16 -6
  141. package/lib/loop/plugins/chat/index.d.ts +1 -1
  142. package/lib/loop/plugins/chat/index.js +2 -1
  143. package/lib/models/PageTag.d.ts +1 -1
  144. package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
  145. package/lib/protocols/BrowserAgentAdapter.js +13 -10
  146. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  147. package/lib/runtimes/browser/protocol.d.ts +1 -1
  148. package/lib/specs/actions.js +78 -1
  149. package/lib/specs/apps.d.ts +5 -0
  150. package/lib/specs/apps.js +1518 -216
  151. package/lib/specs/appspecSchema.js +630 -3
  152. package/lib/specs/teams/teams.d.ts +3 -0
  153. package/lib/specs/teams/teams.js +249 -0
  154. package/lib/specs/uiPlugins.js +6 -1
  155. package/lib/types/agentspecs.d.ts +200 -1
  156. package/lib/types/chat.d.ts +6 -5
  157. package/lib/types/teams.d.ts +13 -2
  158. package/package.json +2 -2
  159. package/scripts/codegen/generate_apps.py +20 -2
  160. package/scripts/codegen/generate_teams.py +15 -2
  161. package/lib/components/inspector/JsonTree.d.ts +0 -24
  162. package/lib/components/inspector/JsonTree.js +0 -89
  163. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  164. package/lib/components/inspector/a2aInspect.js +0 -478
  165. package/lib/components/inspector/agentInspector.d.ts +0 -179
  166. package/lib/components/inspector/agentInspector.js +0 -267
  167. package/lib/components/inspector/chatInspect.d.ts +0 -38
  168. package/lib/components/inspector/chatInspect.js +0 -65
@@ -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 { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, resumeOf, } from './embedConfig';
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 (['mode', 'accent', 'theme', 'font', 'height', 'resume'].includes(name)) {
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 — beside the
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 { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf } from '../apps/hostTools';
27
- export { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf };
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
+ }
@@ -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 => setSurfaces(previous => [...previous, 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>;