@datalayer/agent-runtimes 1.5.16 → 1.5.18

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 (53) hide show
  1. package/dist-embed/chunks/ChatView-Bz0eyWo6.js +32 -0
  2. package/dist-embed/datalayer-app-main.js +102 -102
  3. package/lib/apps/apps/appspec.d.ts +36 -4
  4. package/lib/apps/apps/appspec.js +104 -11
  5. package/lib/apps/apps/threads.d.ts +20 -0
  6. package/lib/apps/apps/threads.js +31 -0
  7. package/lib/apps/plugins/canvas-blocks/index.d.ts +7 -9
  8. package/lib/apps/plugins/canvas-blocks/index.js +8 -10
  9. package/lib/apps/plugins/canvas-parts/index.d.ts +4 -1
  10. package/lib/apps/plugins/canvas-parts/index.js +7 -3
  11. package/lib/apps/plugins/chat/ChatView.js +32 -7
  12. package/lib/chat/ChatFloating.js +12 -4
  13. package/lib/chat/base/ChatBase.js +31 -5
  14. package/lib/chat/messages/ChatMessageList.js +1 -1
  15. package/lib/config/AgentConfiguration.d.ts +5 -1
  16. package/lib/examples/AgentA2ATeamExample.js +6 -5
  17. package/lib/examples/ChatExample.js +2 -2
  18. package/lib/examples/VoiceChatExample.js +9 -3
  19. package/lib/hooks/useBrowserAgent.d.ts +1 -1
  20. package/lib/hooks/useBrowserAgent.js +1 -1
  21. package/lib/protocols/AGUIAdapter.d.ts +11 -0
  22. package/lib/protocols/AGUIAdapter.js +27 -2
  23. package/lib/specs/agents/agents.js +169 -169
  24. package/lib/specs/apps.d.ts +32 -32
  25. package/lib/specs/apps.js +624 -143
  26. package/lib/specs/appspecSchema.js +30 -2
  27. package/lib/specs/cogs.js +3 -3
  28. package/lib/specs/index.d.ts +1 -0
  29. package/lib/specs/index.js +1 -0
  30. package/lib/specs/protocols.js +2 -2
  31. package/lib/specs/sceneSchema.js +2 -0
  32. package/lib/specs/scenes.js +7 -7
  33. package/lib/specs/teams/teams.js +11 -11
  34. package/lib/specs/ui.d.ts +32 -6
  35. package/lib/specs/ui.js +760 -26
  36. package/lib/specs/uiRenderers.d.ts +17 -0
  37. package/lib/specs/uiRenderers.js +40 -0
  38. package/lib/specs/voices.d.ts +2 -0
  39. package/lib/specs/voices.js +5 -0
  40. package/lib/types/agentspecs.d.ts +63 -12
  41. package/lib/types/chat.d.ts +6 -0
  42. package/lib/types/protocol.d.ts +17 -0
  43. package/lib/utils/utils.js +8 -1
  44. package/lib/voice/ReadAloud.d.ts +35 -0
  45. package/lib/voice/ReadAloud.js +29 -0
  46. package/lib/voice/index.d.ts +7 -2
  47. package/lib/voice/index.js +1 -0
  48. package/package.json +2 -2
  49. package/scripts/codegen/generate_agents.py +60 -11
  50. package/scripts/codegen/generate_ui.py +68 -22
  51. package/scripts/codegen/generate_ui_renderers.py +176 -0
  52. package/scripts/codegen/generate_voices.py +5 -1
  53. package/dist-embed/chunks/ChatView-nnI1-MOR.js +0 -32
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @module apps/apps/appspec
26
26
  */
27
- import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, PapProfile, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec } from '../../types/agentspecs';
27
+ import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, PapProfile, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec, UiChoice } from '../../types/agentspecs';
28
28
  /** The version of the spec itself. */
29
29
  export declare const APP_SCHEMA = "loop.app/v1";
30
30
  /** The language an application's own words are in, unless it says (LOOP P-26). */
@@ -53,6 +53,36 @@ export declare const APP_ASSISTANT_CHARACTER_ID: RegExp;
53
53
  export declare function isAssistantCharacterId(value: unknown): value is AppAssistantCharacter;
54
54
  /** The layout a kind starts with. */
55
55
  export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
