@datalayer/agent-runtimes 1.3.40 → 1.3.43

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 (35) hide show
  1. package/lib/api/runtimes/runtimes.d.ts +21 -0
  2. package/lib/api/runtimes/runtimes.js +45 -0
  3. package/lib/chat/ChatFloating.d.ts +30 -1
  4. package/lib/chat/ChatFloating.js +60 -14
  5. package/lib/chat/presence/Presence.d.ts +9 -0
  6. package/lib/chat/presence/Presence.js +11 -2
  7. package/lib/client/AgentRuntimesClient.d.ts +4 -0
  8. package/lib/client/mixins/RuntimesMixin.d.ts +9 -0
  9. package/lib/client/mixins/RuntimesMixin.js +19 -0
  10. package/lib/loop/apps/AppRenderer.d.ts +30 -6
  11. package/lib/loop/apps/AppRenderer.js +29 -8
  12. package/lib/loop/apps/index.d.ts +1 -0
  13. package/lib/loop/apps/index.js +1 -0
  14. package/lib/loop/apps/records.d.ts +42 -0
  15. package/lib/loop/apps/records.js +81 -0
  16. package/lib/loop/apps/rules.d.ts +13 -0
  17. package/lib/loop/apps/rules.js +36 -0
  18. package/lib/loop/embed/AppEmbed.d.ts +2 -0
  19. package/lib/loop/embed/AppEmbed.js +47 -119
  20. package/lib/loop/embed/LoopEmbed.js +1 -0
  21. package/lib/loop/index.d.ts +2 -0
  22. package/lib/loop/index.js +2 -0
  23. package/lib/loop/plugins/app-activity/AppActivity.d.ts +21 -0
  24. package/lib/loop/plugins/app-activity/AppActivity.js +63 -0
  25. package/lib/loop/plugins/app-activity/index.d.ts +11 -0
  26. package/lib/loop/plugins/app-activity/index.js +35 -0
  27. package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +40 -0
  28. package/lib/loop/plugins/app-rules/AppRulesCard.js +50 -0
  29. package/lib/loop/plugins/app-rules/index.d.ts +7 -0
  30. package/lib/loop/plugins/app-rules/index.js +31 -0
  31. package/lib/loop/plugins/chat/ChatView.js +23 -3
  32. package/lib/loop/plugins/chat/index.d.ts +23 -0
  33. package/lib/loop/presets.d.ts +2 -0
  34. package/lib/loop/presets.js +2 -1
  35. package/package.json +2 -2
