@datalayer/agent-runtimes 1.3.73 → 1.3.75

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 (72) hide show
  1. package/lib/chat/ChatFloating.d.ts +25 -2
  2. package/lib/chat/ChatFloating.js +80 -12
  3. package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
  4. package/lib/chat/assistant/AssistantContextMenu.js +68 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +123 -5
  6. package/lib/chat/assistant/AssistantStage.js +245 -7
  7. package/lib/chat/assistant/BalloonParts.d.ts +15 -1
  8. package/lib/chat/assistant/BalloonParts.js +26 -9
  9. package/lib/chat/assistant/BalloonVisual.d.ts +65 -0
  10. package/lib/chat/assistant/BalloonVisual.js +58 -0
  11. package/lib/chat/assistant/SpeechBalloon.d.ts +68 -2
  12. package/lib/chat/assistant/SpeechBalloon.js +113 -7
  13. package/lib/chat/assistant/state.d.ts +19 -0
  14. package/lib/chat/assistant/state.js +44 -17
  15. package/lib/chat/base/ChatBase.js +51 -1
  16. package/lib/chat/indicators/TypingDots.d.ts +13 -0
  17. package/lib/chat/indicators/TypingDots.js +21 -0
  18. package/lib/chat/messages/ChatMessageList.js +1 -15
  19. package/lib/components/inspector/AgentInspector.d.ts +59 -0
  20. package/lib/components/inspector/AgentInspector.js +220 -0
  21. package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
  22. package/lib/components/inspector/AgentInspectorDialog.js +9 -0
  23. package/lib/components/inspector/JsonTree.d.ts +24 -0
  24. package/lib/components/inspector/JsonTree.js +89 -0
  25. package/lib/components/inspector/a2aInspect.d.ts +56 -0
  26. package/lib/components/inspector/a2aInspect.js +478 -0
  27. package/lib/components/inspector/agentInspector.d.ts +179 -0
  28. package/lib/components/inspector/agentInspector.js +267 -0
  29. package/lib/components/inspector/chatInspect.d.ts +38 -0
  30. package/lib/components/inspector/chatInspect.js +65 -0
  31. package/lib/components/inspector/classify.d.ts +19 -0
  32. package/lib/components/inspector/classify.js +42 -0
  33. package/lib/components/inspector/index.d.ts +14 -0
  34. package/lib/components/inspector/index.js +18 -0
  35. package/lib/components/teams/A2ATeamGraph.d.ts +54 -3
  36. package/lib/components/teams/A2ATeamGraph.js +37 -11
  37. package/lib/components/teams/NotebookPreview.d.ts +11 -2
  38. package/lib/components/teams/NotebookPreview.js +6 -3
  39. package/lib/components/teams/NotebookPreviewView.js +9 -1
  40. package/lib/components/teams/TeamNotebook.d.ts +10 -1
  41. package/lib/components/teams/TeamNotebook.js +15 -3
  42. package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
  43. package/lib/components/teams/jupyterThemeVariables.js +441 -0
  44. package/lib/components/teams/teamConnections.d.ts +9 -1
  45. package/lib/components/teams/teamConnections.js +20 -0
  46. package/lib/components/teams/useA2ATeam.d.ts +17 -4
  47. package/lib/components/teams/useA2ATeam.js +87 -23
  48. package/lib/examples/AgentA2ATeamExample.d.ts +16 -2
  49. package/lib/examples/AgentA2ATeamExample.js +65 -13
  50. package/lib/examples/{ChatAssistantExample.d.ts → AssistantExample.d.ts} +2 -2
  51. package/lib/examples/{ChatAssistantExample.js → AssistantExample.js} +69 -7
  52. package/lib/examples/{ChatAssistantGalleryExample.d.ts → AssistantGalleryExample.d.ts} +2 -2
  53. package/lib/examples/{ChatAssistantGalleryExample.js → AssistantGalleryExample.js} +45 -13
  54. package/lib/examples/example-selector.js +4 -2
  55. package/lib/examples/exampleGroups.d.ts +9 -0
  56. package/lib/examples/exampleGroups.js +56 -0
  57. package/lib/examples/index.d.ts +1 -1
  58. package/lib/examples/index.js +1 -1
  59. package/lib/examples/main.js +3 -43
  60. package/lib/examples/utils/AssistantGalleryGrid.d.ts +6 -1
  61. package/lib/examples/utils/AssistantGalleryGrid.js +16 -3
  62. package/lib/examples/utils/assistantGallery.d.ts +51 -2
  63. package/lib/examples/utils/assistantGallery.js +32 -7
  64. package/lib/loop/apps/AppRenderer.js +1 -1
  65. package/lib/loop/core/index.d.ts +17 -0
  66. package/lib/loop/core/index.js +1 -0
  67. package/lib/loop/plugins/assistant/index.d.ts +1 -1
  68. package/lib/loop/plugins/assistant/index.js +8 -3
  69. package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
  70. package/lib/types/chat.d.ts +7 -0
  71. package/package.json +1 -1
  72. package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
