@datalayer/agent-runtimes 1.3.114 → 1.4.0

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 (73) hide show
  1. package/dist-embed/chunks/{ChatView-D41Nx5rC.js → ChatView-BpKZPdcp.js} +4 -4
  2. package/dist-embed/datalayer-app-main.js +150 -150
  3. package/lib/apps/apps/appspec.d.ts +13 -0
  4. package/lib/apps/apps/appspec.js +71 -0
  5. package/lib/apps/apps/checks.d.ts +1 -1
  6. package/lib/apps/apps/checks.js +2 -2
  7. package/lib/apps/apps/customComponents.js +1 -1
  8. package/lib/apps/core/canvasBlocks.d.ts +3 -3
  9. package/lib/apps/core/canvasParts.d.ts +6 -4
  10. package/lib/apps/embed/AppEmbed.js +3 -7
  11. package/lib/apps/embed/embedSession.d.ts +8 -4
  12. package/lib/apps/embed/embedSession.js +16 -9
  13. package/lib/apps/plugins/a2ui-surface/AnswerShows.d.ts +21 -0
  14. package/lib/apps/plugins/a2ui-surface/AnswerShows.js +29 -0
  15. package/lib/apps/plugins/a2ui-surface/SurfaceToolResult.js +4 -7
  16. package/lib/apps/plugins/a2ui-surface/index.d.ts +2 -1
  17. package/lib/apps/plugins/a2ui-surface/index.js +10 -1
  18. package/lib/apps/plugins/a2ui-surface/toolResult.d.ts +20 -11
  19. package/lib/apps/plugins/a2ui-surface/toolResult.js +27 -11
  20. package/lib/apps/plugins/app-components/index.d.ts +1 -1
  21. package/lib/apps/plugins/app-components/index.js +2 -2
  22. package/lib/apps/plugins/app-elements/AppElements.d.ts +3 -3
  23. package/lib/apps/plugins/app-elements/AppElements.js +5 -20
  24. package/lib/apps/plugins/app-elements/QuestionView.d.ts +28 -0
  25. package/lib/apps/plugins/app-elements/QuestionView.js +57 -0
  26. package/lib/apps/plugins/app-elements/index.d.ts +7 -4
  27. package/lib/apps/plugins/app-elements/index.js +14 -5
  28. package/lib/apps/plugins/canvas-blocks/index.d.ts +13 -13
  29. package/lib/apps/plugins/canvas-blocks/index.js +24 -24
  30. package/lib/apps/plugins/canvas-parts/index.d.ts +7 -1
  31. package/lib/apps/plugins/canvas-parts/index.js +36 -0
  32. package/lib/apps/plugins/chat/ChatView.js +7 -0
  33. package/lib/chat/base/ChatBase.js +234 -43
  34. package/lib/chat/base/loopElement.d.ts +16 -17
  35. package/lib/chat/base/loopElement.js +11 -27
  36. package/lib/chat/base/loopStep.d.ts +15 -15
  37. package/lib/chat/base/loopStep.js +10 -13
  38. package/lib/chat/base/loopWindow.d.ts +15 -20
  39. package/lib/chat/base/loopWindow.js +26 -27
  40. package/lib/chat/base/uiOps.d.ts +96 -0
  41. package/lib/chat/base/uiOps.js +251 -0
  42. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  43. package/lib/chat/messages/ChatMessageList.js +4 -4
  44. package/lib/components/a2ui/datalayer/Chart.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/schema.js +1 -1
  46. package/lib/components/teams/AnswerSurfaces.d.ts +3 -2
  47. package/lib/components/teams/AnswerSurfaces.js +7 -7
  48. package/lib/components/teams/useA2ATeam.d.ts +1 -1
  49. package/lib/config/AgentConfiguration.d.ts +1 -1
  50. package/lib/protocols/AGUIAdapter.d.ts +7 -0
  51. package/lib/protocols/AGUIAdapter.js +43 -3
  52. package/lib/specs/agents/agents.js +338 -169
  53. package/lib/specs/apps.js +47 -0
  54. package/lib/specs/appspecSchema.js +16 -0
  55. package/lib/specs/cogs.js +6 -3
  56. package/lib/specs/index.d.ts +2 -1
  57. package/lib/specs/index.js +2 -1
  58. package/lib/specs/protocols.d.ts +24 -0
  59. package/lib/specs/protocols.js +197 -0
  60. package/lib/specs/ui.d.ts +21 -0
  61. package/lib/specs/{uiPlugins.js → ui.js} +14 -14
  62. package/lib/types/agentspecs.d.ts +51 -5
  63. package/lib/types/messages.d.ts +7 -0
  64. package/lib/types/protocol.d.ts +8 -0
  65. package/package.json +2 -2
  66. package/scripts/codegen/generate_agents.py +43 -8
  67. package/scripts/codegen/generate_protocols.py +207 -0
  68. package/scripts/codegen/generate_sandbox_agents.py +1 -1
  69. package/scripts/codegen/{generate_ui_plugins.py → generate_ui.py} +40 -40
  70. package/scripts/gen_workers.py +2 -2
  71. package/lib/chat/base/loopMessage.d.ts +0 -37
  72. package/lib/chat/base/loopMessage.js +0 -37
  73. package/lib/specs/uiPlugins.d.ts +0 -21
