@datalayer/agent-runtimes 1.5.14 → 1.5.16

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.
@@ -48,6 +48,19 @@ export declare const RUN_END_WORDS: Record<string, string>;
48
48
  export declare function runEndOf(entry: RecordEntry): string;
49
49
  /** An entry in words: its summary, or how its run ended when its words are not kept. */
50
50
  export declare function entrySaid(entry: RecordEntry): string;
51
+ /**
52
+ * The mark around what the runtime tells an application of a turn a personal
53
+ * agent asked over PAP or PACT, before the request (`asked_for_said`,
54
+ * `ASKED_FOR_TAG` in agent_runtimes.loop.apps.pap_company).
55
+ */
56
+ export declare const ASKED_FOR_TAG = "asked-for";
57
+ /**
58
+ * What a person asked, as they are shown it: without what the runtime told
59
+ * the application before their words (PAP_DATALAYER, decided 2026-10-14).
60
+ * The record keeps it for the application's builder; the person never wrote
61
+ * it, so *Your conversations* and a thread opened again leave it out.
62
+ */
63
+ export declare function askedOf(text: string): string;
51
64
  /** A session in a sentence: how many calls, what stopped it, how it ended. */
52
65
  export declare function sessionSentence(entries: readonly RecordEntry[]): string;
53
66
  /**
@@ -49,6 +49,30 @@ export function runEndOf(entry) {
49
49
  export function entrySaid(entry) {
50
50
  return entry.summary || RUN_END_WORDS[runEndOf(entry)] || '';
51
51
  }
52
+ /**
53
+ * The mark around what the runtime tells an application of a turn a personal
54
+ * agent asked over PAP or PACT, before the request (`asked_for_said`,
55
+ * `ASKED_FOR_TAG` in agent_runtimes.loop.apps.pap_company).
56
+ */
57
+ export const ASKED_FOR_TAG = 'asked-for';
58
+ /**
59
+ * What a person asked, as they are shown it: without what the runtime told
60
+ * the application before their words (PAP_DATALAYER, decided 2026-10-14).
61
+ * The record keeps it for the application's builder; the person never wrote
62
+ * it, so *Your conversations* and a thread opened again leave it out.
63
+ */
64
+ export function askedOf(text) {
65
+ const open = `<${ASKED_FOR_TAG}>`;
66
+ const close = `</${ASKED_FOR_TAG}>`;
67
+ if (!text.startsWith(open)) {
68
+ return text;
69
+ }
70
+ const end = text.indexOf(close);
71
+ if (end < 0) {
72
+ throw new Error(`What was asked opens <${ASKED_FOR_TAG}> and never closes it.`);
73
+ }
74
+ return text.slice(end + close.length).trimStart();
75
+ }
52
76
  /** A session in a sentence: how many calls, what stopped it, how it ended. */
