@datalayer/agent-runtimes 1.5.15 → 1.5.17

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.
@@ -97,9 +97,11 @@ export interface ParsedAppspec {
97
97
  */
98
98
  export declare function parseAppspec(document: unknown): ParsedAppspec;
99
99
  /**
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.
100
+ * What is wrong with the UI plugins an application names (AGENTIC_UI R8), in
101
+ * the sentences agentspecs says (`ui_choices_problems`): a plugin the
102
+ * catalogue does not have or said twice — a renderer named as a plugin is
103
+ * refused —, a renderer the catalogue does not have or the plugin may not be
104
+ * exposed through, and one not enabled while the application is offered.
103
105
  */
104
106
  export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string[];
105
107
  /**
@@ -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';
@@ -90,6 +91,25 @@ const texts = (value) => Array.isArray(value)
90
91
  ? value.filter((item) => typeof item === 'string')
91
92
  : [];
92
93
  const records = (value) => Array.isArray(value) ? value.filter(isData) : [];
94
+ /**
95
+ * An application's UI plugins as its document says them (AGENTIC_UI R8):
96
+ * `{plugin, renderer}` each. A bare id — the old shape, a renderer named as a
97
+ * UI — is read as the plugin it names, which `uiProblems` refuses.
98
+ */
99
+ const uiChoices = (value) => Array.isArray(value)
100
+ ? value.flatMap((item) => typeof item === 'string'
101
+ ? [{ plugin: item }]
102
+ : isData(item)
103
+ ? [
104
+ {
105
+ plugin: text(item.plugin),
106
+ ...(text(item.renderer)
107
+ ? { renderer: text(item.renderer) }
108
+ : {}),
109
+ },
110
+ ]
111
+ : [])
112
+ : [];
93
113
  /**
94
114
  * One turn of a test's conversation as its document says it (STUDIO E-01):
95
115
  * what it says, chooses or presses, with a press's payload; nothing it does
@@ -984,7 +1004,7 @@ export function parseAppspec(document) {
984
1004
  contents: texts(data.contents),
985
1005
  connections: records(data.connections).map(parseConnection),
986
1006
  protocols: texts(data.protocols),
987
- ui: texts(data.ui),
1007
+ ui: uiChoices(data.ui),
988
1008
  rules: records(data.rules).map(parseRule),
989
1009
  permissions: {
990
1010
  spaces: records(permissions.spaces).map(grant => ({
@@ -1097,25 +1117,45 @@ export function parseAppspec(document) {
1097
1117
  return { app, problems };
1098
1118
  }
1099
1119
  /**
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.
1120
+ * What is wrong with the UI plugins an application names (AGENTIC_UI R8), in
1121
+ * the sentences agentspecs says (`ui_choices_problems`): a plugin the
1122
+ * catalogue does not have or said twice — a renderer named as a plugin is
1123
+ * refused —, a renderer the catalogue does not have or the plugin may not be
1124
+ * exposed through, and one not enabled while the application is offered.
1103
1125
  */
1104
1126
  export function uiProblems(app) {
1105
1127
  const problems = [];
1106
1128
  const seen = [];
1107
- for (const id of app.ui) {
1129
+ for (const choice of app.ui) {
1130
+ const id = choice.plugin;
1108
1131
  if (seen.includes(id)) {
1109
- problems.push(`It lists the UI '${id}' twice.`);
1132
+ problems.push(`It lists the UI plugin '${id}' twice.`);
1110
1133
  continue;
1111
1134
  }
1112
1135
  seen.push(id);
1113
1136
  const spec = UI_CATALOGUE[id];
1114
1137
  if (!spec) {
1115
- problems.push(`There is no UI named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.`);
1138
+ problems.push(`There is no UI plugin named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.` +
1139
+ (UI_RENDERER_CATALOGUE[id]
1140
+ ? ` '${id}' is a UI renderer: say it as a plugin's \`renderer\`.`
1141
+ : ''));
1142
+ continue;
1143
+ }
1144
+ if (choice.renderer === undefined) {
1145
+ continue;
1146
+ }
1147
+ const renderer = UI_RENDERER_CATALOGUE[choice.renderer];
1148
+ if (!renderer) {
1149
+ problems.push(`There is no UI renderer named '${choice.renderer}': one of ${Object.keys(UI_RENDERER_CATALOGUE).sort().join(', ')}.`);
1150
+ }
1151
+ else if (!spec.renderers.includes(renderer.id)) {
1152
+ problems.push(`The UI plugin ${spec.name} ('${id}') is not exposed through ${renderer.name}: ` +
1153
+ (spec.renderers.length > 0
1154
+ ? `through ${spec.renderers.join(', ')} only.`
1155
+ : 'through no renderer.'));
1116
1156
  }
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.`);
1157
+ else if (app.enabled && !renderer.enabled) {
1158
+ problems.push(`The UI renderer ${renderer.name} ('${renderer.id}') is not enabled: an application cannot be exposed through it yet.`);
1119
1159
  }
1120
1160
  }
1121
1161
  return problems;
@@ -1426,7 +1466,9 @@ export function dumpAppspec(app) {
1426
1466
  .value('as', connection.as, 'owner')
1427
1467
  .list('only', connection.only).data))
1428
1468
  .list('protocols', app.protocols)
1429
- .list('ui', app.ui)
1469
+ .list('ui', app.ui.map(choice => choice.renderer
1470
+ ? { plugin: choice.plugin, renderer: choice.renderer }
1471
+ : { plugin: choice.plugin }))
1430
1472
  .part('pap', dumpPap(app.pap))
1431
1473
  .part('pact', dumpPact(app.pact))
1432
1474
  .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'
@@ -1300,7 +1301,12 @@ export default function ChatView({ workspace }) {
1300
1301
  * its record — then drawn and gone on with on the same thread. One that
1301
1302
  * cannot be opened is said, and a new one starts.
1302
1303
  */
1303
- const givenThread = config?.thread;
1304
+ /*
1305
+ * A deployment's visit with no thread given opens a new one: its runtime's
1306
+ * agent holds every visitor's turns, never drawn as this person's.
1307
+ */
1308
+ const [visitThreadId] = useState(generateMessageId);
1309
+ const givenThread = useMemo(() => threadOfVisit(config?.thread, deploymentUid, visitThreadId), [config?.thread, deploymentUid, visitThreadId]);
1304
1310
  const [openedThread, setOpenedThread] = useState(null);
1305
1311
  const opening = Boolean(givenThread?.fromRuntime && openedThread?.given !== givenThread.id);
1306
1312
  useEffect(() => {
@@ -32,6 +32,8 @@ import { useCoreStore } from '@datalayer/core';
32
32
  import { DEFAULT_SERVICE_URLS } from '@datalayer/core/lib/api/constants';
33
33
  import { useChatStore } from '../../stores/chatStore';
34
34
  import { useConversationStore } from '../../stores/conversationStore';
35
+ import { askedOf } from '../../apps/apps/records';
36
+ import { extractChatMessagesFromFullContext } from './fullContextMessages';
35
37
  import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/messages';
36
38
  import { AI_MODEL_CATALOGUE, DEFAULT_MODEL, isChatModel, } from '../../specs/models';
37
39
  import { internalQueryClient, isToolCallMessage, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
@@ -315,99 +317,6 @@ function formatToolResultFallback(result) {
315
317
  return 'Tool completed successfully.';
316
318
  }
317
319
  }
318
- function extractChatMessagesFromFullContext(fullContext) {
319
- if (!fullContext) {
320
- return [];
321
- }
322
- const rawMessages = Array.isArray(fullContext.messages)
323
- ? fullContext.messages
324
- : [];
325
- return rawMessages
326
- .map((msg, index) => {
327
- const role = String(msg.role || '').toLowerCase();
328
- const timestampValue = typeof msg.timestamp === 'string' && msg.timestamp.length > 0
329
- ? msg.timestamp
330
- : new Date().toISOString();
331
- const createdAtRaw = new Date(timestampValue);
332
- const createdAt = Number.isNaN(createdAtRaw.getTime())
333
- ? new Date()
334
- : createdAtRaw;
335
- const rawContent = typeof msg.content === 'string'
336
- ? msg.content
337
- : JSON.stringify(msg.content ?? '');
338
- const isToolCall = Boolean(msg.isToolCall);
339
- const isToolResult = Boolean(msg.isToolResult);
340
- const toolCallId = typeof msg.toolCallId === 'string' && msg.toolCallId.length > 0
341
- ? msg.toolCallId
342
- : undefined;
343
- const toolName = typeof msg.toolName === 'string' && msg.toolName.length > 0
344
- ? msg.toolName
345
- : undefined;
346
- // Tool-call turns arrive as assistant messages whose `content` is the
347
- // JSON-encoded tool arguments. Reconstruct a structured `toolCalls`
348
- // entry so it renders as a tool card (matching the live stream) instead
349
- // of leaking raw JSON into the transcript.
350
- if (isToolCall) {
351
- let args = {};
352
- if (rawContent) {
353
- try {
354
- const parsed = JSON.parse(rawContent);
355
- if (parsed &&
356
- typeof parsed === 'object' &&
357
- !Array.isArray(parsed)) {
358
- args = parsed;
359
- }
360
- }
361
- catch {
362
- args = {};
363
- }
364
- }
365
- const resolvedToolCallId = toolCallId || `history-tc-${index}`;
366
- return {
367
- id: `history-toolcall-${index}-${timestampValue}`,
368
- role: 'assistant',
369
- content: '',
370
- createdAt,
371
- toolCalls: [
372
- {
373
- type: 'tool-call',
374
- toolCallId: resolvedToolCallId,
375
- toolName: toolName || 'tool',
376
- args,
377
- status: 'completed',
378
- },
379
- ],
380
- };
381
- }
382
- // Tool results arrive as `role: 'tool'` messages; keep them (with the
383
- // matching toolCallId) so `convertHistoryToDisplayItems` can merge the
384
- // result into its tool card.
385
- if (isToolResult || role === 'tool') {
386
- return {
387
- id: `history-toolresult-${index}-${timestampValue}`,
388
- role: 'tool',
389
- content: rawContent,
390
- createdAt,
391
- metadata: {
392
- toolCallId,
393
- toolName,
394
- },
395
- };
396
- }
397
- // Only hydrate conversational turns in the visible history.
398
- // System messages may contain internal prompts and metadata.
399
- if (role !== 'user' && role !== 'assistant') {
400
- return null;
401
- }
402
- return {
403
- id: `history-${role}-${index}-${timestampValue}`,
404
- role,
405
- content: rawContent,
406
- createdAt,
407
- };
408
- })
409
- .filter((m) => m !== null);
410
- }
411
320
  function parseEnabledMcpToolsByServer(mcpStatusData) {
412
321
  if (!mcpStatusData || typeof mcpStatusData !== 'object') {
413
322
  return null;
@@ -3867,7 +3776,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3867
3776
  typingSuggestions: resolvedSuggestions?.map(item => item.message || item.title), autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, onSend: () => handleSend(), onStop: handleStop, disableInputPrompt: disableInputPrompt || disabled || !!overlay || launching, promptVariant: promptVariant, mentionableAgents: mentionableAgents, promptCommands: promptCommands,
3868
3777
  // The runtime's memory of what was sent to this agent, for the arrow
3869
3778
  // keys — part of the same initial state as the models and tools.
3870
- promptHistory: configQuery.data?.promptHistory, showTokenUsage: showTokenUsage, showContextRing: showContextRing, agentUsage: agentUsage, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, isA2AProtocol: isA2AProtocol, showAgentsMenu: showAgentsMenu, agents: footerAgents, selectedAgentId: selectedAgentId ?? footerAgents[0]?.id, onSelectAgent: onSelectAgent, hasConfigData: !!configQuery.data, hasSkillsData: !!skillsQuery.data,
3779
+ // Without what the runtime told the application before a personal
3780
+ // agent's request (`askedOf`): the person never wrote it.
3781
+ promptHistory: configQuery.data?.promptHistory?.map(askedOf), showTokenUsage: showTokenUsage, showContextRing: showContextRing, agentUsage: agentUsage, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, isA2AProtocol: isA2AProtocol, showAgentsMenu: showAgentsMenu, agents: footerAgents, selectedAgentId: selectedAgentId ?? footerAgents[0]?.id, onSelectAgent: onSelectAgent, hasConfigData: !!configQuery.data, hasSkillsData: !!skillsQuery.data,
3871
3782
  // So the bar can tell "not here yet" from "not coming": a chat whose
3872
3783
  // agent has no config endpoint waits for ever otherwise.
3873
3784
  configLoading: configQuery.isLoading, models: offeredModels,
@@ -0,0 +1,2 @@
1
+ import type { ChatMessage } from '../../types/messages';
2
+ export declare function extractChatMessagesFromFullContext(fullContext: Record<string, unknown> | null): ChatMessage[];
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A chat's conversation as the runtime's agent holds it (`fullContext`, from
7
+ * the `agent.snapshot` it sends), drawn as messages — kept out of `ChatBase`
8
+ * so it can be read and tested without rendering a chat.
9
+ *
10
+ * A shipped application's page opens on this conversation, so a turn a
11
+ * personal agent asked over PAP or PACT is drawn through `askedOf`: the
12
+ * preamble the runtime told the application (`<asked-for>…</asked-for>`) is
13
+ * kept in the record for the builder and never shown to the person.
14
+ */
15
+ import { askedOf } from '../../apps/apps/records';
16
+ export function extractChatMessagesFromFullContext(fullContext) {
17
+ if (!fullContext) {
18
+ return [];
19
+ }
20
+ const rawMessages = Array.isArray(fullContext.messages)
21
+ ? fullContext.messages
22
+ : [];
23
+ return rawMessages
24
+ .map((msg, index) => {
25
+ const role = String(msg.role || '').toLowerCase();
26
+ const timestampValue = typeof msg.timestamp === 'string' && msg.timestamp.length > 0
27
+ ? msg.timestamp
28
+ : new Date().toISOString();
29
+ const createdAtRaw = new Date(timestampValue);
30
+ const createdAt = Number.isNaN(createdAtRaw.getTime())
31
+ ? new Date()
32
+ : createdAtRaw;
33
+ const rawContent = typeof msg.content === 'string'
34
+ ? msg.content
35
+ : JSON.stringify(msg.content ?? '');
36
+ const isToolCall = Boolean(msg.isToolCall);
37
+ const isToolResult = Boolean(msg.isToolResult);
38
+ const toolCallId = typeof msg.toolCallId === 'string' && msg.toolCallId.length > 0
39
+ ? msg.toolCallId
40
+ : undefined;
41
+ const toolName = typeof msg.toolName === 'string' && msg.toolName.length > 0
42
+ ? msg.toolName
43
+ : undefined;
44
+ // Tool-call turns arrive as assistant messages whose `content` is the
45
+ // JSON-encoded tool arguments. Reconstruct a structured `toolCalls`
46
+ // entry so it renders as a tool card (matching the live stream) instead
47
+ // of leaking raw JSON into the transcript.
48
+ if (isToolCall) {
49
+ let args = {};
50
+ if (rawContent) {
51
+ try {
52
+ const parsed = JSON.parse(rawContent);
53
+ if (parsed &&
54
+ typeof parsed === 'object' &&
55
+ !Array.isArray(parsed)) {
56
+ args = parsed;
57
+ }
58
+ }
59
+ catch {
60
+ args = {};
61
+ }
62
+ }
63
+ const resolvedToolCallId = toolCallId || `history-tc-${index}`;
64
+ return {
65
+ id: `history-toolcall-${index}-${timestampValue}`,
66
+ role: 'assistant',
67
+ content: '',
68
+ createdAt,
69
+ toolCalls: [
70
+ {
71
+ type: 'tool-call',
72
+ toolCallId: resolvedToolCallId,
73
+ toolName: toolName || 'tool',
74
+ args,
75
+ status: 'completed',
76
+ },
77
+ ],
78
+ };
79
+ }
80
+ // Tool results arrive as `role: 'tool'` messages; keep them (with the
81
+ // matching toolCallId) so `convertHistoryToDisplayItems` can merge the
82
+ // result into its tool card.
83
+ if (isToolResult || role === 'tool') {
84
+ return {
85
+ id: `history-toolresult-${index}-${timestampValue}`,
86
+ role: 'tool',
87
+ content: rawContent,
88
+ createdAt,
89
+ metadata: {
90
+ toolCallId,
91
+ toolName,
92
+ },
93
+ };
94
+ }
95
+ // Only hydrate conversational turns in the visible history.
96
+ // System messages may contain internal prompts and metadata.
97
+ if (role !== 'user' && role !== 'assistant') {
98
+ return null;
99
+ }
100
+ // A turn a personal agent asked: what the runtime told the application
101
+ // before the person's words is never shown to them (decided 2026-10-14).
102
+ return {
103
+ id: `history-${role}-${index}-${timestampValue}`,
104
+ role,
105
+ content: role === 'user' ? askedOf(rawContent) : rawContent,
106
+ createdAt,
107
+ };
108
+ })
109
+ .filter((m) => m !== null);
110
+ }
@@ -29,7 +29,11 @@ export interface LibraryAgentspec {
29
29
  welcomeDocument?: string | null;
30
30
  sandboxVariant?: string | null;
31
31
  protocol?: string | null;
32
- ui?: string | null;
32
+ /** Its UI plugins, each with the renderer it is exposed through (AGENTIC_UI R8). */
33
+ ui?: Array<{
34
+ plugin: string;
35
+ renderer?: string;
36
+ }> | null;
33
37
  trigger?: Record<string, unknown> | null;
34
38
  modelConfig?: Record<string, unknown> | null;
35
39
  mcpServerTools?: Array<Record<string, unknown>> | null;