56
+ /** The media type of an answer drawn as A2UI surfaces. */
57
+ export declare const A2UI_OUTPUT = "application/json+a2ui";
58
+ /** What `uiFor` reads of an application besides its kind. */
59
+ export interface AppUiFacts {
60
+ /** Its layout; its kind's own when unsaid. */
61
+ layout?: AppLayout;
62
+ /** The media types it answers in. */
63
+ outputs?: readonly string[];
64
+ /** It signs people in at companies (`pap.connections`). */
65
+ signsIn?: boolean;
66
+ /** It has components of its own (`interface.customComponents`). */
67
+ ownComponents?: boolean;
68
+ }
69
+ /**
70
+ * The UI plugins an application is drawn with (AGENTIC_UI R8), as its `ui`
71
+ * lists them: what `appPreset` mounts for it. A decision: its page alone,
72
+ * drawn by its host. Every other kind: the conversation — `chat`, which
73
+ * stands for the conversation base the runtime mounts — then the A2UI
74
+ * surfaces when it answers in `application/json+a2ui`, the sign-in card when
75
+ * it signs people in at companies, its page, its layout and the Canvas's
76
+ * blocks when its layout is not `chat`, and its own components when it has
77
+ * some. What a host adds (the Preview's sidebar, a frame) is not the
78
+ * application's.
79
+ *
80
+ * agentspecs' `ui_for` is the same function: both are held to
81
+ * `agent_runtimes/tests/fixtures/app_ui.json`.
82
+ */
83
+ export declare function uiFor(kind: AppKind, facts?: AppUiFacts): UiChoice[];
84
+ /** The UI plugins an application is drawn with, by what its spec says (`uiFor`). */
85
+ export declare function uiOf(app: Pick<AppSpec, 'kind' | 'interface' | 'pap'>): UiChoice[];
56
86
  /** What an application keeps of what it did, when it does not say. */
57
87
  export declare const DEFAULT_RECORD_INCLUDE: string[];
58
88
  type Data = Record<string, unknown>;