53
77
  export function sessionSentence(entries) {
54
78
  const calls = entries.filter(entry => entry.kind === 'tool_call').length;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { windowTurnErrorOf } from '../../chat/base/loopWindow';
6
6
  import { AGUI_NAME } from '@datalayer/reactor/ui';
7
+ import { askedOf } from '../apps/records';
7
8
  /** Where a visit's session is kept in the host's storage: `<prefix>:<app>:<visit>`. */
8
9
  export const EMBED_SESSION_KEY_PREFIX = 'datalayer-app:session';
9
10
  /**
@@ -115,7 +116,14 @@ export function messagesOfThread(messages) {
115
116
  typeof id === 'string' &&
116
117
  typeof content === 'string' &&
117
118
  content.trim()) {
118
- drawn.push({ id, role, content, createdAt: new Date() });
119
+ // A turn a personal agent asked: what the runtime told the application
120
+ // before the person's words is never shown to them (decided 2026-10-14).
121
+ drawn.push({
122
+ id,
123
+ role,
124
+ content: role === 'user' ? askedOf(content) : content,
125
+ createdAt: new Date(),
126
+ });
119
127
  }
120
128
  }
121
129
  return drawn;
@@ -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
+ }
package/lib/specs/apps.js CHANGED
@@ -1779,12 +1779,14 @@ export const EXCHANGE_DESK_APP_0_0_2 = {
1779
1779
  },
1780
1780
  ],
1781
1781
  verified: {
1782
- live: [],
1782
+ live: [
1783
+ "Drilled on r1 on 14 October 2026 (runtime image 0.0.34): shipped at its own domain, asked by a person's own agent signed in through Datalayer with orders:read and orders:write, asked to exchange order ord_7Hk2 for size L, it proposed the exchange (Exchange order ord_7Hk2 for size L., awaiting the person's approval) and went on to tell them in its own words that nothing was done yet.",
1784
+ ],
1783
1785
  recorded: [
1784
1786
  "Its exchange_order runs in process in Datalayer's own tests against a stand-in for Manzanita's back office: one exchange per confirmed operation, a retry answered as already done.",
1785
1787
  ],
1786
1788
  unverified: [
1787
- "Drilled on r1 on 13 October 2026, twice: shipped at its own domain, it answered a personal agent signed out, and a person signed in through Datalayer granted orders:read and orders:write. The second time it knew who had signed in, but asked to exchange, it looked for the order first and proposed nothing; it now proposes the exchange at once, and Manzanita checks the order when it is done. Drilled a third time the same evening: signed in, asked to exchange, it said it could not — asked by a person's own agent, it was not given its exchange_order (fixed). Drilled a fourth time the same night, given it: proposing the exchange ended its answer, and the personal agent was told it could not answer just now (fixed, not yet released).",
1789
+ "Drilled on r1 on 13 October 2026, twice: shipped at its own domain, it answered a personal agent signed out, and a person signed in through Datalayer granted orders:read and orders:write. The second time it knew who had signed in, but asked to exchange, it looked for the order first and proposed nothing; it now proposes the exchange at once, and Manzanita checks the order when it is done. Drilled a third time the same evening: signed in, asked to exchange, it said it could not — asked by a person's own agent, it was not given its exchange_order (fixed). Drilled a fourth time the same night, given it: proposing the exchange ended its answer, and the personal agent was told it could not answer just now (fixed, released in runtime image 0.0.34).",
1788
1790
  ],
1789
1791
  },
1790
1792
  },
@@ -1814,8 +1816,8 @@ export const EXCHANGE_DESK_APP_0_0_2 = {
1814
1816
  documents: [],
1815
1817
  alternatives: [],
1816
1818
  },
1817
- enabled: false,
1818
- unavailable_because: "Asked by a person's own agent, proposing an exchange ends its answer until a fix is released.",
1819
+ enabled: true,
1820
+ unavailable_because: '',
1819
1821
  tags: ['example', 'companies', 'python'],
1820
1822
  icon: 'apps',
1821
1823
  emoji: '🏪',
@@ -7029,11 +7031,14 @@ export const APP_SOURCES = {
7029
7031
  },
7030
7032
  ],
7031
7033
  verified: {
7034
+ live: [
7035
+ "Drilled on r1 on 14 October 2026 (runtime image 0.0.34): shipped at its own domain, asked by a person's own agent signed in through Datalayer with orders:read and orders:write, asked to exchange order ord_7Hk2 for size L, it proposed the exchange (Exchange order ord_7Hk2 for size L., awaiting the person's approval) and went on to tell them in its own words that nothing was done yet.",
7036
+ ],
7032
7037
  recorded: [
7033
7038
  "Its exchange_order runs in process in Datalayer's own tests against a stand-in for Manzanita's back office: one exchange per confirmed operation, a retry answered as already done.",
7034
7039
  ],
7035
7040
  unverified: [
7036
- "Drilled on r1 on 13 October 2026, twice: shipped at its own domain, it answered a personal agent signed out, and a person signed in through Datalayer granted orders:read and orders:write. The second time it knew who had signed in, but asked to exchange, it looked for the order first and proposed nothing; it now proposes the exchange at once, and Manzanita checks the order when it is done. Drilled a third time the same evening: signed in, asked to exchange, it said it could not — asked by a person's own agent, it was not given its exchange_order (fixed). Drilled a fourth time the same night, given it: proposing the exchange ended its answer, and the personal agent was told it could not answer just now (fixed, not yet released).",
7041
+ "Drilled on r1 on 13 October 2026, twice: shipped at its own domain, it answered a personal agent signed out, and a person signed in through Datalayer granted orders:read and orders:write. The second time it knew who had signed in, but asked to exchange, it looked for the order first and proposed nothing; it now proposes the exchange at once, and Manzanita checks the order when it is done. Drilled a third time the same evening: signed in, asked to exchange, it said it could not — asked by a person's own agent, it was not given its exchange_order (fixed). Drilled a fourth time the same night, given it: proposing the exchange ended its answer, and the personal agent was told it could not answer just now (fixed, released in runtime image 0.0.34).",
7037
7042
  ],
7038
7043
  },
7039
7044
  },
@@ -7044,8 +7049,6 @@ export const APP_SOURCES = {
7044
7049
  deployment: {
7045
7050
  hosted: {},
7046
7051
  },
7047
- enabled: false,
7048
- unavailable_because: "Asked by a person's own agent, proposing an exchange ends its answer until a fix is released.",
7049
7052
  tags: ['example', 'companies', 'python'],
7050
7053
  emoji: '🏪',
7051
7054
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.14",
3
+ "version": "1.5.16",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",