@@ -177,6 +177,27 @@ export declare const listRuntimeMemories: (token: string, options?: ListRuntimeM
177
177
  * Fetch one persisted memory by id from the runtimes API.
178
178
  */
179
179
  export declare const getRuntimeMemory: (token: string, memoryId: string, baseUrl?: string) => Promise<GetRuntimeMemoryResponse>;
180
+ /**
181
+ * What forgetting answers: how many memories were forgotten.
182
+ */
183
+ export interface ForgetRuntimeMemoriesResponse {
184
+ success: boolean;
185
+ forgotten: number;
186
+ }
187
+ /**
188
+ * Forget one of the caller's own persisted memories (LOOP R-18).
189
+ *
190
+ * Somebody else's is not found (404), whoever asks: forgetting is the
191
+ * person's own.
192
+ */
193
+ export declare const forgetRuntimeMemory: (token: string, memoryId: string, baseUrl?: string) => Promise<ForgetRuntimeMemoriesResponse>;
194
+ /**
195
+ * Forget everything one agent — an application, by its key `app:<uid>` —
196
+ * remembers of the caller, no more than `count`: the number the person was
197
+ * shown and confirmed. When it remembers more now, nothing is forgotten and
198
+ * the service refuses (409) in a sentence (LOOP R-18).
199
+ */
200
+ export declare const forgetRuntimeMemories: (token: string, agentId: string, count: number, baseUrl?: string) => Promise<ForgetRuntimeMemoriesResponse>;
180
201
  /**
181
202
  * Resume a paused runtime by restoring from a checkpoint (async).
182
203
  *
@@ -258,6 +258,51 @@ export const getRuntimeMemory = async (token, memoryId, baseUrl = DEFAULT_SERVIC
258
258
  memory: response?.memory,
259
259
  };
260
260
  };
261
+ /**
262
+ * Forget one of the caller's own persisted memories (LOOP R-18).
263
+ *
264
+ * Somebody else's is not found (404), whoever asks: forgetting is the
265
+ * person's own.
266
+ */
267
+ export const forgetRuntimeMemory = async (token, memoryId, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
268
+ validateToken(token);
269
+ validateRequiredString(memoryId, 'Memory id');
270
+ const response = await requestDatalayerAPI({
271
+ url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/memories/${encodeURIComponent(memoryId)}`,
272
+ method: 'DELETE',
273
+ token,
274
+ });
275
+ return {
276
+ success: Boolean(response?.success),
277
+ forgotten: Number(response?.forgotten ?? 0),
278
+ };
279
+ };
280
+ /**
281
+ * Forget everything one agent — an application, by its key `app:<uid>` —
282
+ * remembers of the caller, no more than `count`: the number the person was
283
+ * shown and confirmed. When it remembers more now, nothing is forgotten and
284
+ * the service refuses (409) in a sentence (LOOP R-18).
285
+ */
286
+ export const forgetRuntimeMemories = async (token, agentId, count, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
287
+ validateToken(token);
288
+ validateRequiredString(agentId, 'Agent id');
289
+ if (!Number.isInteger(count) || count < 0) {
290
+ throw new Error(`Forgetting needs the count confirmed, not ${count}.`);
291
+ }
292
+ const query = new URLSearchParams({
293
+ agent_id: agentId.trim(),
294
+ count: String(count),
295
+ });
296
+ const response = await requestDatalayerAPI({
297
+ url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/memories?${query.toString()}`,
298
+ method: 'DELETE',
299
+ token,
300
+ });
301
+ return {
302
+ success: Boolean(response?.success),
303
+ forgotten: Number(response?.forgotten ?? 0),
304
+ };
305
+ };
261
306
  /**
262
307
  * Resume a paused runtime by restoring from a checkpoint (async).
263
308
  *
@@ -8,13 +8,36 @@
8
8
  * 1. AG-UI mode: When `endpoint` is provided
9
9
  * 2. Store mode: When `useStore` is true
10
10
  * 3. Any protocol supported by ChatBase (AG-UI, A2A, ACP, Vercel AI)
11
+ * 4. A conversation its host draws (`conversation`): the LOOP workspace,
12
+ * which the embed's bubble, panel and assistant hold (LOOP R-01) — the
13
+ * floating chrome around it, told by the host what it is doing and what
14
+ * it last said
11
15
  *
12
16
  * @module chat/ChatFloating
13
17
  */
14
18
  import React from 'react';
15
19
  import { type AssistantCharacter } from './assistant/characters';
16
20
  import type { AssistantCharacterData } from './assistant/formats/types';
21
+ import { type AssistantSaying } from './assistant/state';
22
+ import { type PresenceState } from './presence/presenceStatus';
17
23
  import type { ChatCommonProps, ChatViewMode, ThemeOverrides } from '../types';
24
+ /**
25
+ * A conversation the host draws in the floating window, in place of the
26
+ * chat: the LOOP workspace (LOOP R-01). The window keeps its chrome — the
27
+ * button, its blink and its balloon, the panel, the assistant, dragging and
28
+ * dismissing — and is told by the host what the conversation is doing and
29
+ * what it last said, which it would otherwise read from its own chat.
30
+ */
31
+ export type FloatingConversation = {
32
+ /** What the window holds, under its header (the face, the title, close). */
33
+ body: React.ReactNode;
34
+ /** What it is doing: idle, thinking, working, waiting for the person. */
35
+ presence: PresenceState;
36
+ /** Its newest words, said in the balloon until heard. */
37
+ saying?: AssistantSaying;
38
+ /** Whether its newest item is the answer being written. */
39
+ answering: boolean;
40
+ };
18
41
  /**
19
42
  * ChatFloating props — extends ChatCommonProps with floating/popup-specific configuration.
20
43
  */
@@ -110,10 +133,16 @@ export interface ChatFloatingProps extends ChatCommonProps {
110
133
  * @default false
111
134
  */
112
135
  showPanelBackdrop?: boolean;
136
+ /**
137
+ * A conversation the host draws in the window instead of the chat — the
138
+ * LOOP workspace, as the embed's floating modes do (LOOP R-01). The chat's
139
+ * own props (`protocol`, `suggestions`, …) are then not read.
140
+ */
141
+ conversation?: FloatingConversation;
113
142
  }
114
143
  /**
115
144
  * ChatFloating component
116
145
  * A floating chat window built on ChatBase
117
146
  */
118
- export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, }: ChatFloatingProps): React.JSX.Element;
147
+ export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, assistantCharacter, conversation, }: ChatFloatingProps): React.JSX.Element;
119
148
  export default ChatFloating;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /*
3
3
  * Copyright (c) 2025-2026 Datalayer, Inc.
4
4
  * Distributed under the terms of the Modified BSD License.
@@ -13,6 +13,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
13
13
  * 1. AG-UI mode: When `endpoint` is provided
14
14
  * 2. Store mode: When `useStore` is true
15
15
  * 3. Any protocol supported by ChatBase (AG-UI, A2A, ACP, Vercel AI)
16
+ * 4. A conversation its host draws (`conversation`): the LOOP workspace,
17
+ * which the embed's bubble, panel and assistant hold (LOOP R-01) — the
18
+ * floating chrome around it, told by the host what it is doing and what
19
+ * it last said
16
20
  *
17
21
  * @module chat/ChatFloating
18
22
  */