@@ -97,9 +127,11 @@ export interface ParsedAppspec {
97
127
  */
98
128
  export declare function parseAppspec(document: unknown): ParsedAppspec;
99
129
  /**
100
- * What is wrong with the UI an application says its answers render in
101
- * (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
102
- * know or said twice, one not enabled while it is offered.
130
+ * What is wrong with the UI plugins an application names (AGENTIC_UI R8), in
131
+ * the sentences agentspecs says (`ui_choices_problems`): a plugin the
132
+ * catalogue does not have or said twice — a renderer named as a plugin is
133
+ * refused —, a renderer the catalogue does not have or the plugin may not be
134
+ * exposed through, and one not enabled while the application is offered.
103
135
  */
104
136
  export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string[];
105
137
  /**
@@ -5,6 +5,7 @@
5
5
  import { BEHAVIOURS } from './rules';
6
6
  import { PROTOCOL_CATALOGUE } from '../../specs/protocols';
7
7
  import { UI_CATALOGUE } from '../../specs/ui';
8
+ import { UI_RENDERER_CATALOGUE } from '../../specs/uiRenderers';
8
9
  import { DEFAULT_MAX_FILES, DEFAULT_UPLOAD_MB } from './uploads';
9
10
  /** The version of the spec itself. */
10
11
  export const APP_SCHEMA = 'loop.app/v1';
@@ -73,6 +74,50 @@ export const DEFAULT_LAYOUTS = {
73
74
  decision: 'page',
74
75
  worker: 'split',
75
76
  };
77
+ /** The media type of an answer drawn as A2UI surfaces. */
78
+ export const A2UI_OUTPUT = 'application/json+a2ui';
79
+ /**
80
+ * The UI plugins an application is drawn with (AGENTIC_UI R8), as its `ui`
81
+ * lists them: what `appPreset` mounts for it. A decision: its page alone,
82
+ * drawn by its host. Every other kind: the conversation — `chat`, which
83
+ * stands for the conversation base the runtime mounts — then the A2UI
84
+ * surfaces when it answers in `application/json+a2ui`, the sign-in card when
85
+ * it signs people in at companies, its page, its layout and the Canvas's
86
+ * blocks when its layout is not `chat`, and its own components when it has
87
+ * some. What a host adds (the Preview's sidebar, a frame) is not the
88
+ * application's.
89
+ *
90
+ * agentspecs' `ui_for` is the same function: both are held to
91
+ * `agent_runtimes/tests/fixtures/app_ui.json`.
92
+ */
93
+ export function uiFor(kind, facts = {}) {
94
+ if (kind === 'decision') {
95
+ return [{ plugin: 'app-page' }];
96
+ }
97
+ const drawn = [{ plugin: 'chat' }];
98
+ if (facts.outputs?.includes(A2UI_OUTPUT)) {
99
+ drawn.push({ plugin: 'a2ui-surface', renderer: 'a2ui' });
100
+ }
101
+ if (facts.signsIn) {
102
+ drawn.push({ plugin: 'pap-sign-in' });
103
+ }
104
+ if ((facts.layout ?? DEFAULT_LAYOUTS[kind]) !== 'chat') {
105
+ drawn.push({ plugin: 'page-layout' }, { plugin: 'app-page' }, { plugin: 'canvas-blocks' });
106
+ }
107
+ if (facts.ownComponents) {
108
+ drawn.push({ plugin: 'app-components' });
109
+ }
110
+ return drawn;
111
+ }
112
+ /** The UI plugins an application is drawn with, by what its spec says (`uiFor`). */
113
+ export function uiOf(app) {
114
+ return uiFor(app.kind, {
115
+ layout: app.interface.layout,
116
+ outputs: app.interface.outputs,
117
+ signsIn: Boolean(app.pap?.connections.length),
118
+ ownComponents: Boolean(app.interface.customComponents?.length),
119
+ });
120
+ }
76
121
  /** What an application keeps of what it did, when it does not say. */
77
122
  export const DEFAULT_RECORD_INCLUDE = ['conversations', 'actions', 'approvals'];
78
123
  const DEFAULT_KEEP_FOR = '1_years';
@@ -90,6 +135,30 @@ const texts = (value) => Array.isArray(value)
90
135
  ? value.filter((item) => typeof item === 'string')
91
136
  : [];
92
137
  const records = (value) => Array.isArray(value) ? value.filter(isData) : [];
138
+ /**
139
+ * An application's UI plugins as its document says them (AGENTIC_UI R8):
140
+ * `{plugin, renderer}` each. Anything else — a bare id, the old shape — is
141
+ * not read as a plugin: `parseAppspec` refuses it (`uiShapeProblems`).
142
+ */
143
+ const uiChoices = (value) => Array.isArray(value)
144
+ ? value.filter(isData).map(item => ({
145
+ plugin: text(item.plugin),
146
+ ...(text(item.renderer) ? { renderer: text(item.renderer) } : {}),
147
+ }))
148
+ : [];
149
+ /**
150
+ * An entry of `ui` that is not `{plugin, renderer}`, in agentspecs' sentence
151
+ * (`ui_choices_problems`): a bare id is refused, never read as the plugin it
152
+ * names, and one naming a renderer says how to write it.
153
+ */
154
+ const uiShapeProblems = (value) => Array.isArray(value)
155
+ ? value
156
+ .filter(item => !isData(item))
157
+ .map(item => `\`ui\` lists UI plugins, each as \`{plugin, renderer}\`: ${JSON.stringify(item)} is not one` +
158
+ (typeof item === 'string' && UI_RENDERER_CATALOGUE[item]
159
+ ? ` (${JSON.stringify(item)} is a UI renderer: name the plugin it renders, and say it as its \`renderer\`).`
160
+ : '.'))
161
+ : [];
93
162
  /**
94
163
  * One turn of a test's conversation as its document says it (STUDIO E-01):
95
164
  * what it says, chooses or presses, with a press's payload; nothing it does
@@ -133,7 +202,8 @@ export function emptyAppspec(kind = 'chat') {
133
202
  contents: [],
134
203
  connections: [],
135
204
  protocols: [],
136
- ui: [],
205
+ // A blank application starts with its kind's UI plugins (AGENTIC_UI R8).
206
+ ui: uiFor(kind),
137
207
  rules: [],
138
208
  permissions: {
139
209
  spaces: [],
@@ -937,6 +1007,7 @@ export function parseAppspec(document) {
937
1007
  problems.push(`\`${key}\` is not a field of the spec.`);
938
1008
  }
939
1009
  }
1010
+ problems.push(...uiShapeProblems(data.ui));
940
1011
  const schema = text(data.schema, APP_SCHEMA);
941
1012
  if (schema !== APP_SCHEMA) {
942
1013
  problems.push(`This reads ${APP_SCHEMA}; the application is written in ${schema}.`);
@@ -984,7 +1055,7 @@ export function parseAppspec(document) {
984
1055
  contents: texts(data.contents),
985
1056
  connections: records(data.connections).map(parseConnection),
986
1057
  protocols: texts(data.protocols),
987
- ui: texts(data.ui),
1058
+ ui: uiChoices(data.ui),
988
1059
  rules: records(data.rules).map(parseRule),
989
1060
  permissions: {
990
1061
  spaces: records(permissions.spaces).map(grant => ({
@@ -1097,25 +1168,45 @@ export function parseAppspec(document) {
1097
1168
  return { app, problems };
1098
1169
  }
1099
1170
  /**
1100
- * What is wrong with the UI an application says its answers render in
1101
- * (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
1102
- * know or said twice, one not enabled while it is offered.
1171
+ * What is wrong with the UI plugins an application names (AGENTIC_UI R8), in
1172
+ * the sentences agentspecs says (`ui_choices_problems`): a plugin the
1173
+ * catalogue does not have or said twice — a renderer named as a plugin is
1174
+ * refused —, a renderer the catalogue does not have or the plugin may not be
1175
+ * exposed through, and one not enabled while the application is offered.
1103
1176
  */
1104
1177
  export function uiProblems(app) {
1105
1178
  const problems = [];
1106
1179
  const seen = [];
1107
- for (const id of app.ui) {
1180
+ for (const choice of app.ui) {
1181
+ const id = choice.plugin;
1108
1182
  if (seen.includes(id)) {
1109
- problems.push(`It lists the UI '${id}' twice.`);
1183
+ problems.push(`It lists the UI plugin '${id}' twice.`);
1110
1184
  continue;
1111
1185
  }
1112
1186
  seen.push(id);
1113
1187
  const spec = UI_CATALOGUE[id];
1114
1188
  if (!spec) {
1115
- problems.push(`There is no UI named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.`);
1189
+ problems.push(`There is no UI plugin named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.` +
1190
+ (UI_RENDERER_CATALOGUE[id]
1191
+ ? ` '${id}' is a UI renderer: say it as a plugin's \`renderer\`.`
1192
+ : ''));
1193
+ continue;
1194
+ }
1195
+ if (choice.renderer === undefined) {
1196
+ continue;
1197
+ }
1198
+ const renderer = UI_RENDERER_CATALOGUE[choice.renderer];
1199
+ if (!renderer) {
1200
+ problems.push(`There is no UI renderer named '${choice.renderer}': one of ${Object.keys(UI_RENDERER_CATALOGUE).sort().join(', ')}.`);
1201
+ }
1202
+ else if (!spec.renderers.includes(renderer.id)) {
1203
+ problems.push(`The UI plugin ${spec.name} ('${id}') is not exposed through ${renderer.name}: ` +
1204
+ (spec.renderers.length > 0
1205
+ ? `through ${spec.renderers.join(', ')} only.`
1206
+ : 'through no renderer.'));
1116
1207
  }
1117
- else if (app.enabled && !spec.enabled) {
1118
- problems.push(`The UI ${spec.name} ('${id}') is not enabled: an application cannot render in it yet.`);
1208
+ else if (app.enabled && !renderer.enabled) {
1209
+ problems.push(`The UI renderer ${renderer.name} ('${renderer.id}') is not enabled: an application cannot be exposed through it yet.`);
1119
1210
  }
1120
1211
  }
1121
1212
  return problems;
@@ -1426,7 +1517,9 @@ export function dumpAppspec(app) {
1426
1517
  .value('as', connection.as, 'owner')
1427
1518
  .list('only', connection.only).data))
1428
1519
  .list('protocols', app.protocols)
1429
- .list('ui', app.ui)
1520
+ .list('ui', app.ui.map(choice => choice.renderer
1521
+ ? { plugin: choice.plugin, renderer: choice.renderer }
1522
+ : { plugin: choice.plugin }))
1430
1523
  .part('pap', dumpPap(app.pap))
1431
1524
  .part('pact', dumpPact(app.pact))
1432
1525
  .part('commerce', dumpCommerce(app.commerce))
@@ -13,12 +13,32 @@
13
13
  *
14
14
  * @module apps/apps/threads
15
15
  */
16
+ import type { ResumedThread } from '../../types/chat';
16
17
  import type { ChatMessage } from '../../types/messages';
17
18
  /** What opening a thread says, in a person's words. */
18
19
  export declare const THREAD_WORDS: {
19
20
  readonly opening: "Opening your conversation…";
20
21
  readonly notOpened: (detail: string) => string;
21
22
  };
23
+ /**
24
+ * The deployment an application's agent is created for, as its create
25
+ * payload names it (`app_instance.deployment_uid`, `appDatalayerCreatePayload`),
26
+ * or `''` for a Preview, a test or no application.
27
+ */
28
+ export declare function deploymentOfPayload(createPayload: Record<string, unknown> | undefined): string;
29
+ /**
30
+ * The thread a deployment's chat opens on when its host gave none: a new one,
31
+ * `id` (PAP_DATALAYER's release gates, found on r1, 14 October 2026).
32
+ *
33
+ * A deployment's agent serves every visitor, and what its runtime holds in
34
+ * memory is the last run's, whoever ran it: drawn as the page's
35
+ * conversation, a second account opening the same link saw the first
36
+ * person's turns. A given thread is drawn as given and never replaced by the
37
+ * runtime's snapshot, so each visitor sees only their own conversation —
38
+ * this one, or one opened from their history. A Preview, or a chat that runs
39
+ * no deployment, keeps what it was given.
40
+ */
41
+ export declare function threadOfVisit(given: ResumedThread | undefined, deploymentUid: string, id: string): ResumedThread | undefined;
22
42
  /** The AG-UI messages a session-API stream's `MESSAGES_SNAPSHOT` carries, if one does. */
23
43
  export declare function snapshotOfStream(text: string): unknown[] | undefined;
24
44
  /**
@@ -8,6 +8,37 @@ export const THREAD_WORDS = {
8
8
  opening: 'Opening your conversation…',
9
9
  notOpened: (detail) => `Your conversation could not be opened, so this is a new one: ${detail}`,
10
10
  };
11
+ /**
12
+ * The deployment an application's agent is created for, as its create
13
+ * payload names it (`app_instance.deployment_uid`, `appDatalayerCreatePayload`),
14
+ * or `''` for a Preview, a test or no application.
15
+ */
16
+ export function deploymentOfPayload(createPayload) {
17
+ const instance = createPayload?.app_instance;
18
+ if (!instance || typeof instance !== 'object') {
19
+ return '';
20
+ }
21
+ const uid = instance.deployment_uid;
22
+ return typeof uid === 'string' ? uid.trim() : '';
23
+ }
24
+ /**
25
+ * The thread a deployment's chat opens on when its host gave none: a new one,
26
+ * `id` (PAP_DATALAYER's release gates, found on r1, 14 October 2026).
27
+ *
28
+ * A deployment's agent serves every visitor, and what its runtime holds in
29
+ * memory is the last run's, whoever ran it: drawn as the page's
30
+ * conversation, a second account opening the same link saw the first
31
+ * person's turns. A given thread is drawn as given and never replaced by the
32
+ * runtime's snapshot, so each visitor sees only their own conversation —
33
+ * this one, or one opened from their history. A Preview, or a chat that runs
34
+ * no deployment, keeps what it was given.
35
+ */
36
+ export function threadOfVisit(given, deploymentUid, id) {
37
+ if (given || !deploymentUid) {
38
+ return given;
39
+ }
40
+ return { id, messages: [] };
41
+ }
11
42
  /** The AG-UI messages a session-API stream's `MESSAGES_SNAPSHOT` carries, if one does. */
12
43
  export function snapshotOfStream(text) {
13
44
  let found;
@@ -1,7 +1,8 @@
1
1
  /**
2
- * The Canvas's palette as a contribution point (LOOP C-12): each UI of
3
- * the catalogue that renders components (`agentspecs/ui`, C-13) is a
4
- * Reactor plugin contributing its components to `loop.canvas.block`. The
2
+ * The Canvas's palette as a Contribution Point (LOOP C-12): each UI plugin
3
+ * of the catalogue that renders components (`agentspecs/ui`, C-13 — the
4
+ * A2UI surfaces, `a2ui-surface`) has its components contributed to
5
+ * `loop.canvas.block` by a Reactor plugin of its own, one per UI spec. The
5
6
  * palette lists what the enabled plugins contribute, and nothing else; a
6
7
  * plugin disabled takes its blocks off it, and an application that uses one
7
8
  * of them says so in its setup notes (`blockSetupNotes`).
@@ -24,10 +25,7 @@ type BlocksReader = {
24
25
  export declare const canvasBlocksPluginName: (ui: string) => string;
25
26
  /** A UI of the catalogue as the Reactor plugin contributing its components as blocks. */
26
27
  export declare function uiPluginBlocks(ui: UISpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
27
- /**
28
- * The plugins of the catalogue's UI specs that render components and may
29
- * be named today (`enabled` in the catalogue).
30
- */
28
+ /** The block plugins of the catalogue's UI plugins that render components. */
31
29
  export declare const CANVAS_BLOCK_PLUGINS: ReactorPlugin<Record<string, never>, unknown, unknown>[];
32
30
  /**
33
31
  * A reactor holding the block plugins given, started, with those named in
@@ -35,8 +33,8 @@ export declare const CANVAS_BLOCK_PLUGINS: ReactorPlugin<Record<string, never>,
35
33
  */
36
34
  export declare function canvasBlocksReactor(disabled?: readonly string[], plugins?: ReactorPlugin<any, any, any>[]): ReactorPlatform;
37
35
  /**
38
- * The palette of an organization: the block plugins of the UI specs it has
39
- * turned off (`plugins_off` in IAM, catalogue ids such as 'a2ui') switched
36
+ * The palette of an organization: the block plugins of the UI plugins it has
37
+ * turned off (`plugins_off` in IAM, ids of `agentspecs/ui` such as 'a2ui-surface') switched
40
38
  * off. A UI of the catalogue that contributes no block takes nothing
41
39
  * off; an id the catalogue does not have is said by `unknownPluginsOff`.
42
40
  */
@@ -3,9 +3,10 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  /**
6
- * The Canvas's palette as a contribution point (LOOP C-12): each UI of
7
- * the catalogue that renders components (`agentspecs/ui`, C-13) is a
8
- * Reactor plugin contributing its components to `loop.canvas.block`. The
6
+ * The Canvas's palette as a Contribution Point (LOOP C-12): each UI plugin
7
+ * of the catalogue that renders components (`agentspecs/ui`, C-13 — the
8
+ * A2UI surfaces, `a2ui-surface`) has its components contributed to
9
+ * `loop.canvas.block` by a Reactor plugin of its own, one per UI spec. The
9
10
  * palette lists what the enabled plugins contribute, and nothing else; a
10
11
  * plugin disabled takes its blocks off it, and an application that uses one
11
12
  * of them says so in its setup notes (`blockSetupNotes`).
@@ -30,12 +31,9 @@ export function uiPluginBlocks(ui) {
30
31
  contributes: ui.components.map(component => contribution(LoopCanvasBlock, { id: component.id, ui: ui.id, component }, { id: component.id })),
31
32
  });
32
33
  }
33
- /**
34
- * The plugins of the catalogue's UI specs that render components and may
35
- * be named today (`enabled` in the catalogue).
36
- */
34
+ /** The block plugins of the catalogue's UI plugins that render components. */
37
35
  export const CANVAS_BLOCK_PLUGINS = listUI()
38
- .filter(ui => ui.enabled && ui.components.length > 0)
36
+ .filter(ui => ui.components.length > 0)
39
37
  .map(uiPluginBlocks);
40
38
  /**
41
39
  * A reactor holding the block plugins given, started, with those named in
@@ -53,8 +51,8 @@ export function canvasBlocksReactor(disabled = [], plugins = CANVAS_BLOCK_PLUGIN
53
51
  return reactor;
54
52
  }
55
53
  /**
56
- * The palette of an organization: the block plugins of the UI specs it has
57
- * turned off (`plugins_off` in IAM, catalogue ids such as 'a2ui') switched
54
+ * The palette of an organization: the block plugins of the UI plugins it has
55
+ * turned off (`plugins_off` in IAM, ids of `agentspecs/ui` such as 'a2ui-surface') switched
58
56
  * off. A UI of the catalogue that contributes no block takes nothing
59
57
  * off; an id the catalogue does not have is said by `unknownPluginsOff`.
60
58
  */
@@ -32,7 +32,10 @@ export declare const TEST_PARTS: CanvasPartContribution[];
32
32
  export declare const EVENT_PARTS: CanvasPartContribution[];
33
33
  /** Each protocol of the catalogue (AGENT_PROTOCOLS PS-04): a planned one offered, not enabled. */
34
34
  export declare const PROTOCOL_PARTS: CanvasPartContribution[];
35
- /** Each UI of the catalogue its answers may render in (AGENT_PROTOCOLS PS-04). */
35
+ /**
36
+ * Each UI plugin of the catalogue (AGENTIC_UI R8): a part of the agent's
37
+ * interface, dropped on it, then exposed through a renderer it may be.
38
+ */
36
39
  export declare const UI_PARTS: CanvasPartContribution[];
37
40
  /** Datalayer's characters for its floating assistant, dropped on the agent (AGENT_PROTOCOLS PS-05). */
38
41
  export declare const CHARACTER_PARTS: CanvasPartContribution[];
@@ -187,14 +187,18 @@ export const PROTOCOL_PARTS = listProtocolSpecs().map(protocol => ({
187
187
  example: protocol.id,
188
188
  enabled: protocol.status === 'shipped',
189
189
  }));
190
- /** Each UI of the catalogue its answers may render in (AGENT_PROTOCOLS PS-04). */
190
+ /**
191
+ * Each UI plugin of the catalogue (AGENTIC_UI R8): a part of the agent's
192
+ * interface, dropped on it, then exposed through a renderer it may be.
193
+ */
191
194
  export const UI_PARTS = listUI().map(ui => ({
192
195
  id: `ui:${ui.id}`,
193
196
  kind: 'ui',
194
197
  label: ui.name,
195
198
  says: ui.description,
196
- example: ui.id,
197
- enabled: ui.enabled,
199
+ // The Appspec's `ui` entry it adds: the plugin, its renderer chosen after.
200
+ example: { plugin: ui.id },
201
+ enabled: true,
198
202
  }));
199
203
  /** Datalayer's characters for its floating assistant, dropped on the agent (AGENT_PROTOCOLS PS-05). */
200
204
  export const CHARACTER_PARTS = ASSISTANT_CHARACTERS.map(character => ({
@@ -48,7 +48,7 @@ import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView
48
48
  import { agentBundleToolDefinitions, useAgentCommandTools, } from '../../../tools/adapters/commands';
49
49
  import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
50
50
  import { generateMessageId } from '../../../types/messages';
51
- import { openThread, THREAD_WORDS } from '../../apps/threads';
51
+ import { deploymentOfPayload, openThread, THREAD_WORDS, threadOfVisit, } from '../../apps/threads';
52
52
  import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
53
53
  import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
54
54
  import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
@@ -1280,9 +1280,10 @@ export default function ChatView({ workspace }) {
1280
1280
  * not published (STUDIO D-15). It speaks to the application's session API,
1281
1281
  * whose refusal says why.
1282
1282
  */
1283
- const runsApp = Boolean(blueprintTurn?.createPayload?.app_spec ||
1284
- reactor.getConfig(AGENTS_PLUGIN_NAME)?.datalayerCreatePayload
1285
- ?.app_spec);
1283
+ const hostCreatePayload = reactor.getConfig(AGENTS_PLUGIN_NAME)?.datalayerCreatePayload;
1284
+ const runsApp = Boolean(blueprintTurn?.createPayload?.app_spec || hostCreatePayload?.app_spec);
1285
+ // The deployment its host runs, if it runs one: its agent is every visitor's.
1286
+ const deploymentUid = deploymentOfPayload(hostCreatePayload);
1286
1287
  const memberToken = useIAMStore(state => state.token);
1287
1288
  /*
1288
1289
  * Without an account (LOOP R-30) the application runs on the visitors'
@@ -1294,13 +1295,28 @@ export default function ChatView({ workspace }) {
1294
1295
  // not know (LOOP R-20): it runs a session of its deployment, and no more.
1295
1296
  const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
1296
1297
  const iamToken = visitors ? visitor.token : embedToken || memberToken;
1298
+ /*
1299
+ * A deployment's runtime, and the visitors', answer only the application's
1300
+ * own routes (OwnRoutesOnly, PAP_DATALAYER's release gates): its chat calls
1301
+ * nothing else — Stop is its session's, no monitoring stream, no sandbox
1302
+ * socket, no runtime-wide model switch. A Preview runs on the Maker's.
1303
+ */
1304
+ const ownRoutesOnly = Boolean(deploymentUid || visitors);
1305
+ if (ownRoutesOnly && !runsApp) {
1306
+ throw new Error('A deployment’s page runs its application: its host gave no app_spec.');
1307
+ }
1297
1308
  /*
1298
1309
  * A thread opened from the person's history (LOOP P-24): its conversation
1299
1310
  * read from the runtime once it answers — held there, else resumed from
1300
1311
  * its record — then drawn and gone on with on the same thread. One that
1301
1312
  * cannot be opened is said, and a new one starts.
1302
1313
  */
1303
- const givenThread = config?.thread;
1314
+ /*
1315
+ * A deployment's visit with no thread given opens a new one: its runtime's
1316
+ * agent holds every visitor's turns, never drawn as this person's.
1317
+ */
1318
+ const [visitThreadId] = useState(generateMessageId);
1319
+ const givenThread = useMemo(() => threadOfVisit(config?.thread, deploymentUid, visitThreadId), [config?.thread, deploymentUid, visitThreadId]);
1304
1320
  const [openedThread, setOpenedThread] = useState(null);
1305
1321
  const opening = Boolean(givenThread?.fromRuntime && openedThread?.given !== givenThread.id);
1306
1322
  useEffect(() => {
@@ -1414,6 +1430,9 @@ export default function ChatView({ workspace }) {
1414
1430
  uiSocket: `${agentServerUrl.replace(/^http/, 'ws')}/api/v1/apps/ui`,
1415
1431
  // Its files, uploaded before what names them (RU-6).
1416
1432
  uiFiles: `${agentServerUrl}/api/v1/apps/agents/${encodeURIComponent(agentId)}/files`,
1433
+ // Each thread a session: Stop is its session's.
1434
+ appSessions: `${agentServerUrl}/api/v1/apps/sessions`,
1435
+ ...(ownRoutesOnly ? { ownRoutesOnly: true } : {}),
1417
1436
  }
1418
1437
  : {}),
1419
1438
  agentId,
@@ -1436,6 +1455,7 @@ export default function ChatView({ workspace }) {
1436
1455
  agentServerUrl,
1437
1456
  runsApp,
1438
1457
  iamToken,
1458
+ ownRoutesOnly,
1439
1459
  ]);
1440
1460
  /*
1441
1461
  * The model catalogue, and the builtin tools and skills the agent reports.
@@ -1550,6 +1570,10 @@ export default function ChatView({ workspace }) {
1550
1570
  * to agree.
1551
1571
  */
1552
1572
  const selectModel = useCallback(async (model) => {
1573
+ if (ownRoutesOnly) {
1574
+ // Runtime-wide, and not the application's own: never offered here.
1575
+ throw new Error('A deployment’s chat does not switch its runtime’s model.');
1576
+ }
1553
1577
  setPickedModel(model);
1554
1578
  if (inPage || agentServerUrl === undefined) {
1555
1579
  return;
@@ -1559,7 +1583,7 @@ export default function ChatView({ workspace }) {
1559
1583
  headers: { 'content-type': 'application/json' },
1560
1584
  body: JSON.stringify({ model }),
1561
1585
  }).catch(() => undefined);
1562
- }, [agentServerUrl, inPage]);
1586
+ }, [agentServerUrl, inPage, ownRoutesOnly]);
1563
1587
  /* `bottom-chat` keeps the prompt in the chat column; `bottom`, the default,
1564
1588
  spans the workspace. */
1565
1589
  const besideChat = config?.promptPlacement === 'bottom-chat';
@@ -1654,7 +1678,8 @@ export default function ChatView({ workspace }) {
1654
1678
  showTokenUsage: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true,
1655
1679
  showContextRing: true,
1656
1680
  agentUsage: contextUsage ?? undefined,
1657
- showModelSelector: !presence,
1681
+ // Nor on a deployment's page: the model is its runtime's, for everyone.
1682
+ showModelSelector: !presence && !ownRoutesOnly,
1658
1683
  models: offeredModels,
1659
1684
  decisions,
1660
1685
  selectedModel: activeModel,
@@ -35,6 +35,7 @@ import { decisionsAskerAt } from './assistant/decisions';
35
35
  import { DecisionAsk } from './assistant/DecisionAsk';
36
36
  import { ServerSpeaker } from '../voice/speaker';
37
37
  import { useSpokenAnswers } from '../voice/useSpokenAnswers';
38
+ import { readAloud } from '../voice/ReadAloud';
38
39
  import { BalloonApprovalMessage, CONVERSATION_BALLOON_WIDTH, ConversationBalloonClose, balloonTailAt, conversationBalloonHeight, conversationBalloonSx, CURRENT_BALLOON_HEIGHT, peekLine, } from './assistant/ConversationBalloon';
39
40
  import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/BalloonParts';
40
41
  import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
@@ -165,7 +166,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
165
166
  const [speakerState, setSpeakerState] = useState({
166
167
  speaking: false,
167
168
  });
168
- const speaker = useMemo(() => voice && voice.output === 'always' && voice.speechUrl
169
+ const speaker = useMemo(() => voice && voice.output !== 'off' && voice.speechUrl
169
170
  ? new ServerSpeaker({
170
171
  speechUrl: voice.speechUrl,
171
172
  voice: voice.voice,
@@ -195,8 +196,15 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
195
196
  window.removeEventListener('keydown', unlock);
196
197
  };
197
198
  }, [speaker]);
198
- useSpokenAnswers(voiceItems, chatBusy, !!speaker && !speechMuted, speaker);
199
+ // `always`: each answer heard as it is written; `on_request`: a *Read
200
+ // aloud* under each (VO-21).
201
+ useSpokenAnswers(voiceItems, chatBusy, !!speaker && voice?.output === 'always' && !speechMuted, speaker);
199
202
  const stopSpeaking = useCallback(() => speaker?.stop(), [speaker]);
203
+ const readAnswerAloud = useCallback((answer) => {
204
+ if (speaker) {
205
+ readAloud(speaker, answer);
206
+ }
207
+ }, [speaker]);
200
208
  const mouthLevel = useMemo(() => (speaker ? () => speaker.level() : undefined), [speaker]);
201
209
  const [arriving, setArriving] = useState(false);
202
210
  const [leaving, setLeaving] = useState(false);
@@ -1057,7 +1065,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1057
1065
  ...poweredByProps,
1058
1066
  }, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
1059
1067
  showAvatars: true,
1060
- }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt ?? suggestionPrompt, inspector: inspector, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
1068
+ }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt ?? suggestionPrompt, inspector: inspector, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, onReadAloud: speaker ? readAnswerAloud : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
1061
1069
  // Current: the one thing said or done now, over the composer.
1062
1070
  _jsx(Box, { px: 3, pt: 3, pb: 2, overflow: "hidden", children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), fullText: toolSaid ? undefined : currentWhole, tool: toolSaid, busy: atWork, speaking: answering && atWork, waiting: atWork && !answering }) })) : (children) }))] }));
1063
1071
  /*
@@ -1109,7 +1117,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1109
1117
  }
1110
1118
  : getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, balloonDisplay: balloonDisplay, expandTarget: expandTarget, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel, suggestions: balloonSuggestions, onSuggestion: sendSuggestion, inspector: inspector, onBalloonDisplayChange: changeBalloonDisplay, onStop: chatBusy && chatControls.current
1111
1119
  ? () => chatControls.current?.stop()
1112
- : undefined, onNewChat: !conversation && showNewChatButton ? handleNewChat : undefined, onClear: !conversation && useStoreMode ? handleClear : undefined, onChangeCharacter: onChangeCharacter, speech: speaker
1120
+ : undefined, onNewChat: !conversation && showNewChatButton ? handleNewChat : undefined, onClear: !conversation && useStoreMode ? handleClear : undefined, onChangeCharacter: onChangeCharacter, speech: speaker && voice?.output === 'always'
1113
1121
  ? {
1114
1122
  muted: speechMuted,
1115
1123
  onToggle: () => {