@@ -18,10 +18,14 @@
18
18
  import React from 'react';
19
19
  import type { ChatVoice } from '../voice';
20
20
  import { type BalloonDisplay, type BalloonToolLine } from './assistant/toolLine';
21
+ import type { BalloonExpandTarget } from './assistant/BalloonVisual';
21
22
  import { type AssistantCharacter } from './assistant/characters';
22
23
  import type { AssistantCharacterData } from './assistant/formats/types';
23
24
  import { type AssistantSaying } from './assistant/state';
24
25
  import { type PresenceState } from './presence/presenceStatus';
26
+ import type { AgentInspectorSink } from '../components/inspector/agentInspector';
27
+ import type { AssistantMenuItem } from './assistant/AssistantContextMenu';
28
+ import type { AssistantAbout } from './assistant/AssistantStage';
25
29
  import type { ChatCommonProps, ChatViewMode, ThemeOverrides } from '../types';
26
30
  /**
27
31
  * A conversation the host draws in the floating window, in place of the
@@ -118,7 +122,7 @@ export interface ChatFloatingProps extends ChatCommonProps {
118
122
  * How the floating assistant's balloon shows the conversation (LOOP
119
123
  * T-23; an Appspec's `interface.balloon`):
120
124
  * - 'history': every message, scrolled, under a header that counts them,
121
- * the composer last; closed, a peek of the newest words.
125
+ * the composer last; closed, the balloon lists the messages too.
122
126
  * - 'current': only what it says or does now — the answer being written,
123
127
  * or the tool it calls — in one compact balloon, *Now* on it; open, that
124
128
  * line and the composer, nothing to scroll.
@@ -126,6 +130,12 @@ export interface ChatFloatingProps extends ChatCommonProps {
126
130
  * @default 'history'
127
131
  */
128
132
  balloonDisplay?: BalloonDisplay;
133
+ /**
134
+ * Where a large visual the assistant's balloon carries (a notebook) is
135
+ * drawn when expanded: an element of the page, through a portal; unsaid,
136
+ * a large dialog over the page.
137
+ */
138
+ expandTarget?: BalloonExpandTarget;
129
139
  /**
130
140
  * The runtime the floating assistant asks typed decisions at — its
131
141
  * `/api/v1/configure/inference/decisions`, as the `decide` tool asks — and
@@ -165,6 +175,19 @@ export interface ChatFloatingProps extends ChatCommonProps {
165
175
  * own props (`protocol`, `suggestions`, …) are then not read.
166
176
  */
167
177
  conversation?: FloatingConversation;
178
+ /**
179
+ * The Agent Inspector's sink: the chat records its agent's turns and tool
180
+ * calls there, and the assistant's menu offers *Inspect the agent…*.
181
+ */
182
+ inspector?: AgentInspectorSink | null;
183
+ /** The balloon's display, changed from the assistant's menu. */
184
+ onBalloonDisplayChange?: (display: BalloonDisplay) => void;
185
+ /** Opens the host's character picker, from the assistant's menu. */
186
+ onChangeCharacter?: () => void;
187
+ /** What the assistant's menu's *About* says. */
188
+ about?: AssistantAbout;
189
+ /** The host's own entries of the assistant's menu. */
190
+ contextMenu?: readonly AssistantMenuItem[];
168
191
  /**
169
192
  * Its voice (VOICE.md V1): push-to-talk in the composer, heard on the
170
193
  * device; with `output: 'always'`, its answers said by Datalayer's speech
@@ -177,5 +200,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
177
200
  * ChatFloating component
178
201
  * A floating chat window built on ChatBase
179
202
  */