@@ -59,7 +63,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
59
63
  * ChatFloating component
60
64
  * A floating chat window built on ChatBase
61
65
  */
62
- export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, }) {
66
+ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, conversation, }) {
63
67
  // Store-based state
64
68
  const storeIsOpen = useChatOpen();
65
69
  const storeMessages = useChatMessages();
@@ -121,7 +125,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
121
125
  open: false,
122
126
  pendingApproval: false,
123
127
  });
124
- const [answering, setAnswering] = useState(false);
128
+ const [ownAnswering, setAnswering] = useState(false);
125
129
  const [arriving, setArriving] = useState(false);
126
130
  const [leaving, setLeaving] = useState(false);
127
131
  const [assistantAway, setAssistantAway] = useState(keptAway);
@@ -130,8 +134,13 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
130
134
  keepAway('none');
131
135
  setAssistantAway('none');
132
136
  }, []);
133
- /* The agent's newest words, said in the balloon until heard (T-23). */
134
- const [saying, setSaying] = useState();
137
+ /*
138
+ * The agent's newest words, said in the balloon until heard (T-23): the
139
+ * chat's own, or what the host's conversation says it said.
140
+ */
141
+ const [ownSaying, setSaying] = useState();
142
+ const saying = conversation ? conversation.saying : ownSaying;
143
+ const answering = conversation ? conversation.answering : ownAnswering;
135
144
  const [heardId, setHeardId] = useState();
136
145
  const [freshSaying, setFreshSaying] = useState(false);
137
146
  useEffect(() => {
@@ -146,7 +155,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
146
155
  if (isOpen && saying) {
147
156
  setHeardId(saying.id);
148
157
  }
149
- }, [isOpen, saying]);
158
+ }, [isOpen, saying?.id]);
150
159
  const stageRef = useRef(null);
151
160
  const stageDrag = useViewportDrag(stageRef, { whole: true });
152
161
  useEffect(() => {
@@ -157,7 +166,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
157
166
  const timer = setTimeout(() => setArriving(false), 1800);
158
167
  return () => clearTimeout(timer);
159
168
  }, [assistantShown]);