@@ -87,6 +87,19 @@ export interface ParsedAppspec {
87
87
  * is always whole, so an editor can open it and show what is wrong.
88
88
  */
89
89
  export declare function parseAppspec(document: unknown): ParsedAppspec;
90
+ /**
91
+ * What is wrong with the UI an application says its answers render in
92
+ * (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
93
+ * know or said twice, one not enabled while it is offered.
94
+ */
95
+ export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string[];
96
+ /**
97
+ * What is wrong with the protocols an application says it speaks
98
+ * (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
99
+ * know or said twice, a planned protocol while it is offered, and a `pap`,
100
+ * `pact` or `a2a` block configuring a protocol it does not list.
101
+ */
102
+ export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a'>): string[];
90
103
  /**
91
104
  * An application as the document its file holds.
92
105
  *
@@ -3,6 +3,8 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { BEHAVIOURS } from './rules';
6
+ import { PROTOCOL_CATALOGUE } from '../../specs/protocols';
7
+ import { UI_CATALOGUE } from '../../specs/ui';
6
8
  import { DEFAULT_MAX_FILES, DEFAULT_UPLOAD_MB } from './uploads';
7
9
  /** The version of the spec itself. */
8
10
  export const APP_SCHEMA = 'loop.app/v1';
@@ -130,6 +132,8 @@ export function emptyAppspec(kind = 'chat') {
130
132
  context: [],
131
133
  contents: [],
132
134
  connections: [],
135
+ protocols: [],
136
+ ui: [],
133
137
  rules: [],
134
138
  permissions: {
135
139
  spaces: [],
@@ -195,6 +199,8 @@ const KNOWN_KEYS = [
195
199
  'context',
196
200
  'contents',
197
201
  'connections',
202
+ 'protocols',
203
+ 'ui',
198
204
  'pap',
199
205
  'pact',
200
206
  'a2a',
@@ -809,6 +815,8 @@ export function parseAppspec(document) {
809
815
  context: texts(data.context),
810
816
  contents: texts(data.contents),
811
817
  connections: records(data.connections).map(parseConnection),
818
+ protocols: texts(data.protocols),
819
+ ui: texts(data.ui),
812
820
  rules: records(data.rules).map(parseRule),
813
821
  permissions: {
814
822
  spaces: records(permissions.spaces).map(grant => ({
@@ -894,8 +902,69 @@ export function parseAppspec(document) {
894
902
  if (isData(data.a2a)) {
895
903
  app.a2a = parseA2A(data.a2a);
896
904
  }
905
+ problems.push(...protocolsProblems(app), ...uiProblems(app));
897
906
  return { app, problems };
898
907
  }
908
+ /**
909
+ * What is wrong with the UI an application says its answers render in
910
+ * (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
911
+ * know or said twice, one not enabled while it is offered.
912
+ */
913
+ export function uiProblems(app) {
914
+ const problems = [];
915
+ const seen = [];
916
+ for (const id of app.ui) {
917
+ if (seen.includes(id)) {
918
+ problems.push(`It lists the UI '${id}' twice.`);
919
+ continue;
920
+ }
921
+ seen.push(id);
922
+ const spec = UI_CATALOGUE[id];
923
+ if (!spec) {
924
+ problems.push(`There is no UI named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.`);
925
+ }
926
+ else if (app.enabled && !spec.enabled) {
927
+ problems.push(`The UI ${spec.name} ('${id}') is not enabled: an application cannot render in it yet.`);
928
+ }
929
+ }
930
+ return problems;
931
+ }
932
+ /**
933
+ * What is wrong with the protocols an application says it speaks
934
+ * (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
935
+ * know or said twice, a planned protocol while it is offered, and a `pap`,
936
+ * `pact` or `a2a` block configuring a protocol it does not list.
937
+ */
938
+ export function protocolsProblems(app) {
939
+ const problems = [];
940
+ const seen = [];
941
+ for (const id of app.protocols) {
942
+ if (seen.includes(id)) {
943
+ problems.push(`It lists the protocol '${id}' twice.`);
944
+ continue;
945
+ }
946
+ seen.push(id);
947
+ const spec = PROTOCOL_CATALOGUE[id];
948
+ if (!spec) {
949
+ problems.push(`There is no protocol named '${id}': one of ${Object.keys(PROTOCOL_CATALOGUE).sort().join(', ')}.`);
950
+ }
951
+ else if (app.enabled && spec.status === 'planned') {
952
+ problems.push(`${spec.name} ('${id}') is planned, not shipped: an application cannot speak it yet.`);
953
+ }
954
+ }
955
+ const configured = {
956
+ pap: Boolean(app.pap && (app.pap.connections.length > 0 || app.pap.company.enabled)),
957
+ pact: Boolean(app.pact && app.pact.connections.length > 0),
958
+ a2a: Boolean(app.a2a && app.a2a.extensions.length > 0),
959
+ };
960
+ for (const spec of Object.values(PROTOCOL_CATALOGUE)) {
961
+ const block = spec.appspecBlock;
962
+ if (block && configured[block] && !app.protocols.includes(spec.id)) {
963
+ problems.push(`Its \`${block}\` block configures ${spec.name}, which it does not list: add '${spec.id}' to \`protocols\`.`);
964
+ }
965
+ }
966
+ return problems;
967
+ }
899
968
  // --- writing --------------------------------------------------------------------------
900
969
  const sameList = (a, b) => a.length === b.length && a.every((item, index) => item === b[index]);
901
970
  /** A document with a key only when its value says something. */
@@ -1148,6 +1217,8 @@ export function dumpAppspec(app) {
1148
1217
  .value('access', connection.access, 'read')
1149
1218
  .value('as', connection.as, 'owner')
1150
1219
  .list('only', connection.only).data))
1220
+ .list('protocols', app.protocols)
1221
+ .list('ui', app.ui)
1151
1222
  .part('pap', dumpPap(app.pap))
1152
1223
  .part('pact', dumpPact(app.pact))
1153
1224
  .part('a2a', dumpA2A(app.a2a))
@@ -11,7 +11,7 @@ export interface AppCheck {
11
11
  }
12
12
  /**
13
13
  * The component a layout names (LOOP C-13), from the catalogs of the enabled
14
- * UI plugins, by the name a surface gives it.
14
+ * UI specs, by the name a surface gives it.
15
15
  */
16
16
  export declare const componentNamed: (name: string) => ComponentSpec | undefined;
17
17
  /**
@@ -29,7 +29,7 @@ import { getCog } from '../../specs/cogs';
29
29
  import { getFrame } from '../../specs/frames';
30
30
  import { getGate } from '../../specs/gates';
31
31
  import { GUARD_CATALOGUE } from '../../specs/guards';
32
- import { getComponent } from '../../specs/uiPlugins';
32
+ import { getComponent } from '../../specs/ui';
33
33
  import { MCP_SERVER_LIBRARY } from '../../specs/mcpServers';
34
34
  import { getMemory } from '../../specs/memory';
35
35
  import { getModel } from '../../specs/models';
@@ -57,7 +57,7 @@ const idOf = (ref) => {
57
57
  };
58
58
  /**
59
59
  * The component a layout names (LOOP C-13), from the catalogs of the enabled
60
- * UI plugins, by the name a surface gives it.
60
+ * UI specs, by the name a surface gives it.
61
61
  */
62
62
  export const componentNamed = (name) => getComponent(name);
63
63
  const own = (record, key) => Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * @module apps/apps/customComponents
18
18
  */
19
- import { getComponent } from '../../specs/uiPlugins';
19
+ import { getComponent } from '../../specs/ui';
20
20
  /** Its name: a word starting with a capital letter, as the catalog's are named. */
21
21
  export const CUSTOM_COMPONENT_NAME = /^[A-Z][A-Za-z0-9]{0,63}$/;
22
22
  /** What one of its properties may be typed: what the renderer tells apart. */
@@ -1,7 +1,7 @@
1
1
  import type { ComponentSpec } from '../../types/agentspecs';
2
2
  /**
3
3
  * A block the Canvas may place (LOOP C-12): one component of the catalog of
4
- * visual components (C-13), contributed by the plugin of the UI plugin that
4
+ * visual components (C-13), contributed by the plugin of the UI that
5
5
  * renders it. The Canvas's palette is what the enabled plugins contribute
6
6
  * here, and nothing else: a plugin disabled takes its blocks off it, and an
7
7
  * application that uses one of them says so in its setup notes.
@@ -9,8 +9,8 @@ import type { ComponentSpec } from '../../types/agentspecs';
9
9
  export type CanvasBlockContribution = {
10
10
  /** The name a surface gives it, the component's id (e.g. 'Table'). */
11
11
  id: string;
12
- /** The UI plugin of the catalogue that renders it (e.g. 'a2ui'). */
13
- uiPlugin: string;
12
+ /** The UI of the catalogue that renders it (e.g. 'a2ui'). */
13
+ ui: string;
14
14
  component: ComponentSpec;
15
15
  };
16
16
  export declare const LoopCanvasBlock: import("@datalayer/reactor").ContributionPoint<CanvasBlockContribution>;
@@ -1,8 +1,10 @@
1
1
  /** The kinds of part an agent is built from on the Canvas. */
2
- export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model';
2
+ export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
3
3
  /**
4
4
  * A part of an agent the Canvas may place (LOOP C-20): a connection to a
5
- * server of the catalogue, a skill, a model, a rule, a test, an event —
5
+ * server of the catalogue, a skill, a model, a rule, a test, an event, a
6
+ * protocol it speaks, a UI its answers render in, the character it shows
7
+ * (AGENT_PROTOCOLS PS-04, PS-05) —
6
8
  * contributed by the plugin of what it comes from. The toolbox is what the
7
9
  * enabled plugins contribute here, and nothing else: a plugin disabled takes
8
10
  * its parts off it, as it takes its blocks off the palette.
@@ -18,8 +20,8 @@ export type CanvasPartContribution = {
18
20
  emoji?: string;
19
21
  /**
20
22
  * What it arrives with, spelled as the Appspec's text spells it: a
21
- * connection's, a rule's, a test's or an event's object, a skill's or a
22
- * model's id. Every example is one the Appspec's schema takes as it is.
23
+ * connection's, a rule's, a test's or an event's object, a skill's, a
24
+ * model's, a protocol's, a UI's or a character's id. Every example is one the Appspec's schema takes as it is.
23
25
  */
24
26
  example: unknown;
25
27
  /** Whether what it names is enabled today; a part that is not is offered, and its application's setup notes say so. */
@@ -157,18 +157,14 @@ export function useWindowPort({ serverUrl, embedToken, session, host, }) {
157
157
  if (!uid.current) {
158
158
  throw new Error(WINDOW_NOT_YET);
159
159
  }
160
- const said = await postWindowMessage({
160
+ // What its code answers comes on the ui socket, raised as any
161
+ // window message is (REACTOR_UI RU-W).
162
+ await postWindowMessage({
161
163
  serverUrl,
162
164
  uid: uid.current,
163
165
  ...(embedToken ? { token: embedToken } : {}),
164
166
  data,
165
167
  });
166
- for (const answer of said) {
167
- hostRef.current?.onEvent?.({
168
- type: 'window-message',
169
- detail: { data: answer },
170
- });
171
- }
172
168
  });
173
169
  return () => port(null);
174
170
  }, [serverUrl, embedToken, host]);
@@ -51,7 +51,9 @@ export declare function forgetSessionsInMemory(): void;
51
51
  /**
52
52
  * A session's conversation as the chat draws it, from the AG-UI messages
53
53
  * the runtime says (`GET …/sessions/{uid}/messages`): what was said and
54
- * answered, in words. A page an answer drew is not drawn again here.
54
+ * answered, in words. What its messages showed comes on the conversation's
55
+ * ui socket once the chat opens it (REACTOR_UI RU-W), never here: a
56
+ * `reactor.ui` activity in a thread is refused.
55
57
  */
56
58
  export declare function messagesOfThread(messages: unknown): ChatMessage[];
57
59
  /** What asking for a kept session found. */
@@ -79,8 +81,10 @@ export declare function reattachSession({ serverUrl, uid, token, fetcher, }: {
79
81
  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
82
  /**
81
83
  * 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
+ * runs on it. What the code tells the page back arrives on the
85
+ * conversation's ui socket (REACTOR_UI RU-W), raised as any window message
86
+ * is; refused — the runtime's sentence — when the session or its code takes
87
+ * none.
84
88
  */
85
89
  export declare function postWindowMessage({ serverUrl, uid, token, data, fetcher, }: {
86
90
  serverUrl: string;
@@ -88,6 +92,6 @@ export declare function postWindowMessage({ serverUrl, uid, token, data, fetcher
88
92
  token?: string;
89
93
  data: unknown;
90
94
  fetcher?: typeof fetch;
91
- }): Promise<unknown[]>;
95
+ }): Promise<void>;
92
96
  /** What the page says, once, when the conversation it kept is gone. */
93
97
  export declare const sessionGoneSentence: (name: string) => string;
@@ -2,7 +2,8 @@
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
+ import { windowTurnErrorOf } from '../../chat/base/loopWindow';
6
+ import { AGUI_NAME } from '@datalayer/reactor/ui';
6
7
  /** Where a visit's session is kept in the host's storage: `<prefix>:<app>:<visit>`. */
7
8
  export const EMBED_SESSION_KEY_PREFIX = 'datalayer-app:session';
8
9
  /**
@@ -93,7 +94,9 @@ export function forgetSessionsInMemory() {
93
94
  /**
94
95
  * A session's conversation as the chat draws it, from the AG-UI messages
95
96
  * the runtime says (`GET …/sessions/{uid}/messages`): what was said and
96
- * answered, in words. A page an answer drew is not drawn again here.
97
+ * answered, in words. What its messages showed comes on the conversation's
98
+ * ui socket once the chat opens it (REACTOR_UI RU-W), never here: a
99
+ * `reactor.ui` activity in a thread is refused.
97
100
  */
98
101
  export function messagesOfThread(messages) {
99
102
  if (!Array.isArray(messages)) {
@@ -104,7 +107,10 @@ export function messagesOfThread(messages) {
104
107
  if (!message || typeof message !== 'object') {
105
108
  continue;
106
109
  }
107
- const { id, role, content } = message;
110
+ const { id, role, content, activityType } = message;
111
+ if (role === 'activity' && activityType === AGUI_NAME) {
112
+ throw new Error(`Activity ${String(id)}: what a message showed comes on the ui socket (REACTOR_UI RU-W)`);
113
+ }
108
114
  if ((role === 'user' || role === 'assistant') &&
109
115
  typeof id === 'string' &&
110
116
  typeof content === 'string' &&
@@ -137,8 +143,10 @@ export async function reattachSession({ serverUrl, uid, token, fetcher = fetch,
137
143
  export const WINDOW_NOT_YET = 'The application takes a window message once its conversation has started, on its own server (the "server" attribute).';
138
144
  /**
139
145
  * 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.
146
+ * runs on it. What the code tells the page back arrives on the
147
+ * conversation's ui socket (REACTOR_UI RU-W), raised as any window message
148
+ * is; refused — the runtime's sentence — when the session or its code takes
149
+ * none.
142
150
  */
143
151
  export async function postWindowMessage({ serverUrl, uid, token, data, fetcher = fetch, }) {
144
152
  const response = await fetcher(`${serverUrl.replace(/\/+$/, '')}/api/v1/apps/sessions/${encodeURIComponent(uid)}/window`, {
@@ -160,11 +168,10 @@ export async function postWindowMessage({ serverUrl, uid, token, data, fetcher =
160
168
  }
161
169
  throw new Error(detail || `The window message was refused (${response.status}).`);
162
170
  }
163
- const turn = windowTurnOf(await response.text());
164
- if (turn.error) {
165
- throw new Error(turn.error);
171
+ const error = windowTurnErrorOf(await response.text());
172
+ if (error) {
173
+ throw new Error(error);
166
174
  }
167
- return turn.said;
168
175
  }
169
176
  /** What the page says, once, when the conversation it kept is gone. */
170
177
  export const sessionGoneSentence = (name) => `Your earlier conversation with ${name} has ended, so this is a new one.`;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * What an application's answer shows, drawn from its display (LOOP P-04,
3
+ * REACTOR_UI RU-2): the A2UI surface in its data, and its buttons pressing
4
+ * only the actions the display offered, each with the token the session
5
+ * minted for it. The answer under its message and an element in a panel or
6
+ * a page of its own are drawn by this one view.
7
+ *
8
+ * @module apps/plugins/a2ui-surface/AnswerShows
9
+ */
10
+ import type { JSX } from 'react';
11
+ import type { Display } from '@datalayer/reactor/ui';
12
+ export type AnswerShowsViewProps = {
13
+ display: Display;
14
+ /**
15
+ * Say something as the reader, with what goes with the run besides it;
16
+ * `false` when the chat cannot take it.
17
+ */
18
+ send: (message: string, forwardedProps?: Record<string, unknown>) => boolean;
19
+ };
20
+ export declare function AnswerShowsView({ display, send, }: AnswerShowsViewProps): JSX.Element;
21
+ export default AnswerShowsView;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Text } from '@primer/react';
4
+ import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
5
+ import { useOwnCatalog } from './ownComponents';
6
+ import { answerPress, answerSurface } from './toolResult';
7
+ export function AnswerShowsView({ display, send, }) {
8
+ const parsed = useMemo(() => answerSurface(display, SURFACE_CATALOG_ID), [display]);
9
+ // With the components the application's developer wrote (LOOP P-17).
10
+ const catalog = useOwnCatalog();
11
+ const [said, setSaid] = useState(null);
12
+ if (!parsed?.messages || parsed.messages.length === 0) {
13
+ return _jsx(Text, { sx: { color: 'fg.muted' }, children: display.text });
14
+ }
15
+ const onAction = (action) => {
16
+ const pressed = answerPress(display, action);
17
+ if ('refused' in pressed) {
18
+ setSaid(pressed.refused);
19
+ return;
20
+ }
21
+ setSaid(send(pressed.message, pressed.forwardedProps)
22
+ ? null
23
+ : 'The chat is not ready to take it yet.');
24
+ };
25
+ return (_jsx(InlineSurface
26
+ // Drawn again whole when the code changes it.
27
+ , { messages: parsed.messages, onAction: onAction, validationError: said, catalog: catalog }, JSON.stringify(display)));
28
+ }
29
+ export default AnswerShowsView;
@@ -5,7 +5,7 @@ import { Box } from '@datalayer/primer-addons';
5
5
  import { encodeFormSubmission } from '../../../chat/messages/formSubmission';
6
6
  import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
7
7
  import { useOwnCatalog } from './ownComponents';
8
- import { answerAction, readA2uiToolResult, validateA2uiSubmission, } from './toolResult';
8
+ import { readA2uiToolResult, validateA2uiSubmission, } from './toolResult';
9
9
  export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
10
10
  const parsed = useMemo(() => context.status === 'complete'
11
11
  ? readA2uiToolResult(context.result, SURFACE_CATALOG_ID)
@@ -30,12 +30,9 @@ export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
30
30
  }, [parsed, context.toolCallId]);
31
31
  const handleAction = useCallback((action) => {
32
32
  if (action.name !== 'submit_a2ui_form') {
33
- // A button of what an application's answer shows: its action goes to
34
- // the application, as a button of its page does (LOOP P-04).
35
- const pressed = answerAction(action);
36
- if (pressed && !send(pressed.message, pressed.forwardedProps)) {
37
- setValidationError('The chat is not ready to take it yet.');
38
- }
33
+ // A tool's surface submits its form, and nothing else: the buttons
34
+ // of what an application's answer shows are its display's
35
+ // (`AnswerShowsView`, REACTOR_UI RU-2).
39
36
  return;
40
37
  }
41
38
  const values = (action.context ?? {});
@@ -32,5 +32,6 @@ export declare function createA2uiSurfacePlugin(options?: A2uiSurfacePluginOptio
32
32
  /** The instance the preset mounts: no host callbacks. */
33
33
  export declare const A2uiSurfacePlugin: ReactorPlugin<Record<string, never>, unknown, unknown>;
34
34
  export type { SurfaceRendered, SurfaceSubmitted } from './SurfaceToolResult';
35
- export { answerAction, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, type A2uiFieldRule, type A2uiToolResult, } from './toolResult';
35
+ export { AnswerShowsView } from './AnswerShows';
36
+ export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, type A2uiFieldRule, type A2uiToolResult, type AnswerPress, } from './toolResult';
36
37
  export default A2uiSurfacePlugin;
@@ -5,6 +5,8 @@ import { Renderers, ToolBindings } from '@datalayer/reactor/ui';
5
5
  import { defineRenderer, } from '@datalayer/reactor/ui/react';
6
6
  import { AGENTSPECS } from '../../../specs/agents';
7
7
  import { LoopSlots } from '../../core';
8
+ import { AnswerShowsView } from './AnswerShows';
9
+ import { ANSWER_DISPLAY_TYPE } from './toolResult';
8
10
  import { SurfaceToolResult, } from './SurfaceToolResult';
9
11
  export const A2UI_SURFACE_PLUGIN_NAME = '@datalayer/loop-plugin-a2ui-surface';
10
12
  /** The renderer name a spec binds a tool to. */
@@ -74,6 +76,11 @@ export function createA2uiSurfacePlugin(options = {}) {
74
76
  }
75
77
  return (_jsx(SurfaceToolResult, { context: context, send: send, onRendered: options.onRendered, onSubmitted: options.onSubmitted }, context.toolCallId));
76
78
  }
79
+ // What an application's answer shows (REACTOR_UI RU-2): its buttons press
80
+ // what the display offered, through the workspace's chat.
81
+ function Answer({ display }) {
82
+ return _jsx(AnswerShowsView, { display: display, send: send });
83
+ }
77
84
  const plugin = definePlugin({
78
85
  name: counter === 1
79
86
  ? A2UI_SURFACE_PLUGIN_NAME
@@ -89,6 +96,7 @@ export function createA2uiSurfacePlugin(options = {}) {
89
96
  // A host's own instance, with its callbacks, over the preset's.
90
97
  rank: options.onRendered || options.onSubmitted ? 1 : 0,
91
98
  }), { id }),
99
+ contribution(Renderers, defineRenderer({ type: ANSWER_DISPLAY_TYPE, component: Answer }), { id: `${id}:answer` }),
92
100
  ...[...surfaceToolNames()].map(tool => contribution(ToolBindings, { tool, type: A2UI_SURFACE_TYPE }, { id: `${id}:${tool}` })),
93
101
  ],
94
102
  build() {
@@ -103,5 +111,6 @@ export function createA2uiSurfacePlugin(options = {}) {
103
111
  }
104
112
  /** The instance the preset mounts: no host callbacks. */
105
113
  export const A2uiSurfacePlugin = createA2uiSurfacePlugin();
106
- export { answerAction, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, } from './toolResult';
114
+ export { AnswerShowsView } from './AnswerShows';
115
+ export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, } from './toolResult';
107
116
  export default A2uiSurfacePlugin;
@@ -8,6 +8,7 @@
8
8
  * @module apps/plugins/a2ui-surface/toolResult
9
9
  */
10
10
  import type { A2uiMessage } from '@a2ui/web_core/v0_9';
11
+ import type { Display } from '@datalayer/reactor/ui';
11
12
  export type A2uiRequiredField = {
12
13
  id: string;
13
14
  label: string;
@@ -50,10 +51,13 @@ export declare function isEmptyA2uiValue(value: unknown): boolean;
50
51
  export declare function validateA2uiField(rule: A2uiFieldRule, value: unknown): string | null;
51
52
  /** Every rule's complaint about a submission; empty when it passes. */
52
53
  export declare function validateA2uiSubmission(rules: A2uiFieldRule[], values: Record<string, unknown>): string[];
53
- /** Where the surface of what an application's answer shows is named (LOOP P-04). */
54
- export declare const ANSWER_SURFACE_PREFIX = "answer-";
55
- /** A button pressed on an answer's surface, as the chat sends it on. */
56
- export type AnswerAction = {
54
+ /**
55
+ * The display type of what an application's answer shows (LOOP P-04,
56
+ * REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
57
+ */
58
+ export declare const ANSWER_DISPLAY_TYPE = "application/vnd.datalayer.loop.answer+json";
59
+ /** A button pressed on what an answer shows, as the chat sends it on. */
60
+ export type AnswerPress = {
57
61
  /** The person's turn, in words: the action's name. */
58
62
  message: string;
59
63
  /** What the session API reads besides the message (`forwardedProps.loop`). */
@@ -61,19 +65,24 @@ export type AnswerAction = {
61
65
  loop: {
62
66
  action: {
63
67
  name: string;
68
+ display_id: string;
69
+ token: string;
64
70
  payload: Record<string, unknown>;
65
71
  };
66
72
  };
67
73
  };
68
74
  };
75
+ /** The A2UI surface an answer's display draws, or `null` for a display that has none. */
76
+ export declare function answerSurface(display: Display, catalogId: string): A2uiToolResult | null;
69
77
  /**
70
- * What a button pressed on the surface an application's answer shows sends:
71
- * its action, by name, with what it read — the application's code reacts
72
- * with its `@app.action(name)`, as to a button of its page. `null` for any
73
- * other surface, whose buttons stay the agent's form submission.
78
+ * What a button pressed on what an answer shows sends: the action the
79
+ * display offered under that name, with its token and what the button read
80
+ * — the application's code reacts with its `@app.action(name)`. A button
81
+ * whose action the display did not offer sends nothing: the sentence why.
74
82
  */
75
- export declare function answerAction(action: {
83
+ export declare function answerPress(display: Display, action: {
76
84
  name: string;
77
- surfaceId: string;
78
85
  context?: Record<string, unknown>;
79
- }): AnswerAction | null;
86
+ }): AnswerPress | {
87
+ refused: string;
88
+ };
@@ -104,24 +104,40 @@ export function validateA2uiSubmission(rules, values) {
104
104
  .map(rule => validateA2uiField(rule, values[rule.id]))
105
105
  .filter((message) => message !== null);
106
106
  }
107
- /** Where the surface of what an application's answer shows is named (LOOP P-04). */
108
- export const ANSWER_SURFACE_PREFIX = 'answer-';
109
107
  /**
110
- * What a button pressed on the surface an application's answer shows sends:
111
- * its action, by name, with what it read — the application's code reacts
112
- * with its `@app.action(name)`, as to a button of its page. `null` for any
113
- * other surface, whose buttons stay the agent's form submission.
108
+ * The display type of what an application's answer shows (LOOP P-04,
109
+ * REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
114
110
  */
115
- export function answerAction(action) {
116
- if (!action.surfaceId.startsWith(ANSWER_SURFACE_PREFIX) ||
117
- !action.name.trim()) {
111
+ export const ANSWER_DISPLAY_TYPE = 'application/vnd.datalayer.loop.answer+json';
112
+ /** The A2UI surface an answer's display draws, or `null` for a display that has none. */
113
+ export function answerSurface(display, catalogId) {
114
+ if (display.type !== ANSWER_DISPLAY_TYPE) {
118
115
  return null;
119
116
  }
117
+ return readA2uiToolResult(display.data?.surface, catalogId);
118
+ }
119
+ /**
120
+ * What a button pressed on what an answer shows sends: the action the
121
+ * display offered under that name, with its token and what the button read
122
+ * — the application's code reacts with its `@app.action(name)`. A button
123
+ * whose action the display did not offer sends nothing: the sentence why.
124
+ */
125
+ export function answerPress(display, action) {
126
+ const name = action.name.trim();
127
+ const offer = (display.actions ?? []).find(one => one.name === name);
128
+ if (!offer) {
129
+ return { refused: `“${name || 'This button'}” was not offered here.` };
130
+ }
120
131
  return {
121
- message: action.name.trim(),
132
+ message: name,
122
133
  forwardedProps: {
123
134
  loop: {
124
- action: { name: action.name.trim(), payload: { ...action.context } },
135
+ action: {
136
+ name,
137
+ display_id: display.id,
138
+ token: offer.token,
139
+ payload: { ...action.context },
140
+ },
125
141
  },
126
142
  },
127
143
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `app-components` — the components an application's developer wrote (LOOP
3
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
4
+ * of its Canvas's palette (`loop.canvas.block`, as a UI's components
5
5
  * are) and a renderer of the catalog its page, its elements and its answers
6
6
  * are drawn with (`loop.a2ui.component`). One plugin per application, in its
7
7
  * workspace's preset only: the catalog grows for it, it does not open.
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * `app-components` — the components an application's developer wrote (LOOP
7
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
8
+ * of its Canvas's palette (`loop.canvas.block`, as a UI's components
9
9
  * are) and a renderer of the catalog its page, its elements and its answers
10
10
  * are drawn with (`loop.a2ui.component`). One plugin per application, in its
11
11
  * workspace's preset only: the catalog grows for it, it does not open.
@@ -43,7 +43,7 @@ export function defineAppComponentsPlugin(app, fetcher) {
43
43
  contributes: own.flatMap(component => [
44
44
  contribution(LoopCanvasBlock, {
45
45
  id: component.name,
46
- uiPlugin: `app:${app.id}`,
46
+ ui: `app:${app.id}`,
47
47
  component: customComponentEntry(component, app.version),
48
48
  }, { id: component.name }),
49
49
  contribution(LoopA2uiComponent, {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * The elements an application's code opened beside the conversation (LOOP
3
3
  * P-18): a side panel on the trailing edge, and a page of its own over the
4
- * workspace — each drawn from the same A2UI surface an answer is drawn from
5
- * (`InlineSurface`), its buttons sent to the application's actions as an
6
- * answer's are (`answerAction`), and closed with its own button.
4
+ * workspace — each drawn from its display as an answer is (`AnswerShowsView`),
5
+ * its buttons pressing the actions the display offered, and closed with its
6
+ * own button.
7
7
  *
8
8
  * Drawn from the workspace's root slot, positioned against the workspace:
9
9
  * nothing is drawn, and nothing is laid out, while nothing is open.