180
- 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, balloonDisplay, conversation, decisions, voice, }: ChatFloatingProps): React.JSX.Element;
203
+ 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, balloonDisplay: balloonDisplayGiven, expandTarget, conversation, decisions, voice, inspector, onBalloonDisplayChange, onChangeCharacter, about, contextMenu, }: ChatFloatingProps): React.JSX.Element;
181
204
  export default ChatFloating;
@@ -40,7 +40,7 @@ import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/Ball
40
40
  import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
41
41
  import { SpeechBalloon } from './assistant/SpeechBalloon';
42
42
  import { DEFAULT_ASSISTANT_CHARACTER, } from './assistant/characters';
43
- import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
43
+ import { assistantStateOf, balloonHistoryOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
44
44
  import { presenceState, presenceToolOf, } from './presence/presenceStatus';
45
45
  import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
46
46
  import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
@@ -70,7 +70,20 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
70
70
  * ChatFloating component
71
71
  * A floating chat window built on ChatBase
72
72
  */
73
- 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, balloonDisplay = DEFAULT_BALLOON_DISPLAY, conversation, decisions, voice, }) {
73
+ 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, balloonDisplay: balloonDisplayGiven = DEFAULT_BALLOON_DISPLAY, expandTarget, conversation, decisions, voice, inspector, onBalloonDisplayChange, onChangeCharacter, about, contextMenu, }) {
74
+ // The chat's own send, once it can: what a suggestion in the balloon sends.
75
+ const chatControls = useRef(null);
76
+ const [suggestionPrompt, setSuggestionPrompt] = useState();
77
+ const [speechMuted, setSpeechMuted] = useState(false);
78
+ // The balloon's display, as the assistant's menu chose it; the host's
79
+ // until then, and again when the host changes it.
80
+ const [chosenDisplay, setChosenDisplay] = useState();
81
+ useEffect(() => setChosenDisplay(undefined), [balloonDisplayGiven]);
82
+ const balloonDisplay = chosenDisplay ?? balloonDisplayGiven;
83
+ const changeBalloonDisplay = useCallback((next) => {
84
+ setChosenDisplay(next);
85
+ onBalloonDisplayChange?.(next);
86
+ }, [onBalloonDisplayChange]);
74
87
  // Store-based state
75
88
  const storeIsOpen = useChatOpen();
76
89
  const storeMessages = useChatMessages();
@@ -136,6 +149,8 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
136
149
  // The tool it calls now, and how many messages the history holds (T-23).
137
150
  const [ownTool, setOwnTool] = useState();
138
151
  const [messageCount, setMessageCount] = useState(0);
152
+ // The conversation's messages, listed in the closed `history` balloon.
153
+ const [ownHistory, setOwnHistory] = useState([]);
139
154
  /*
140
155
  * Its voice (VOICE.md V1): the answers said by the speech service, as
141
156
  * they are written (VO-20), the state following the sound (VO-22).
@@ -174,7 +189,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
174
189
  window.removeEventListener('keydown', unlock);
175
190
  };
176
191
  }, [speaker]);
177
- useSpokenAnswers(voiceItems, chatBusy, !!speaker, speaker);
192
+ useSpokenAnswers(voiceItems, chatBusy, !!speaker && !speechMuted, speaker);
178
193
  const stopSpeaking = useCallback(() => speaker?.stop(), [speaker]);
179
194
  const mouthLevel = useMemo(() => (speaker ? () => speaker.level() : undefined), [speaker]);
180
195
  const [arriving, setArriving] = useState(false);
@@ -259,6 +274,11 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
259
274
  const toolSaid = toolLine && (toolLine.phase === 'running' || atWork) ? toolLine : undefined;
260
275
  // Current: what is said now — thinking until the answer's words arrive.
261
276
  const currentWords = atWork && !answering ? 'Thinking…' : saying?.text || undefined;
277
+ // History: the closed balloon lists the conversation, not its newest line.
278
+ const listedHistory = !showsCurrent && !conversation && ownHistory.length > 0
279
+ ? ownHistory
280
+ : undefined;
281
+ const withHistory = (said) => listedHistory ? { ...said, history: listedHistory } : said;
262
282
  const assistantBalloon = assistantState === 'paused'
263
283
  ? { text: ASSISTANT_WORDS.paused }
264
284
  : speakerState.sentence
@@ -267,11 +287,18 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
267
287
  : speakerState.refused
268
288
  ? { text: speakerState.refused }
269
289
  : balloonApproval
270
- ? { text: ASSISTANT_WORDS.approval, approval: balloonApproval }
290
+ ? withHistory({
291
+ text: ASSISTANT_WORDS.approval,
292
+ approval: balloonApproval,
293
+ })
271
294
  : assistantState === 'waiting'
272
295
  ? { text: ASSISTANT_WORDS.waiting }
273
296
  : toolSaid
274
- ? { text: toolLineText(toolSaid), tool: toolSaid, busy: atWork }
297
+ ? withHistory({
298
+ text: toolLineText(toolSaid),
299
+ tool: toolSaid,
300
+ busy: atWork,
301
+ })
275
302
  : showsCurrent && (unheard || atWork)
276
303
  ? // Current: the words as they are written, whole.
277
304
  {
@@ -287,15 +314,16 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
287
314
  : undefined,
288
315
  }
289
316
  : unheard && saying
290
- ? {
291
- // A peek: the first words; the rest is in the conversation.
317
+ ? withHistory({
318
+ // A peek: the first words; the rest is in the
319
+ // conversation, listed when there is one.
292
320
  ...peekLine(saying.text),
293
321
  onDismiss: () => {
294
322
  setHeardId(saying.id);
295
323
  setFreshSaying(false);
296
324
  },
297
- }
298
- : { text: description };
325
+ })
326
+ : withHistory({ text: description });
299
327
  const balloonInsists = !!speakerState.sentence ||
300
328
  assistantState === 'greeting' ||
301
329
  assistantState === 'waiting' ||
@@ -303,6 +331,28 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
303
331
  !!toolSaid ||
304
332
  (showsCurrent && atWork) ||
305
333
  (unheard && (assistantState === 'speaking' || freshSaying));
334
+ // The chat's suggestions, in the balloon while it waits for a question:
335
+ // one clicked is sent as the prompt, as if typed.
336
+ const balloonSuggestions = useMemo(() => conversation || !suggestions?.length
337
+ ? undefined
338
+ : suggestions.map(suggestion => ({
339
+ label: suggestion.title,
340
+ prompt: suggestion.message,
341
+ })), [conversation, suggestions]);
342
+ const sendSuggestion = useCallback((suggestion) => {
343
+ if (chatControls.current) {
344
+ chatControls.current.send(suggestion.prompt);
345
+ }
346
+ else {
347
+ // Not able to send yet: sent once it is.
348
+ setSuggestionPrompt(suggestion.prompt);
349
+ }
350
+ }, []);
351
+ const panelOnSendReady = panelProps?.onSendReady;
352
+ const handleSendReady = useCallback(controls => {
353
+ chatControls.current = controls;
354
+ panelOnSendReady?.(controls);
355
+ }, [panelOnSendReady]);
306
356
  const panelOnLoadingChange = panelProps?.onLoadingChange;
307
357
  const panelOnDisplayItemsChange = panelProps?.onDisplayItemsChange;
308
358
  const handleLoadingChange = useCallback((loading) => {
@@ -321,6 +371,12 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
321
371
  ? previous
322
372
  : line);
323
373
  setMessageCount(conversationCount(items));
374
+ const listed = balloonHistoryOf(items);
375
+ setOwnHistory(previous => previous.length === listed.length &&
376
+ previous.every((message, index) => message.id === listed[index].id &&
377
+ message.text === listed[index].text)
378
+ ? previous
379
+ : listed);
324
380
  setVoiceItems(items);
325
381
  const said = latestSaying(items);
326
382
  setSaying(previous => previous?.id === said?.id && previous?.text === said?.text
@@ -1000,9 +1056,9 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1000
1056
  ...poweredByProps,
1001
1057
  }, renderToolResult: renderToolResult, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
1002
1058
  showAvatars: true,
1003
- }, 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, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, children: conversationBalloon && currentOpen ? (
1059
+ }, 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 ? (
1004
1060
  // Current: the one thing said or done now, over the composer.
1005
- _jsx(Box, { sx: { px: 3, pt: 3, pb: 2, overflow: 'hidden' }, children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), tool: toolSaid, busy: atWork, speaking: answering && atWork }) })) : (children) }))] }));
1061
+ _jsx(Box, { sx: { px: 3, pt: 3, pb: 2, overflow: 'hidden' }, children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), tool: toolSaid, busy: atWork, speaking: answering && atWork, waiting: atWork && !answering }) })) : (children) }))] }));
1006
1062
  /*
1007
1063
  * The floating popup's button. Words not yet heard make it blink for the
1008
1064
  * person's attention, until the chat is opened; hovered, they show in a
@@ -1050,7 +1106,19 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1050
1106
  left: px(stageDrag.position.left),
1051
1107
  top: px(stageDrag.position.top),
1052
1108
  }
1053
- : getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, balloonDisplay: balloonDisplay, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
1109
+ : 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
1110
+ ? () => chatControls.current?.stop()
1111
+ : undefined, onNewChat: !conversation && showNewChatButton ? handleNewChat : undefined, onClear: !conversation && useStoreMode ? handleClear : undefined, onChangeCharacter: onChangeCharacter, speech: speaker
1112
+ ? {
1113
+ muted: speechMuted,
1114
+ onToggle: () => {
1115
+ if (!speechMuted) {
1116
+ speaker.stop();
1117
+ }
1118
+ setSpeechMuted(!speechMuted);
1119
+ },
1120
+ }
1121
+ : undefined, onResetPosition: stageDrag.position ? stageDrag.reset : undefined, about: about, contextMenu: contextMenu })), showButton && !isOpen && !assistantShown && (_jsx(Box, { sx: {
1054
1122
  ...getPositionStyles(),
1055
1123
  }, children: _jsxs(Box, { sx: {
1056
1124
  position: 'relative',
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The assistant's own menu (LOOP T-27): right-click the character — or
3
+ * Shift+F10 or the ContextMenu key while it has the focus, or a long press on
4
+ * a touch screen — and a menu opens where the pointer is, with what applies
5
+ * to this agent on this page: inspect it, open the conversation, the
6
+ * balloon's display, the suggestions, stop the turn, a new conversation,
7
+ * another character, its voice, send it away, put it back, about it. Esc or
8
+ * a click outside closes it.
9
+ *
10
+ * Its entries are composed from what the host passes ({@link
11
+ * AssistantMenuItem}s): a host or a plugin adds its own (`contextMenu`; in
12
+ * the LOOP workspace, the `loop.assistant.menu` contribution point).
13
+ *
14
+ * @module chat/assistant/AssistantContextMenu
15
+ */
16
+ import type { JSX, RefObject } from 'react';
17
+ import type { Icon } from '@primer/octicons-react';
18
+ /** One entry of the assistant's menu. */
19
+ export type AssistantMenuItem = {
20
+ /** Stable id: `inspect`, `stop`, a plugin's own. */
21
+ id: string;
22
+ label: string;
23
+ /** Said under the label, smaller. */
24
+ description?: string;
25
+ /** An octicon, before the label. */
26
+ icon?: Icon;
27
+ onSelect: () => void;
28
+ /**
29
+ * Checkable: `true` or `false` makes it one of a choice — the entries of
30
+ * its group are a radio group, one checked.
31
+ */
32
+ checked?: boolean;
33
+ disabled?: boolean;
34
+ /** The group it is listed in, by its heading; ungrouped entries come first. */
35
+ group?: string;
36
+ /** Drawn as a danger: send away. */
37
+ danger?: boolean;
38
+ };
39
+ /** The entries by group, in the order their first entry came. */
40
+ export declare function menuGroups(items: readonly AssistantMenuItem[]): {
41
+ group: string | undefined;
42
+ items: AssistantMenuItem[];
43
+ }[];
44
+ /** Whether a key opens the menu: Shift+F10, or the ContextMenu key. */
45
+ export declare function opensContextMenu(event: {
46
+ key: string;
47
+ shiftKey: boolean;
48
+ }): boolean;
49
+ /** How long a touch is held before the menu opens, in ms. */
50
+ export declare const LONG_PRESS_MS = 550;
51
+ export type AssistantContextMenuProps = {
52
+ /** Where it opens, in the window's coordinates; closed when `null`. */
53
+ at: {
54
+ x: number;
55
+ y: number;
56
+ } | null;
57
+ items: readonly AssistantMenuItem[];
58
+ /** It closed: Esc, a click outside, or an entry chosen. */
59
+ onClose: () => void;
60
+ /** Where the focus goes back to once it closes. */
61
+ returnFocusRef?: RefObject<HTMLElement | null>;
62
+ /** Its name, for a screen reader: `Paper clip's menu`. */
63
+ label: string;
64
+ };
65
+ /** The menu, anchored at a point of the window. */
66
+ export declare function AssistantContextMenu({ at, items, onClose, returnFocusRef, label, }: AssistantContextMenuProps): JSX.Element | null;
67
+ export default AssistantContextMenu;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { ActionList, ActionMenu } from '@primer/react';
5
+ /** The entries by group, in the order their first entry came. */
6
+ export function menuGroups(items) {
7
+ const groups = [];
8
+ for (const item of items) {
9
+ const found = groups.find(group => group.group === item.group);
10
+ if (found) {
11
+ found.items.push(item);
12
+ }
13
+ else {
14
+ groups.push({ group: item.group, items: [item] });
15
+ }
16
+ }
17
+ // The ungrouped come first.
18
+ return [
19
+ ...groups.filter(group => group.group === undefined),
20
+ ...groups.filter(group => group.group !== undefined),
21
+ ];
22
+ }
23
+ /** Whether a key opens the menu: Shift+F10, or the ContextMenu key. */
24
+ export function opensContextMenu(event) {
25
+ return event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey);
26
+ }
27
+ /** How long a touch is held before the menu opens, in ms. */
28
+ export const LONG_PRESS_MS = 550;
29
+ /** The menu, anchored at a point of the window. */
30
+ export function AssistantContextMenu({ at, items, onClose, returnFocusRef, label, }) {
31
+ const anchor = useRef(null);
32
+ const open = at !== null;
33
+ useEffect(() => {
34
+ if (!open) {
35
+ return;
36
+ }
37
+ // A scroll moves what it was opened on: it closes.
38
+ const close = () => onClose();
39
+ window.addEventListener('scroll', close, true);
40
+ return () => window.removeEventListener('scroll', close, true);
41
+ }, [open, onClose]);
42
+ if (!at || typeof document === 'undefined') {
43
+ return null;
44
+ }
45
+ const groups = menuGroups(items);
46
+ return (_jsxs(_Fragment, { children: [createPortal(_jsx("div", { ref: anchor, "aria-hidden": "true", "data-assistant-menu-anchor": "", style: {
47
+ position: 'fixed',
48
+ left: at.x,
49
+ top: at.y,
50
+ width: 1,
51
+ height: 1,
52
+ pointerEvents: 'none',
53
+ } }), document.body), _jsx(ActionMenu, { open: true, anchorRef: anchor, onOpenChange: next => {
54
+ if (!next) {
55
+ onClose();
56
+ requestAnimationFrame(() => returnFocusRef?.current?.focus());
57
+ }
58
+ }, children: _jsx(ActionMenu.Overlay, { width: "auto", "data-assistant-menu": "", children: _jsx(ActionList, { "aria-label": label, children: groups.map(({ group, items: entries }, index) => {
59
+ const choice = entries.some(item => item.checked !== undefined);
60
+ const list = entries.map(item => (_jsxs(ActionList.Item, { "data-assistant-menu-item": item.id, disabled: item.disabled, variant: item.danger ? 'danger' : 'default', ...(choice ? { selected: !!item.checked } : {}), onSelect: () => {
61
+ item.onSelect();
62
+ onClose();
63
+ requestAnimationFrame(() => returnFocusRef?.current?.focus());
64
+ }, children: [item.icon && (_jsx(ActionList.LeadingVisual, { children: _jsx(item.icon, {}) })), item.label, item.description && (_jsx(ActionList.Description, { variant: "block", children: item.description }))] }, item.id)));
65
+ return (_jsxs(ActionList.Group, { ...(choice ? { selectionVariant: 'single' } : {}), children: [group && (_jsx(ActionList.GroupHeading, { children: group })), list] }, group ?? `ungrouped-${index}`));
66
+ }) }) }) })] }));
67
+ }
68
+ export default AssistantContextMenu;
@@ -8,6 +8,17 @@
8
8
  * working: it steps aside — out of sight and out of the pointer's way — until