160
- const assistantState = assistantStateOf(presenceState(chatBusy, chatTool), {
169
+ const assistantState = assistantStateOf(conversation ? conversation.presence : presenceState(chatBusy, chatTool), {
161
170
  arriving,
162
171
  leaving,
163
172
  speaking: answering,
@@ -271,11 +280,16 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
271
280
  const measure = () => {
272
281
  const rect = host.getBoundingClientRect();
273
282
  setPanelHostRect(prev => {
274
- const next = {
275
- top: rect.top,
276
- height: rect.height,
277
- right: Math.max(0, window.innerWidth - rect.right),
278
- };
283
+ // A host that holds nothing of the page — an embed's own element,
284
+ // everything in it fixed to the viewport — has no height to align
285
+ // to: the panel is then the viewport's right edge, at full height.
286
+ const next = rect.height < 1
287
+ ? { top: 0, height: window.innerHeight, right: 0 }
288
+ : {
289
+ top: rect.top,
290
+ height: rect.height,
291
+ right: Math.max(0, window.innerWidth - rect.right),
292
+ };
279
293
  if (prev &&
280
294
  Math.abs(prev.top - next.top) < 1 &&
281
295
  Math.abs(prev.height - next.height) < 1 &&
@@ -459,6 +473,22 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
459
473
  document.addEventListener('keydown', handleKeyDown);
460
474
  return () => document.removeEventListener('keydown', handleKeyDown);
461
475
  }, [escapeToClose, isOpen, handleToggle]);
476
+ /*
477
+ * A host's conversation takes the caret when the window opens, as the
478
+ * chat's own does (`autoFocus`): its composer, once the window shows.
479
+ */
480
+ const hostsConversation = Boolean(conversation);
481
+ useEffect(() => {
482
+ if (!hostsConversation || !isOpen) {
483
+ return undefined;
484
+ }
485
+ const timer = setTimeout(() => {
486
+ popupRef.current
487
+ ?.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"], textarea')
488
+ ?.focus();
489
+ }, animationDuration);
490
+ return () => clearTimeout(timer);
491
+ }, [hostsConversation, isOpen, animationDuration, focusTrigger]);
462
492
  // Click outside to close
463
493
  useEffect(() => {
464
494
  if (!clickOutsideToClose || !isOpen)
@@ -698,7 +728,23 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
698
728
  borderColor: 'border.muted',
699
729
  touchAction: 'none',
700
730
  '&:active': { cursor: 'grabbing' },
701
- }, children: _jsx(GrabberIcon, { size: 16 }) })), _jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, themeVariant: themeVariant, themeOverrides: themeOverrides, colorMode: colorMode, autoFocus: isOpen, focusTrigger: focusTrigger, launching: launching, launchingMessage: launchingMessage, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
731
+ }, children: _jsx(GrabberIcon, { size: 16 }) })), conversation ? (_jsxs(_Fragment, { children: [showHeader ? (_jsxs(Box, { sx: {
732
+ display: 'flex',
733
+ alignItems: 'center',
734
+ gap: 2,
735
+ flexShrink: 0,
736
+ px: 3,
737
+ py: 2,
738
+ borderBottom: '1px solid',
739
+ borderColor: 'border.default',
740
+ }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), _jsx(Text, { sx: {
741
+ flex: 1,
742
+ minWidth: 0,
743
+ fontWeight: 'semibold',
744
+ overflow: 'hidden',
745
+ textOverflow: 'ellipsis',
746
+ whiteSpace: 'nowrap',
747
+ }, children: title }), closeButton] })) : null, _jsx(Box, { "data-floating-conversation": true, sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: conversation.body })] })) : (_jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, themeVariant: themeVariant, themeOverrides: themeOverrides, colorMode: colorMode, autoFocus: isOpen, focusTrigger: focusTrigger, launching: launching, launchingMessage: launchingMessage, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
702
748
  showNewChat: showNewChatButton,
703
749
  showClear: showClearButton && messages.length > 0,
704
750
  showSettings: showSettingsButton && !!onSettingsClick,
@@ -714,7 +760,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
714
760
  ...poweredByProps,
715
761
  }, renderToolResult: renderToolResult, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
716
762
  showAvatars: true,
717
- }, 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, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children })] }));
763
+ }, 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, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, ...panelProps, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: children }))] }));
718
764
  /*
719
765
  * The floating popup's button. Words not yet heard make it blink for the
720
766
  * person's attention, until the chat is opened; hovered, they show in a
@@ -9,6 +9,15 @@
9
9
  */
10
10
  import type { ReactNode } from 'react';
11
11
  import { type PresenceState } from './presenceStatus';