9
9
  * the place is clear again.
10
10
  *
11
+ * Right-clicked — or Shift+F10 or the ContextMenu key while it has the
12
+ * focus, or a long press — it opens its own menu where the pointer is
13
+ * (`AssistantContextMenu`): what the host passes applies — *Inspect the
14
+ * agent…* (the Agent Inspector, in a dialog loaded then), the conversation,
15
+ * the balloon's display, the suggestions, *Stop*, a new conversation,
16
+ * another character, its voice, send it away, its place, about it — and
17
+ * what a host or a plugin adds (`contextMenu`).
18
+ *
19
+ * Given suggestions, its balloon offers them while it waits for a question
20
+ * (idle, or greeting): one clicked is sent as the prompt (`onSuggestion`).
21
+ *
11
22
  * Its motions are one set for every character, by the parts each drawing
12
23
  * names (`assistant-body`, `-pupils`, `-lids`, `-mouth`), and nothing moves
13
24
  * for a reader who asks the system for reduced motion.
@@ -16,10 +27,54 @@
16
27
  */
17
28
  import type { JSX, ReactNode, RefObject } from 'react';
18
29
  import { type AssistantCharacter } from './characters';
30
+ import { type BalloonSuggestion } from './SpeechBalloon';
31
+ import { type AssistantMenuItem } from './AssistantContextMenu';
32
+ import type { AgentInspectorSink } from '../../components/inspector/agentInspector';
33
+ import { type BalloonExpandTarget, type BalloonVisual } from './BalloonVisual';
19
34
  import type { AssistantCharacterData } from './formats/types';