12
+ /**
13
+ * A face at `size`: an emoji drawn in Fluent Emoji, the same on every
14
+ * platform (LOOP T-20) — the system's, as text, only for one Datalayer ships
15
+ * no drawing of — or a host's own drawing, left as it is.
16
+ */
17
+ export declare function FaceDrawing({ face, size }: {
18
+ face: ReactNode;
19
+ size: number;
20
+ }): import("react").JSX.Element;
12
21
  export declare function PresenceFace({ face, size, state, }: {
13
22
  /**
14
23
  * The face: an emoji, drawn at `size`, or a host's own drawing of it — an
@@ -1,7 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from '@primer/react';
3
+ import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
3
4
  import { PRESENCE_LINES } from './presenceStatus';
4
5
  const ACCENT = 'var(--loop-accent, var(--fgColor-accent))';
6
+ /**
7
+ * A face at `size`: an emoji drawn in Fluent Emoji, the same on every
8
+ * platform (LOOP T-20) — the system's, as text, only for one Datalayer ships
9
+ * no drawing of — or a host's own drawing, left as it is.
10
+ */
11
+ export function FaceDrawing({ face, size }) {
12
+ return typeof face === 'string' ? (_jsx(FluentEmoji, { emoji: face, size: size, label: "" })) : (_jsx(_Fragment, { children: face }));
13
+ }
5
14
  export function PresenceFace({ face, size, state, }) {
6
15
  const active = state === 'thinking' || state === 'working';
7
16
  return (_jsxs(Box, { as: "span", "aria-hidden": true, "data-presence": state, sx: {
@@ -31,7 +40,7 @@ export function PresenceFace({ face, size, state, }) {
31
40
  '@media (prefers-reduced-motion: reduce)': {
32
41
  animation: 'none',
33
42
  },
34
- } }), face] }));
43
+ } }), _jsx(FaceDrawing, { face: face, size: size })] }));
35
44
  }