20
35
  import type { DecisionAsker } from './decisions';
21
36
  import type { BalloonDisplay, BalloonToolLine } from './toolLine';
22
- import { type AssistantAway, type AssistantState, type BalloonApproval } from './state';
37
+ import { type AssistantAway, type AssistantState, type BalloonApproval, type BalloonHistoryMessage } from './state';
38
+ /** What the assistant's *About* says. */
39
+ export type AssistantAbout = {
40
+ /** The agent's name, or its application's. */
41
+ name?: string;
42
+ /** Its spec or application id, with its version. */
43
+ spec?: string;
44
+ /** Its model. */
45
+ model?: string;
46
+ /** Where it runs: `in your browser`, `on a runtime`. */
47
+ where?: string;
48
+ };
49
+ /**
50
+ * The entries of the assistant's menu, composed from what the host passes:
51
+ * each shows when what it does is there to do.
52
+ */
53
+ export declare function assistantMenuItems(options: {
54
+ name: string;
55
+ open: boolean;
56
+ onToggle: () => void;
57
+ onDismiss: (away: AssistantAway) => void;
58
+ /** The conversation entry's label; `false` leaves it out. */
59
+ conversationLabel?: string | false;
60
+ inspect?: () => void;
61
+ balloonDisplay?: BalloonDisplay;
62
+ onBalloonDisplayChange?: (display: BalloonDisplay) => void;
63
+ suggestions?: readonly BalloonSuggestion[];
64
+ onSuggestion?: (suggestion: BalloonSuggestion) => void;
65
+ busy?: boolean;
66
+ onStop?: () => void;
67
+ onNewChat?: () => void;
68
+ onClear?: () => void;
69
+ onChangeCharacter?: () => void;
70
+ speech?: {
71
+ muted: boolean;
72
+ onToggle: () => void;
73
+ };
74
+ onResetPosition?: () => void;
75
+ about?: () => void;
76
+ extra?: readonly AssistantMenuItem[];
77
+ }): AssistantMenuItem[];
23
78
  /**
24
79
  * Where the balloon goes so that it stays inside the window (T-23): toward
25
80
  * the middle of the page from whichever half the character stands in, and
@@ -76,6 +131,8 @@ export interface AssistantStageProps {
76
131
  */