36
45
  export function PresenceLine({ state }) {
37
46
  return (_jsx(Text, { role: "status", "aria-live": "polite", "data-presence-line": state, sx: {
@@ -58,6 +58,8 @@ declare const AgentRuntimesClientWithMixins: {
58
58
  deleteRuntime(runtimeName: string): Promise<void>;
59
59
  listRuntimeMemories(options?: ListRuntimeMemoriesOptions): Promise<RuntimeMemory[]>;
60
60
  getRuntimeMemory(memoryId: string): Promise<RuntimeMemory>;
61
+ forgetRuntimeMemory(memoryId: string): Promise<number>;
62
+ forgetRuntimeMemories(agentId: string, count: number): Promise<number>;
61
63
  terminateAllRuntimes(): Promise<PromiseSettledResult<void>[]>;
62
64
  createSnapshot(runtimeName: string, name: string, description: string, stop?: boolean): Promise<CodeSandboxSnapshotDTO>;
63
65
  listSnapshots(): Promise<CodeSandboxSnapshotDTO[]>;
@@ -92,6 +94,8 @@ export interface AgentRuntimesClient extends DatalayerCoreClient {
92
94
  listRuntimes(): Promise<RuntimeDTO[]>;
93
95
  listRuntimeMemories(options?: ListRuntimeMemoriesOptions): Promise<RuntimeMemory[]>;
94
96
  getRuntimeMemory(memoryId: string): Promise<RuntimeMemory>;
97
+ forgetRuntimeMemory(memoryId: string): Promise<number>;
98
+ forgetRuntimeMemories(agentId: string, count: number): Promise<number>;
95
99
  getRuntime(runtimeName: string): Promise<RuntimeDTO>;
96
100
  deleteRuntime(runtimeName: string): Promise<void>;
97
101
  terminateAllRuntimes(): Promise<PromiseSettledResult<void>[]>;
@@ -92,6 +92,15 @@ export declare function RuntimesMixin<TBase extends Constructor>(Base: TBase): {
92
92
  * Fetch one persisted memory by id.
93
93
  */
94
94
  getRuntimeMemory(memoryId: string): Promise<RuntimeMemory>;
95
+ /**
96
+ * Forget one of the caller's own memories; answers how many were forgotten.
97
+ */
98
+ forgetRuntimeMemory(memoryId: string): Promise<number>;
99
+ /**
100
+ * Forget everything an agent or application (`app:<uid>`) remembers of
101
+ * the caller, no more than the `count` confirmed.
102
+ */
103
+ forgetRuntimeMemories(agentId: string, count: number): Promise<number>;
95
104
  /**
96
105
  * Terminate all runtimes.
97
106
  * Lists all runtimes and deletes them in parallel.
@@ -169,6 +169,25 @@ export function RuntimesMixin(Base) {
169
169
  const response = await runtimes.getRuntimeMemory(token, memoryId, runtimesUrl);
170
170
  return response.memory;
171
171
  }
172
+ /**
173
+ * Forget one of the caller's own memories; answers how many were forgotten.
174
+ */
175
+ async forgetRuntimeMemory(memoryId) {
176
+ const token = this.getToken();
177
+ const runtimesUrl = this.getRuntimesUrl();
178
+ const response = await runtimes.forgetRuntimeMemory(token, memoryId, runtimesUrl);
179
+ return response.forgotten;
180
+ }
181
+ /**
182
+ * Forget everything an agent or application (`app:<uid>`) remembers of
183
+ * the caller, no more than the `count` confirmed.
184
+ */
185
+ async forgetRuntimeMemories(agentId, count) {
186
+ const token = this.getToken();
187
+ const runtimesUrl = this.getRuntimesUrl();
188
+ const response = await runtimes.forgetRuntimeMemories(token, agentId, count, runtimesUrl);
189
+ return response.forgotten;
190
+ }
172
191
  /**
173
192
  * Terminate all runtimes.
174
193
  * Lists all runtimes and deletes them in parallel.
@@ -27,7 +27,11 @@
27
27
  * page its host draws (`page`, R-02) — the Studio's run page, at its
28
28
  * address and embedded;
29
29
  * - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
30
- * window, its title the application's face and name.
30
+ * window, its title the application's face and name;
31
+ * - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
32
+ * the approvals waiting for the person (`app-rules`), and what it did
33
+ * (`app-activity`), beside its page — what its builder reads in the
34
+ * Studio's Preview.
31
35
  *
32
36
  * Which plugins a kind needs, and how its workspace is laid out, is one
33
37
  * function: `appPreset`, the preset per kind, as `loopPlugins` is for the
@@ -42,6 +46,7 @@ import type { ThemeOverrides } from '../../types/chat';
42
46
  import { type AppHostPageProps } from '../plugins/app-page';
43
47
  import { type LoopEmbedProps } from '../embed/LoopEmbed';
44
48
  import type { LoopPresetOptions } from '../presets';
49
+ import type { ChatSaid } from '../plugins/chat';
45
50
  import type { PresenceState } from '../../chat/presence/presenceStatus';
46
51
  /** The id of an agent or a Cog, without its version. */
47
52
  export declare const agentIdOf: (app: Pick<AppSpec, "agent" | "team">) => string;
@@ -78,6 +83,20 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
78
83
  * stable function, such as a state setter.
79
84
  */
80
85
  onPresence?: (state: PresenceState) => void;
86
+ /**
87
+ * Told what the application last said, and whether its newest words are
88
+ * an answer — for a host that says them outside the conversation: the
89
+ * embed's floating chrome, its balloon and its blink (R-01). A stable
90
+ * function, such as a state setter.
91
+ */
92
+ onSaying?: (said: ChatSaid) => void;
93
+ /**
94
+ * Draw its rules and the approvals waiting for the person, and its
95
+ * activity, beside its page (R-01b): what its builder reads in the
96
+ * Studio's Preview. The activity is read from its record, kept under
97
+ * `instance.appUid`.
98
+ */
99
+ sidebar?: boolean;
81
100
  /**
82
101
  * Draw the application in a window (the `window-frame` plugin), its title
83
102
  * the application's face and name. A host's own `frameTitle` wins.
@@ -127,8 +146,9 @@ export type AppPreset = {
127
146
  * its page (`app-page`) when it has one, with the Canvas's block plugins
128
147
  * but those of the UI plugins its organization turned off (`pluginsOff`),
129
148
  * whose contributions are the blocks it may draw (R-01b); a thumb and a
130
- * comment on each answer when its record keeps feedback (V-18); laid out
131
- * as `interface.layout` says;
149
+ * comment on each answer when its record keeps feedback (V-18); with
150
+ * `sidebar`, its rules and approvals card and its activity feed (R-01b);
151
+ * laid out as `interface.layout` says;
132
152
  * - a **decision**: the page its host draws (`page`), as the one view of a
133
153
  * workspace without a conversation (R-02).
134
154
  *
@@ -138,14 +158,18 @@ export type AppPreset = {
138
158
  export declare function appPreset(app: AppSpec, options?: {
139
159
  page?: ComponentType<AppHostPageProps>;
140
160
  pluginsOff?: readonly string[];
161
+ /** Its rules and approvals, and its activity, beside its page. */
162
+ sidebar?: boolean;
163
+ /** What its record is kept under: its activity's. */
164
+ appUid?: string;
141
165
  }): AppPreset;
142
166
  /**
143
167
  * What an application's agent is created with on a Datalayer runtime: under
144
168
  * the application's id, over AG-UI, with the application's own document —
145
169
  * so that the runtime registers it and decides every tool call by its rules
146
170
  * (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
147
- * place it runs: the workspace (`AppRenderer`) and the embed's floating
148
- * modes (`AppEmbed`).
171
+ * place it runs, all of them the workspace (`AppRenderer`): the Studio's
172
+ * Preview, the hosted page, and the embed inline and floating.
149
173
  */
150
174
  export declare function appDatalayerCreatePayload(app: AppSpec, instance?: AppInstance): Record<string, unknown>;
151
175
  /**
@@ -164,5 +188,5 @@ export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
164
188
  * what the page around it set does not reach in.
165
189
  */
166
190
  export declare function appThemeOverrides(app: AppSpec): ThemeOverrides | undefined;
167
- export declare function AppRenderer({ app, plugins, instance, onPresence, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
191
+ export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, sidebar, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
168
192
  export default AppRenderer;
@@ -32,7 +32,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
32
32
  * page its host draws (`page`, R-02) — the Studio's run page, at its
33
33
  * address and embedded;
34
34
  * - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
35
- * window, its title the application's face and name.
35
+ * window, its title the application's face and name;
36
+ * - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
37
+ * the approvals waiting for the person (`app-rules`), and what it did
38
+ * (`app-activity`), beside its page — what its builder reads in the
39
+ * Studio's Preview.
36
40
  *
37
41
  * Which plugins a kind needs, and how its workspace is laid out, is one
38
42
  * function: `appPreset`, the preset per kind, as `loopPlugins` is for the
@@ -47,6 +51,8 @@ import { APP_PAGE_SURFACE, defineAppHostPagePlugin, defineAppPagePlugin, hasAppP
47
51
  import { CANVAS_BLOCK_PLUGINS, canvasBlocksPluginName, } from '../plugins/canvas-blocks';
48
52
  import { LoopEmbed } from '../embed/LoopEmbed';
49
53
  import { dumpAppspec } from './appspec';
54
+ import { defineAppActivityPlugin } from '../plugins/app-activity';
55
+ import { defineAppRulesPlugin } from '../plugins/app-rules';
50
56
  import { defineAppFeedbackPlugin } from './AppFeedback';
51
57
  import { keepsFeedback } from './feedback';
52
58
  /** The id of an agent or a Cog, without its version. */
@@ -134,8 +140,9 @@ export function appLayoutOptions(app) {
134
140
  * its page (`app-page`) when it has one, with the Canvas's block plugins
135
141
  * but those of the UI plugins its organization turned off (`pluginsOff`),
136
142
  * whose contributions are the blocks it may draw (R-01b); a thumb and a
137
- * comment on each answer when its record keeps feedback (V-18); laid out
138
- * as `interface.layout` says;
143
+ * comment on each answer when its record keeps feedback (V-18); with
144
+ * `sidebar`, its rules and approvals card and its activity feed (R-01b);
145
+ * laid out as `interface.layout` says;
139
146
  * - a **decision**: the page its host draws (`page`), as the one view of a
140
147
  * workspace without a conversation (R-02).
141
148
  *
@@ -167,6 +174,14 @@ export function appPreset(app, options = {}) {
167
174
  // A thumb and a comment on each answer, kept in its record (LOOP
168
175
  // V-18): only for an application whose record keeps feedback.
169
176
  ...(keepsFeedback(app) ? [defineAppFeedbackPlugin(app)] : []),
177
+ // What its builder reads beside its page (R-01b): its rules and the
178
+ // approvals waiting, and what it did.
179
+ ...(options.sidebar
180
+ ? [
181
+ defineAppRulesPlugin(app),
182
+ defineAppActivityPlugin(app, options.appUid),
183
+ ]
184
+ : []),
170
185
  ],
171
186
  workspace: appLayoutOptions(app),
172
187
  };
@@ -176,8 +191,8 @@ export function appPreset(app, options = {}) {
176
191
  * the application's id, over AG-UI, with the application's own document —
177
192
  * so that the runtime registers it and decides every tool call by its rules
178
193
  * (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
179
- * place it runs: the workspace (`AppRenderer`) and the embed's floating
180
- * modes (`AppEmbed`).
194
+ * place it runs, all of them the workspace (`AppRenderer`): the Studio's
195
+ * Preview, the hosted page, and the embed inline and floating.
181
196
  */
182
197
  export function appDatalayerCreatePayload(app, instance) {
183
198
  return {
@@ -227,7 +242,7 @@ export function appThemeOverrides(app) {
227
242
  }
228
243
  : undefined;
229
244
  }
230
- export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, page, pluginsOff, ...embed }) {
245
+ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, onSaying, sidebar = false, frame = false, page, pluginsOff, ...embed }) {
231
246
  /*
232
247
  * The kind's plugins, made once per application. `LoopEmbed` rebuilds its
233
248
  * whole reactor when its plugins change, so a new plugin on every render
@@ -238,7 +253,12 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
238
253
  const off = [...(pluginsOff ?? [])].sort().join(',');
239
254
  const preset = useMemo(() => {
240
255
  try {
241
- return appPreset(app, { page, pluginsOff });
256
+ return appPreset(app, {
257
+ page,
258
+ pluginsOff,
259
+ sidebar,
260
+ appUid: instance?.appUid,
261
+ });
242
262
  }
243
263
  catch (error) {
244
264
  return {
@@ -247,7 +267,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
247
267
  }
248
268
  },
249
269
  // eslint-disable-next-line react-hooks/exhaustive-deps
250
- [source, page, off]);
270
+ [source, page, off, sidebar, instance?.appUid]);
251
271
  const allPlugins = useMemo(() => ('problem' in preset ? plugins : [...preset.plugins, ...plugins]), [preset, plugins]);
252
272
  /*
253
273
  * On Datalayer, the application runs on a runtime: allocated with a plain
@@ -296,6 +316,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, f
296
316
  face: app.emoji,
297
317
  welcome: app.interface.welcome || app.description,
298
318
  onPresence,
319
+ onSaying,
299
320
  }, showTokenUsage: false, datalayerCreatePayload: datalayerCreatePayload, ...embed, plugins: allPlugins }));
300
321
  }
301
322
  export default AppRenderer;
@@ -9,5 +9,6 @@ export * from './AppRenderer';
9
9
  export * from './appspec';
10
10
  export * from './checks';
11
11
  export * from './feedback';
12
+ export * from './records';
12
13
  export * from './rules';
13
14
  export * from './yaml';
@@ -13,5 +13,6 @@ export * from './AppRenderer';
13
13
  export * from './appspec';
14
14
  export * from './checks';
15
15
  export * from './feedback';
16
+ export * from './records';
16
17
  export * from './rules';
17
18
  export * from './yaml';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The record of an application (LOOP R-07), as ai-agents keeps it: what it
3
+ * did, session by session, written by the runtime it ran on — read here for
4
+ * its activity feed (R-01b, R-15): the sessions, newest first, and what each
5
+ * one did, step by step.
6
+ *
7
+ * Pure but for `fetch`: no React.
8
+ *
9
+ * @module loop/apps/records
10
+ */
11
+ /** What a record entry is. */
12
+ export type RecordKind = 'session' | 'tool_call' | 'decision' | 'check' | 'approval' | 'output' | 'feedback'
13
+ /** A turn of a conversation: what was asked, what it answered. */
14
+ | 'turn';
15
+ /** One entry of an application's record. */
16
+ export type RecordEntry = {
17
+ uid: string;
18
+ sessionUid: string;
19
+ deploymentUid: string;
20
+ version: number;
21
+ kind: RecordKind;
22
+ summary: string;
23
+ payload: Record<string, unknown>;
24
+ createdAt: string;
25
+ };
26
+ /** Where the record is read, and as whom. */
27
+ export type RecordsContext = {
28
+ /** The ai-agents service. */
29
+ aiAgentsUrl: string;
30
+ /** The reader's token: the record is its owner's. */
31
+ token: string;
32
+ };
33
+ /** Each kind in a word, as the feed labels it. */
34
+ export declare const RECORD_KIND_WORDS: Record<RecordKind, string>;
35
+ /** An entry as ai-agents answers with it. */
36
+ export declare function entryOf(raw: Record<string, unknown>): RecordEntry;
37
+ /** A session in a sentence: how many calls, what stopped it, how it ended. */
38
+ export declare function sessionSentence(entries: readonly RecordEntry[]): string;
39
+ /** The sessions of an application, newest first: their opening entries. */
40
+ export declare function listSessions(context: RecordsContext, appUid: string, limit?: number): Promise<RecordEntry[]>;
41
+ /** What one session did, in order. */
42
+ export declare function readSession(context: RecordsContext, sessionUid: string): Promise<RecordEntry[]>;