77
132
  balloon?: {
78
133
  text: string;
134
+ /** The words uncut, when `text` was cut to fit: *more* shows them. */
135
+ fullText?: string;
79
136
  more?: boolean;
80
137
  approval?: BalloonApproval;
81
138
  /** Puts the peek away (a × beside its line); never an approval's. */
@@ -88,15 +145,76 @@ export interface AssistantStageProps {
88
145
  busy?: boolean;
89
146
  /** What goes with the words (`current`): a notebook given, read-only. */
90
147
  attachment?: ReactNode;
148
+ /**
149
+ * The conversation's messages (`history`): the balloon lists them all,
150
+ * counted and scrolled, rather than the newest line alone.
151
+ */
152
+ history?: readonly BalloonHistoryMessage[];
153
+ /**
154
+ * The large visual `attachment` is the compact form of (a notebook):
155
+ * the balloon offers *Expand*, and draws it large — into
156
+ * `expandTarget`, or in a dialog over the page.
157
+ */
158
+ visual?: BalloonVisual;
91
159
  };
92
160
  /**
93
- * How the balloon shows the conversation (LOOP T-23): a peek of it
94
- * (`history`, the default — open, the conversation is the whole history),
95
- * or the one thing being said or done now (`current`).
161
+ * Where a balloon's large visual is drawn when expanded: an element of
162
+ * the page (through a portal), such as the area under a team's graph;
163
+ * without one, a large dialog over the page.
164
+ */
165
+ expandTarget?: BalloonExpandTarget;
166
+ /**
167
+ * Draw a large visual into `expandTarget` as soon as it arrives, without
168
+ * waiting for *Expand*: a team's notebook, under its graph.
169
+ */
170
+ expandOnArrival?: boolean;
171
+ /**
172
+ * How the balloon shows the conversation (LOOP T-23): the conversation
173
+ * (`history`, the default — closed, the messages in `balloon.history`, or
174
+ * a peek of the newest when none are given; open, the whole history), or
175
+ * the one thing being said or done now (`current`).
96
176
  */
97
177
  balloonDisplay?: BalloonDisplay;
98
178
  /** Show the balloon without being hovered: something new to say. */
99
179
  insist?: boolean;
180
+ /**
181
+ * What the person may ask: chips in the balloon while it waits for a
182
+ * question (idle or greeting), and a group of its menu. One chosen is sent
183
+ * as the prompt with `onSuggestion`.
184
+ */
185
+ suggestions?: readonly BalloonSuggestion[];
186
+ onSuggestion?: (suggestion: BalloonSuggestion) => void;
187
+ /** The Agent Inspector's sink: its menu offers *Inspect the agent…*. */
188
+ inspector?: AgentInspectorSink | null;
189
+ /** The agent whose own record the inspector shows, of a shared sink (a team's member). */
190
+ inspectAgent?: string;
191
+ /**
192
+ * What its menu calls the conversation's entry, which does `onToggle`;
193
+ * `false` leaves it out, where there is no conversation to open (a team's
194
+ * member that is asked by another). Unsaid: *Open / Close the conversation*.
195
+ */
196
+ conversationLabel?: string | false;
197
+ /** The balloon's display changes from its menu: *Balloon: History / Current*. */
198
+ onBalloonDisplayChange?: (display: BalloonDisplay) => void;
199
+ /** Stops the turn: offered in its menu while it is at work. */
200
+ onStop?: () => void;
201
+ /** Starts a new conversation, from its menu. */
202
+ onNewChat?: () => void;
203
+ /** Clears the conversation, from its menu. */
204
+ onClear?: () => void;
205
+ /** Opens the host's character picker, from its menu. */
206
+ onChangeCharacter?: () => void;
207
+ /** Its voice, muted or not, from its menu. */
208
+ speech?: {
209
+ muted: boolean;
210
+ onToggle: () => void;
211
+ };
212
+ /** Puts it back where it started, after a drag: offered in its menu. */
213
+ onResetPosition?: () => void;
214
+ /** What its menu's *About* says. */
215
+ about?: AssistantAbout;
216
+ /** The host's or a plugin's own entries of its menu. */
217
+ contextMenu?: readonly AssistantMenuItem[];
100
218
  /** Send it away (T-27): for the page, for the session, or for good. */
101
219
  onDismiss: (away: AssistantAway) => void;
102
220
  /**
@@ -129,5 +247,5 @@ export declare function mouthOpening(level: number): 0 | 1 | 2 | 3;
129
247
  * `data-assistant-mouth`, each frame, without drawing the character again.
130
248
  */
131
249
  export declare function useMouth(stageRef: RefObject<HTMLElement | null>, mouthLevel: (() => number) | undefined, speaking: boolean): void;
132
- export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, balloonDisplay, }: AssistantStageProps): JSX.Element;
250
+ export declare function AssistantStage({ character, state, size, place, stageRef, onDragStart, open, onToggle, balloon, insist, onDismiss, ownRef, decide, mouthLevel, stayPut, balloonDisplay, expandTarget, expandOnArrival, suggestions, onSuggestion, inspector, inspectAgent, conversationLabel, onBalloonDisplayChange, onStop, onNewChat, onClear, onChangeCharacter, speech, onResetPosition, about, contextMenu, }: AssistantStageProps): JSX.Element;
133
251
  export default AssistantStage;