@datalayer/agent-runtimes 1.3.80 → 1.3.82

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 (97) hide show
  1. package/lib/chat/assistant/AssistantStage.d.ts +9 -4
  2. package/lib/chat/assistant/AssistantStage.js +18 -6
  3. package/lib/chat/assistant/SpeechBalloon.js +16 -1
  4. package/lib/chat/assistant/SpriteCharacter.d.ts +25 -3
  5. package/lib/chat/assistant/SpriteCharacter.js +68 -3
  6. package/lib/chat/assistant/formats/acf.d.ts +56 -0
  7. package/lib/chat/assistant/formats/acf.js +317 -0
  8. package/lib/chat/assistant/formats/acs.d.ts +85 -7
  9. package/lib/chat/assistant/formats/acs.js +111 -39
  10. package/lib/chat/assistant/formats/files.d.ts +6 -2
  11. package/lib/chat/assistant/formats/files.js +18 -4
  12. package/lib/chat/assistant/formats/index.d.ts +6 -3
  13. package/lib/chat/assistant/formats/index.js +4 -2
  14. package/lib/chat/assistant/formats/types.d.ts +14 -0
  15. package/lib/chat/assistant/state.d.ts +2 -0
  16. package/lib/chat/assistant/state.js +9 -0
  17. package/lib/chat/base/ChatBase.js +47 -4
  18. package/lib/chat/base/loopStep.d.ts +40 -0
  19. package/lib/chat/base/loopStep.js +73 -0
  20. package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
  21. package/lib/chat/indicators/McpStatusIndicator.js +13 -6
  22. package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
  23. package/lib/chat/indicators/SkillsStatusIndicator.js +7 -3
  24. package/lib/chat/prompt/InputPrompt.d.ts +4 -1
  25. package/lib/chat/prompt/InputPrompt.js +2 -2
  26. package/lib/chat/prompt/InputPromptBase.d.ts +4 -1
  27. package/lib/chat/prompt/InputPromptBase.js +2 -2
  28. package/lib/chat/prompt/InputPromptLexical.d.ts +7 -1
  29. package/lib/chat/prompt/InputPromptLexical.js +5 -3
  30. package/lib/components/teams/index.d.ts +1 -0
  31. package/lib/components/teams/index.js +1 -0
  32. package/lib/components/teams/usePeerSandbox.d.ts +13 -0
  33. package/lib/components/teams/usePeerSandbox.js +59 -0
  34. package/lib/examples/AgentA2ATeamExample.js +7 -2
  35. package/lib/loop/apps/AppComposer.d.ts +31 -0
  36. package/lib/loop/apps/AppComposer.js +67 -0
  37. package/lib/loop/apps/AppRenderer.d.ts +16 -5
  38. package/lib/loop/apps/AppRenderer.js +49 -16
  39. package/lib/loop/apps/appspec.d.ts +8 -1
  40. package/lib/loop/apps/appspec.js +119 -2
  41. package/lib/loop/apps/checks.js +161 -1
  42. package/lib/loop/apps/composer.d.ts +37 -0
  43. package/lib/loop/apps/composer.js +61 -0
  44. package/lib/loop/apps/hostTools.d.ts +17 -0
  45. package/lib/loop/apps/hostTools.js +20 -0
  46. package/lib/loop/apps/index.d.ts +2 -0
  47. package/lib/loop/apps/index.js +2 -0
  48. package/lib/loop/apps/rules.d.ts +22 -1
  49. package/lib/loop/apps/rules.js +49 -1
  50. package/lib/loop/core/index.d.ts +31 -0
  51. package/lib/loop/core/index.js +22 -0
  52. package/lib/loop/embed/AppEmbed.d.ts +100 -6
  53. package/lib/loop/embed/AppEmbed.js +172 -27
  54. package/lib/loop/embed/LoopEmbed.js +2 -0
  55. package/lib/loop/embed/element.js +58 -5
  56. package/lib/loop/embed/embedConfig.d.ts +10 -1
  57. package/lib/loop/embed/embedConfig.js +12 -1
  58. package/lib/loop/embed/embedSession.d.ts +79 -0
  59. package/lib/loop/embed/embedSession.js +136 -0
  60. package/lib/loop/embed/embedTheme.d.ts +12 -9
  61. package/lib/loop/embed/embedTheme.js +32 -22
  62. package/lib/loop/embed/hostBridge.d.ts +79 -0
  63. package/lib/loop/embed/hostBridge.js +104 -0
  64. package/lib/loop/index.d.ts +5 -3
  65. package/lib/loop/index.js +6 -3
  66. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +5 -2
  67. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +7 -1
  68. package/lib/loop/plugins/a2ui-surface/index.d.ts +1 -1
  69. package/lib/loop/plugins/a2ui-surface/index.js +4 -4
  70. package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +27 -0
  71. package/lib/loop/plugins/a2ui-surface/toolResult.js +22 -0
  72. package/lib/loop/plugins/agent-capacity/index.d.ts +3 -1
  73. package/lib/loop/plugins/agent-capacity/index.js +2 -1
  74. package/lib/loop/plugins/chat/ChatView.js +31 -4
  75. package/lib/loop/plugins/chat/index.d.ts +6 -1
  76. package/lib/loop/presets.d.ts +3 -1
  77. package/lib/loop/presets.js +2 -1
  78. package/lib/loop/shell/LoopWorkspace.js +3 -0
  79. package/lib/protocols/BrowserAgentAdapter.d.ts +18 -0
  80. package/lib/protocols/BrowserAgentAdapter.js +35 -2
  81. package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +8 -0
  82. package/lib/runtimes/browser/a2aPeer.d.ts +11 -0
  83. package/lib/runtimes/browser/a2aPeer.js +15 -1
  84. package/lib/runtimes/browser/protocol.d.ts +3 -0
  85. package/lib/runtimes/browser/protocol.js +2 -1
  86. package/lib/specs/agents/agents.js +6 -25
  87. package/lib/specs/apps.js +50 -8
  88. package/lib/specs/appspecSchema.js +258 -0
  89. package/lib/types/agentspecs.d.ts +86 -0
  90. package/lib/types/chat.d.ts +29 -1
  91. package/package.json +1 -1
  92. package/scripts/codegen/generate_agents.py +4 -2
  93. package/scripts/codegen/generate_apps.py +7 -0
  94. package/scripts/codegen/generate_backend_tools.py +4 -12
  95. package/scripts/codegen/generate_cogs.py +3 -1
  96. package/scripts/codegen/generate_ui_plugins.py +22 -9
  97. package/scripts/voice/transcribe.mjs +29 -21
@@ -61,6 +61,7 @@ import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHo
61
61
  import { useAgentInspectorSink } from '../../components/inspector/agentInspector';
62
62
  import { chatInspectorRecorder, } from '../../components/inspector/chatInspect';
63
63
  import { loopMessageChange, speakerOf, withLoopMessage } from './loopMessage';
64
+ import { loopStepOf, withLoopStep } from './loopStep';
64
65
  // Tracks pending prompts already auto-sent for a given conversation scope.
65
66
  // This prevents layout-driven unmount/remount cycles from re-sending prompts.
66
67
  const sentPendingPromptKeys = new Set();
@@ -510,7 +511,7 @@ export function ChatBase(props) {
510
511
  // ---------------------------------------------------------------------------
511
512
  // ChatBaseInner — contains all actual logic
512
513
  // ---------------------------------------------------------------------------
513
- function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = true, showContextRing = false, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = true, showToolsMenu = true, showSkillsMenu = true, disableInputPrompt = false, promptVariant, mentionableAgents: mentionableAgentsProp, showAgentsMenu = true, agents: agentsProp, selectedAgentId, onSelectAgent, disabled: disabledProp, disableReason: disableReasonProp, overlay, launching = false, launchingMessage, autoConnect = true, codemodeEnabled = false, onToggleCodemode, initialModel, availableModels, mcpServers, initialSkills: _initialSkills, className, loadingState, headerActions, kernelIndicatorState, kernelIndicatorPlacement = 'left', kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, themeVariant, colorMode, chatViewMode, onChatViewModeChange, disabledViewModes,
514
+ function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = true, showContextRing = false, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = true, showToolsMenu = true, showSkillsMenu = true, disableInputPrompt = false, promptVariant, mentionableAgents: mentionableAgentsProp, promptCommands, showAgentsMenu = true, agents: agentsProp, selectedAgentId, onSelectAgent, disabled: disabledProp, disableReason: disableReasonProp, overlay, launching = false, launchingMessage, autoConnect = true, codemodeEnabled = false, onToggleCodemode, initialModel, availableModels, mcpServers, initialSkills: _initialSkills, className, loadingState, headerActions, kernelIndicatorState, kernelIndicatorPlacement = 'left', kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, themeVariant, colorMode, chatViewMode, onChatViewModeChange, disabledViewModes,
514
515
  // Mode selection
515
516
  useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady, onLoadingChange, onContextSnapshot, enableStreaming = false,
516
517
  // Extended props
@@ -520,7 +521,7 @@ onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
520
521
  // Identity/Authorization props
521
522
  onAuthorizationRequired: _onAuthorizationRequired, connectedIdentities,
522
523
  // Conversation persistence
523
- runtimeId, historyEndpoint, historyAuthToken: _historyAuthToken,
524
+ runtimeId, thread, historyEndpoint, historyAuthToken: _historyAuthToken,
524
525
  // Pending prompt
525
526
  pendingPrompt, contextSnapshot: externalContextSnapshot, mcpStatusData, sandboxStatusData,
526
527
  // Tool approval banner
@@ -1573,7 +1574,20 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1573
1574
  const suppressAssistantTextForToolOnlyRef = useRef(false);
1574
1575
  const hideMessagesAfterToolUIRef = useRef(hideMessagesAfterToolUI);
1575
1576
  hideMessagesAfterToolUIRef.current = hideMessagesAfterToolUI;
1576
- const threadIdRef = useRef(generateMessageId());
1577
+ const threadIdRef = useRef(thread?.id || generateMessageId());
1578
+ /*
1579
+ * The thread a host gave, kept as it was given at mount (LOOP D-13), and
1580
+ * told when a message is first sent on a thread: the host keeps it.
1581
+ */
1582
+ const threadGivenRef = useRef(thread);
1583
+ const threadStartedRef = useRef(thread?.messages.length ? thread.id : null);
1584
+ const noteThreadStarted = useCallback(() => {
1585
+ const id = threadIdRef.current;
1586
+ if (threadStartedRef.current !== id) {
1587
+ threadStartedRef.current = id;
1588
+ threadGivenRef.current?.onStarted?.(id);
1589
+ }
1590
+ }, []);
1577
1591
  const messagesContainerRef = useRef(null);
1578
1592
  const messagesEndRef = useRef(null);
1579
1593
  const inputRef = useRef(null);
@@ -2131,6 +2145,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
2131
2145
  // Set when the next load must come from a fresh snapshot, not the one in
2132
2146
  // the store: what the store holds is the transcript before the change.
2133
2147
  const freshSnapshotRef = useRef(false);
2148
+ // The given thread drawn, by its id: once.
2149
+ const threadSeededRef = useRef(null);
2134
2150
  useEffect(() => {
2135
2151
  if (historyScopeId !== prevHistoryScopeRef.current) {
2136
2152
  if (historyScopeId) {
@@ -2144,6 +2160,24 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
2144
2160
  }
2145
2161
  if (!historyScopeId)
2146
2162
  return;
2163
+ /*
2164
+ * A thread the host gave (LOOP D-13): its session's conversation, drawn
2165
+ * once, and never replaced by the runtime's snapshot, which is its
2166
+ * agent's — every session's — not this one's.
2167
+ */
2168
+ const given = threadGivenRef.current;
2169
+ if (given) {
2170
+ if (threadSeededRef.current !== given.id) {
2171
+ threadSeededRef.current = given.id;
2172
+ useConversationStore
2173
+ .getState()
2174
+ .setMessages(historyScopeId, given.messages);
2175
+ setDisplayItems(convertHistoryToDisplayItems(given.messages));
2176
+ useConversationStore.getState().markFetched(historyScopeId);
2177
+ setHistoryLoaded(true);
2178
+ }
2179
+ return;
2180
+ }
2147
2181
  if (historyVersion !== historyVersionRef.current) {
2148
2182
  historyVersionRef.current = historyVersion;
2149
2183
  if (historyVersion > 0) {
@@ -2684,6 +2718,12 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
2684
2718
  }
2685
2719
  break;
2686
2720
  case 'activity': {
2721
+ // A step of an application's code, drawn as it runs (LOOP P-16).
2722
+ const step = loopStepOf(event.activity);
2723
+ if (step) {
2724
+ setDisplayItems(prev => withLoopStep(prev, step));
2725
+ break;
2726
+ }
2687
2727
  // An application's code changed a message it sent: its author
2688
2728
  // said, or the message taken away (LOOP P-15).
2689
2729
  const change = loopMessageChange(event.activity);
@@ -3004,6 +3044,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3004
3044
  console.warn('[ChatBase] frontendTools count:', frontendTools?.length ?? 0, 'toolsForRequest:', toolsForRequest.map(t => t.name));
3005
3045
  const enabledMcpToolNames = getEnabledMcpToolNames();
3006
3046
  const enabledSkillIds = getEnabledSkillIds();
3047
+ // The thread is opened by this run: its host keeps it (D-13).
3048
+ noteThreadStarted();
3007
3049
  await adapterRef.current.sendMessage(userMessage, {
3008
3050
  threadId: threadIdRef.current,
3009
3051
  messages: allMessages,
@@ -3051,6 +3093,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3051
3093
  enableStreaming,
3052
3094
  getEnabledMcpToolNames,
3053
3095
  getEnabledSkillIds,
3096
+ noteThreadStarted,
3054
3097
  ]);
3055
3098
  // Send pending prompt once history loaded and adapter/handler available
3056
3099
  useEffect(() => {
@@ -3620,7 +3663,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3620
3663
  somebody typing, and what they would have typed is the message the
3621
3664
  chip sends, not the label on it.
3622
3665
  */
3623
- 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,
3666
+ 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,
3624
3667
  // The runtime's memory of what was sent to this agent, for the arrow
3625
3668
  // keys — part of the same initial state as the models and tools.
3626
3669
  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,
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A step of an application's code, shown in the conversation as it runs
3
+ * (LOOP P-16): the chain of thought.
4
+ *
5
+ * AG-UI's `STEP_STARTED` and `STEP_FINISHED` carry a step's name only; the
6
+ * session API says the step whole beside them, with a `CUSTOM` event named
7
+ * `loop.step` — `{id, name, kind, parent_id, input, output, error,
8
+ * started_at, ended_at}` — which the AG-UI adapter hands on as an
9
+ * `activity`. A step is drawn as the chat draws a tool call: one row,
10
+ * collapsed, its input and output inside, running until it ends; a nested
11
+ * step says the step it is in.
12
+ */
13
+ import type { DisplayItem, ToolCallMessage } from '../../types/chat';
14
+ /** The `CUSTOM` event's name. */
15
+ export declare const LOOP_STEP = "loop.step";
16
+ /** A step, as the event says it. */
17
+ export type LoopStep = {
18
+ id: string;
19
+ name: string;
20
+ kind: string;
21
+ parentId: string | null;
22
+ input: unknown;
23
+ output: unknown;
24
+ error: string;
25
+ ended: boolean;
26
+ };
27
+ /** The step an activity says, or `null` when it says none. */
28
+ export declare function loopStepOf(activity: {
29
+ type: string;
30
+ data: unknown;
31
+ } | undefined): LoopStep | null;
32
+ /** The id of the row a step is drawn as. */
33
+ export declare const stepCallId: (id: string) => string;
34
+ /** A step as the row the chat draws a tool call as. */
35
+ export declare function stepItem(step: LoopStep, inside?: string): ToolCallMessage;
36
+ /**
37
+ * The conversation with the step drawn: its row changed in place when it is
38
+ * there (a step ending), else added after the rest.
39
+ */
40
+ export declare function withLoopStep(items: DisplayItem[], step: LoopStep): DisplayItem[];
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** The `CUSTOM` event's name. */
6
+ export const LOOP_STEP = 'loop.step';
7
+ /** The step an activity says, or `null` when it says none. */
8
+ export function loopStepOf(activity) {
9
+ if (!activity || activity.type !== LOOP_STEP)
10
+ return null;
11
+ const data = activity.data;
12
+ if (!data ||
13
+ typeof data.id !== 'string' ||
14
+ !data.id ||
15
+ typeof data.name !== 'string') {
16
+ return null;
17
+ }
18
+ return {
19
+ id: data.id,
20
+ name: data.name,
21
+ kind: typeof data.kind === 'string' ? data.kind : 'run',
22
+ parentId: typeof data.parent_id === 'string' && data.parent_id
23
+ ? data.parent_id
24
+ : null,
25
+ input: data.input ?? null,
26
+ output: data.output ?? null,
27
+ error: typeof data.error === 'string' ? data.error : '',
28
+ ended: typeof data.ended_at === 'string' && data.ended_at !== '',
29
+ };
30
+ }
31
+ /** The id of the row a step is drawn as. */
32
+ export const stepCallId = (id) => `step-${id}`;
33
+ /** A step as the row the chat draws a tool call as. */
34
+ export function stepItem(step, inside) {
35
+ const callId = stepCallId(step.id);
36
+ const kind = `${step.kind} step`;
37
+ return {
38
+ id: callId,
39
+ type: 'tool-call',
40
+ toolCallId: callId,
41
+ toolName: step.name,
42
+ args: {
43
+ kind: step.kind,
44
+ ...(step.input !== null ? { input: step.input } : {}),
45
+ },
46
+ ...(step.ended && !step.error && step.output !== null
47
+ ? { result: step.output }
48
+ : {}),
49
+ status: !step.ended ? 'executing' : step.error ? 'error' : 'complete',
50
+ ...(step.error ? { error: step.error } : {}),
51
+ summary: inside ? `${kind}, inside ${inside}` : kind,
52
+ };
53
+ }
54
+ /**
55
+ * The conversation with the step drawn: its row changed in place when it is
56
+ * there (a step ending), else added after the rest.
57
+ */
58
+ export function withLoopStep(items, step) {
59
+ const parent = step.parentId
60
+ ? items.find(item => 'toolCallId' in item &&
61
+ item.toolCallId === stepCallId(step.parentId))
62
+ : undefined;
63
+ const row = stepItem(step, parent && 'toolName' in parent ? parent.toolName : undefined);
64
+ let found = false;
65
+ const changed = items.map(item => {
66
+ if ('toolCallId' in item && item.toolCallId === row.toolCallId) {
67
+ found = true;
68
+ return row;
69
+ }
70
+ return item;
71
+ });
72
+ return found ? changed : [...items, row];
73
+ }
@@ -8,5 +8,5 @@ export interface McpStatusIndicatorProps {
8
8
  /** @deprecated — Only used when `data` is not provided. */
9
9
  authToken?: string;
10
10
  }
11
- export declare function McpStatusIndicator({ data: wsData, apiBase, authToken, }: McpStatusIndicatorProps): import("react").JSX.Element;
11
+ export declare function McpStatusIndicator({ data: wsData, apiBase, authToken, }: McpStatusIndicatorProps): import("react").JSX.Element | null;
12
12
  export default McpStatusIndicator;
@@ -10,7 +10,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
10
10
  *
11
11
  * Aggregate logic
12
12
  * ───────────────
13
- * - no servers array / empty → "none" (gray, hidden or subtle)
13
+ * - no servers array / empty → "none" (not drawn: nothing to report)
14
14
  * - any server "starting" → "starting" (amber, pulsing)
15
15
  * - any server "failed" → "failed" (red)
16
16
  * - all servers "started" → "started" (green)
@@ -35,8 +35,6 @@ function deriveAggregate(servers) {
35
35
  return 'not_started';
36
36
  }
37
37
  function buildTooltipText(aggregate, servers) {
38
- if (aggregate === 'none')
39
- return 'No MCP Server defined';
40
38
  const lines = [MCP_STATUS_LABELS[aggregate]];
41
39
  for (const s of servers) {
42
40
  let detail = `• ${s.id}: ${s.status}`;
@@ -75,9 +73,17 @@ export function McpStatusIndicator({ data: wsData, apiBase, authToken, }) {
75
73
  const servers = effectiveData?.servers ?? [];
76
74
  const aggregate = useMemo(() => deriveAggregate(servers), [servers]);
77
75
  const tooltipText = useMemo(() => buildTooltipText(aggregate, servers), [aggregate, servers]);
78
- // Show a subtle gray dot when no MCP servers are configured.
79
- // The tooltip tells the user none are defined.
80
- return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, style: {
76
+ /*
77
+ * Nothing drawn when the agent has no MCP server (LOOP H-21): a control
78
+ * that only says so was read out as *No MCP Server defined* in every
79
+ * composer, an in-page agent's and an application's alike. When there are
80
+ * some, it is named by their state, and the tooltip — its description —
81
+ * lists them.
82
+ */
83
+ if (aggregate === 'none') {
84
+ return null;
85
+ }
86
+ return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": MCP_STATUS_LABELS[aggregate], style: {
81
87
  display: 'inline-flex',
82
88
  alignItems: 'center',
83
89
  justifyContent: 'center',
@@ -98,6 +104,7 @@ export function McpStatusIndicator({ data: wsData, apiBase, authToken, }) {
98
104
  ...(aggregate === 'starting' && {
99
105
  animation: 'mcp-pulse 1.5s ease-in-out infinite',
100
106
  }),
107
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
101
108
  } }) }) }));
102
109
  }
103
110
  export default McpStatusIndicator;
@@ -3,5 +3,5 @@ export interface SkillsStatusIndicatorProps {
3
3
  enabledCount: number;
4
4
  loading?: boolean;
5
5
  }
6
- export declare function SkillsStatusIndicator({ skillsCount, enabledCount, loading, }: SkillsStatusIndicatorProps): import("react").JSX.Element;
6
+ export declare function SkillsStatusIndicator({ skillsCount, enabledCount, loading, }: SkillsStatusIndicatorProps): import("react").JSX.Element | null;
7
7
  export default SkillsStatusIndicator;
@@ -7,7 +7,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  * SkillsStatusIndicator — Round status dot representing skill state.
8
8
  *
9
9
  * Aggregate logic:
10
- * - no skills configured -> none (gray)
10
+ * - no skills configured -> none (not drawn)
11
11
  * - skills loading -> loading (amber, pulsing)
12
12
  * - some enabled -> active (green)
13
13
  * - none enabled -> inactive (gray)
@@ -48,8 +48,6 @@ function deriveAggregate(skillsCount, enabledCount, loading) {
48
48
  return 'inactive';
49
49
  }
50
50
  function buildTooltip(aggregate, skillsCount, enabledCount) {
51
- if (aggregate === 'none')
52
- return 'No Skills defined';
53
51
  if (aggregate === 'loading') {
54
52
  return `Skills loading (${enabledCount}/${skillsCount} enabled)`;
55
53
  }
@@ -62,6 +60,11 @@ export function SkillsStatusIndicator({ skillsCount, enabledCount, loading = fal
62
60
  useInjectKeyframes();
63
61
  const aggregate = useMemo(() => deriveAggregate(skillsCount, enabledCount, loading), [skillsCount, enabledCount, loading]);
64
62
  const tooltipText = useMemo(() => buildTooltip(aggregate, skillsCount, enabledCount), [aggregate, skillsCount, enabledCount]);
63
+ // Nothing drawn when the agent has no skill (LOOP H-21): a control that
64
+ // only says so was read out as *No Skills defined* in every composer.
65
+ if (aggregate === 'none') {
66
+ return null;
67
+ }
65
68
  return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, style: {
66
69
  display: 'inline-flex',
67
70
  alignItems: 'center',
@@ -83,6 +86,7 @@ export function SkillsStatusIndicator({ skillsCount, enabledCount, loading = fal
83
86
  ...(aggregate === 'loading' && {
84
87
  animation: 'skills-pulse 1.5s ease-in-out infinite',
85
88
  }),
89
+ '@media (prefers-reduced-motion: reduce)': { animation: 'none' },
86
90
  } }) }) }));
87
91
  }
88
92
  export default SkillsStatusIndicator;
@@ -14,6 +14,7 @@ import type { BuiltinTool, ContextSnapshotData, MCPServerConfig, McpToolsetsStat
14
14
  import { type InputPromptVariant } from './InputPromptBase';
15
15
  import type { PromptSuggestion } from './menus/SuggestionsMenu';
16
16
  import type { MentionableAgent } from './plugins/AgentMentionPlugin';
17
+ import type { PromptCommand } from './plugins/CommandPlugin';
17
18
  /** One agent the footer may offer. */
18
19
  import type { FooterAgent } from '../../types/chat';
19
20
  import type { Decisions } from '../base/modelChoice';
@@ -111,6 +112,8 @@ export interface InputPromptProps {
111
112
  promptVariant?: InputPromptVariant;
112
113
  /** Agents the prompt may address by typing `@`. Lexical only. */
113
114
  mentionableAgents?: MentionableAgent[];
115
+ /** Commands listed while `/` is typed, before the prompt's own (LOOP P-19). Lexical only. */
116
+ promptCommands?: PromptCommand[];
114
117
  /**
115
118
  * What was sent to this agent before this page loaded, oldest first.
116
119
  *
@@ -223,4 +226,4 @@ export interface InputPromptProps {
223
226
  /** Pre-fetched MCP status from WebSocket — bypasses REST polling */
224
227
  mcpStatusData?: McpToolsetsStatusResponse | null;
225
228
  }
226
- export declare function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor, draggable, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt, promptVariant, mentionableAgents, promptHistory, headerContent, showAgentsMenu, showInlineAgentsMenu, agents, selectedAgentId, onSelectAgent, showTokenUsage, showContextRing, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading, models, decisions, selectedModel, onModelSelect, availableTools, availableFrontendTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }: InputPromptProps): import("react").JSX.Element;
229
+ export declare function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor, draggable, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt, promptVariant, mentionableAgents, promptCommands, promptHistory, headerContent, showAgentsMenu, showInlineAgentsMenu, agents, selectedAgentId, onSelectAgent, showTokenUsage, showContextRing, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading, models, decisions, selectedModel, onModelSelect, availableTools, availableFrontendTools, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, skills, skillsLoading, enabledSkills, onToggleSkill, onToggleAllSkills, approvedSkills, onToggleSkillApproval, apiBase, authToken, mcpStatusData, }: InputPromptProps): import("react").JSX.Element;
@@ -29,7 +29,7 @@ const EMPTY_ID_SET = new Set();
29
29
  // ---------------------------------------------------------------------------
30
30
  // Component
31
31
  // ---------------------------------------------------------------------------
32
- export function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor = 'bottom', draggable = false, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt = false, promptVariant, mentionableAgents, promptHistory, headerContent, showAgentsMenu = false, showInlineAgentsMenu = true, agents = [], selectedAgentId, onSelectAgent, showTokenUsage, showContextRing = false, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu = true, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading = false, models = [], decisions, selectedModel = '', onModelSelect = () => { }, availableTools = [], availableFrontendTools = [], mcpServers = [], enabledMcpTools = EMPTY_TOOL_MAP, enabledMcpToolCount = 0, onToggleMcpTool = () => { }, onToggleAllMcpServerTools = () => { }, approvedMcpTools = EMPTY_TOOL_MAP, onToggleMcpToolApproval = () => { }, skills = [], skillsLoading = false, enabledSkills = EMPTY_ID_SET, onToggleSkill = () => { }, onToggleAllSkills = () => { }, approvedSkills = EMPTY_ID_SET, onToggleSkillApproval = () => { }, apiBase, authToken, mcpStatusData, }) {
32
+ export function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor = 'bottom', draggable = false, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt = false, promptVariant, mentionableAgents, promptCommands, promptHistory, headerContent, showAgentsMenu = false, showInlineAgentsMenu = true, agents = [], selectedAgentId, onSelectAgent, showTokenUsage, showContextRing = false, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu = true, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading = false, models = [], decisions, selectedModel = '', onModelSelect = () => { }, availableTools = [], availableFrontendTools = [], mcpServers = [], enabledMcpTools = EMPTY_TOOL_MAP, enabledMcpToolCount = 0, onToggleMcpTool = () => { }, onToggleAllMcpServerTools = () => { }, approvedMcpTools = EMPTY_TOOL_MAP, onToggleMcpToolApproval = () => { }, skills = [], skillsLoading = false, enabledSkills = EMPTY_ID_SET, onToggleSkill = () => { }, onToggleAllSkills = () => { }, approvedSkills = EMPTY_ID_SET, onToggleSkillApproval = () => { }, apiBase, authToken, mcpStatusData, }) {
33
33
  const isKernelBusy = kernelStatus === 'busy';
34
34
  /*
35
35
  * Each menu is offered when it is switched on *and* has something behind it.
@@ -105,7 +105,7 @@ export function InputPrompt({ input, setInput, isLoading, kernelStatus, connecti
105
105
  * from the start; this shows it.
106
106
  */
107
107
  const hasContext = Boolean(agentUsage && !agentUsage.error);
108
- const body = (_jsxs(Box, { children: [leadingPanel, _jsx(InputPromptBase, { variant: promptVariant, mentionableAgents: mentionableAgents, promptHistory: promptHistory,
108
+ const body = (_jsxs(Box, { children: [leadingPanel, _jsx(InputPromptBase, { variant: promptVariant, mentionableAgents: mentionableAgents, promptCommands: promptCommands, promptHistory: promptHistory,
109
109
  /*
110
110
  Whatever the host puts inside the prompt, and the agent chip beside
111
111
  it. Both, rather than either: a host contributing its own controls
@@ -17,6 +17,7 @@
17
17
  import type { ReactNode } from 'react';
18
18
  import type { PromptStack } from './stack';
19
19
  import type { MentionableAgent } from './plugins/AgentMentionPlugin';
20
+ import type { PromptCommand } from './plugins/CommandPlugin';
20
21
  /** Input variant type. */
21
22
  export type InputPromptVariant = 'text' | 'lexical';
22
23
  /**
@@ -75,6 +76,8 @@ export interface InputPromptBaseProps {
75
76
  * suggestion a person cannot see is worse than none.
76
77
  */
77
78
  mentionableAgents?: MentionableAgent[];
79
+ /** Commands listed while `/` is typed, before the prompt's own (LOOP P-19). Lexical only. */
80
+ promptCommands?: PromptCommand[];
78
81
  /**
79
82
  * What was sent to this agent before this composer mounted, oldest first.
80
83
  *
@@ -108,5 +111,5 @@ export interface InputPromptBaseProps {
108
111
  /**
109
112
  * InputPromptBase — the editor, its header and its footer with header, input area, and footer.
110
113
  */
111
- export declare function InputPromptBase({ variant, placeholder, typingSuggestions, isLoading, isKernelBusy, onSend, onStop, autoFocus, focusTrigger, showBorderTop, showBackground, padding, disabled, readOnly, mentionableAgents, promptHistory, sx, value: controlledValue, onChange: controlledOnChange, headerContent, headerStacks, footerContent, footerStacks, footerRightContent, }: InputPromptBaseProps): import("react").JSX.Element;
114
+ export declare function InputPromptBase({ variant, placeholder, typingSuggestions, isLoading, isKernelBusy, onSend, onStop, autoFocus, focusTrigger, showBorderTop, showBackground, padding, disabled, readOnly, mentionableAgents, promptCommands, promptHistory, sx, value: controlledValue, onChange: controlledOnChange, headerContent, headerStacks, footerContent, footerStacks, footerRightContent, }: InputPromptBaseProps): import("react").JSX.Element;
112
115
  export default InputPromptBase;
@@ -10,7 +10,7 @@ import { usePromptHistory } from './promptHistory';
10
10
  /**
11
11
  * InputPromptBase — the editor, its header and its footer with header, input area, and footer.
12
12
  */
13
- export function InputPromptBase({ variant = 'lexical', placeholder = 'Ask anything…', typingSuggestions, isLoading = false, isKernelBusy = false, onSend, onStop, autoFocus = false, focusTrigger, showBorderTop = true, showBackground = true, padding = 3, disabled = false, readOnly = false, mentionableAgents, promptHistory, sx, value: controlledValue, onChange: controlledOnChange, headerContent, headerStacks, footerContent, footerStacks, footerRightContent, }) {
13
+ export function InputPromptBase({ variant = 'lexical', placeholder = 'Ask anything…', typingSuggestions, isLoading = false, isKernelBusy = false, onSend, onStop, autoFocus = false, focusTrigger, showBorderTop = true, showBackground = true, padding = 3, disabled = false, readOnly = false, mentionableAgents, promptCommands, promptHistory, sx, value: controlledValue, onChange: controlledOnChange, headerContent, headerStacks, footerContent, footerStacks, footerRightContent, }) {
14
14
  // ---- Controlled / uncontrolled state -----------------------------------
15
15
  const [internalInput, setInternalInput] = useState('');
16
16
  const input = controlledValue !== undefined ? controlledValue : internalInput;
@@ -221,6 +221,6 @@ export function InputPromptBase({ variant = 'lexical', placeholder = 'Ask anythi
221
221
  borderColor: 'accent.fg',
222
222
  boxShadow: (t) => `0 0 0 1px ${t?.colors?.accent?.fg ?? '#16A085'}`,
223
223
  },
224
- }, children: [_jsx(InPromptHeader, { stacks: headerStacks, children: headerContent }), variant === 'lexical' ? (_jsx(InputPromptLexical, { value: input, onChange: handleChange, placeholder: animatedPlaceholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, onHistory: navigateHistory, autoFocus: autoFocus, focusSignal: lexicalFocusSignal, mentionableAgents: mentionableAgents })) : (_jsx(InputPromptText, { value: input, onChange: handleChange, placeholder: animatedPlaceholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, onHistory: navigateHistory, inputRef: inputRef })), _jsx(InPromptFooter, { stacks: footerStacks, isLoading: isLoading, isKernelBusy: isKernelBusy, sendDisabled: !input.trim() || disabled || readOnly, onSend: handleSend, onStop: handleStop, rightContent: footerRightContent, children: footerContent })] }) }) }));
224
+ }, children: [_jsx(InPromptHeader, { stacks: headerStacks, children: headerContent }), variant === 'lexical' ? (_jsx(InputPromptLexical, { value: input, onChange: handleChange, placeholder: animatedPlaceholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, onHistory: navigateHistory, autoFocus: autoFocus, focusSignal: lexicalFocusSignal, mentionableAgents: mentionableAgents, promptCommands: promptCommands })) : (_jsx(InputPromptText, { value: input, onChange: handleChange, placeholder: animatedPlaceholder, disabled: isLoading || disabled, readOnly: readOnly, onSubmit: handleSend, onHistory: navigateHistory, inputRef: inputRef })), _jsx(InPromptFooter, { stacks: footerStacks, isLoading: isLoading, isKernelBusy: isKernelBusy, sendDisabled: !input.trim() || disabled || readOnly, onSend: handleSend, onStop: handleStop, rightContent: footerRightContent, children: footerContent })] }) }) }));
225
225
  }
226
226
  export default InputPromptBase;
@@ -1,4 +1,5 @@
1
1
  import { type MentionableAgent } from './plugins/AgentMentionPlugin';
2
+ import { type PromptCommand } from './plugins/CommandPlugin';
2
3
  import type { HistoryDirection } from './promptHistory';
3
4
  export interface InputPromptLexicalProps {
4
5
  /** Current input value */
@@ -30,6 +31,11 @@ export interface InputPromptLexicalProps {
30
31
  * choose between, and a menu on every `@` would be in the way.
31
32
  */
32
33
  mentionableAgents?: MentionableAgent[];
34
+ /**
35
+ * Commands listed while `/` is typed, before the prompt's own: an
36
+ * application's (LOOP P-19).
37
+ */
38
+ promptCommands?: PromptCommand[];
33
39
  }
34
- export declare function InputPromptLexical({ value, onChange, placeholder, disabled, readOnly, onSubmit, onHistory, autoFocus, focusSignal, mentionableAgents, }: InputPromptLexicalProps): import("react").JSX.Element;
40
+ export declare function InputPromptLexical({ value, onChange, placeholder, disabled, readOnly, onSubmit, onHistory, autoFocus, focusSignal, mentionableAgents, promptCommands, }: InputPromptLexicalProps): import("react").JSX.Element;
35
41
  export default InputPromptLexical;
@@ -28,7 +28,7 @@ import { PlainTextExtension } from '@lexical/plain-text';
28
28
  import { MentionNode } from './plugins/MentionNode';
29
29
  import { Box } from '@datalayer/primer-addons';
30
30
  import { AgentMentionPlugin, } from './plugins/AgentMentionPlugin';
31
- import { CommandPlugin, PROMPT_COMMANDS } from './plugins/CommandPlugin';
31
+ import { CommandPlugin, PROMPT_COMMANDS, } from './plugins/CommandPlugin';
32
32
  // ---- Lexical extension (plain-text only) ---------------------------------
33
33
  // Module scope on purpose: the composer rebuilds the editor whenever this
34
34
  // reference changes.
@@ -265,7 +265,7 @@ function ReadOnlyPlugin({ readOnly }) {
265
265
  }, [editor, readOnly]);
266
266
  return null;
267
267
  }
268
- export function InputPromptLexical({ value, onChange, placeholder = 'Ask anything…', disabled = false, readOnly = false, onSubmit, onHistory, autoFocus = false, focusSignal, mentionableAgents, }) {
268
+ export function InputPromptLexical({ value, onChange, placeholder = 'Ask anything…', disabled = false, readOnly = false, onSubmit, onHistory, autoFocus = false, focusSignal, mentionableAgents, promptCommands, }) {
269
269
  return (_jsx(Box, { sx: {
270
270
  /*
271
271
  The placeholder is positioned against this box.
@@ -321,6 +321,8 @@ export function InputPromptLexical({ value, onChange, placeholder = 'Ask anythin
321
321
  fontSize: 14,
322
322
  lineHeight: '1.5',
323
323
  padding: '2px 0',
324
- } }), _jsx(SyncPlugin, { value: value, onChange: onChange }), _jsx(ReadOnlyPlugin, { readOnly: readOnly || disabled }), _jsx(EnterSubmitPlugin, { onSubmit: onSubmit, disabled: disabled, readOnly: readOnly }), _jsx(AutoFocusPlugin, { autoFocus: autoFocus, focusSignal: focusSignal }), _jsx(CommandPlugin, { commands: PROMPT_COMMANDS }), mentionableAgents?.length ? (_jsx(AgentMentionPlugin, { agents: mentionableAgents })) : null, _jsx(HistoryPlugin, { onHistory: onHistory, disabled: disabled, readOnly: readOnly })] }) }));
324
+ } }), _jsx(SyncPlugin, { value: value, onChange: onChange }), _jsx(ReadOnlyPlugin, { readOnly: readOnly || disabled }), _jsx(EnterSubmitPlugin, { onSubmit: onSubmit, disabled: disabled, readOnly: readOnly }), _jsx(AutoFocusPlugin, { autoFocus: autoFocus, focusSignal: focusSignal }), _jsx(CommandPlugin, { commands: promptCommands?.length
325
+ ? [...promptCommands, ...PROMPT_COMMANDS]
326
+ : PROMPT_COMMANDS }), mentionableAgents?.length ? (_jsx(AgentMentionPlugin, { agents: mentionableAgents })) : null, _jsx(HistoryPlugin, { onHistory: onHistory, disabled: disabled, readOnly: readOnly })] }) }));
325
327
  }
326
328
  export default InputPromptLexical;
@@ -9,6 +9,7 @@
9
9
  export * from './a2aTeamFlow';
10
10
  export * from './teamConnections';
11
11
  export * from './useA2ATeam';
12
+ export * from './usePeerSandbox';
12
13
  export * from './A2ATeamGraph';
13
14
  export * from './TeamNotebook';
14
15
  export * from './NotebookPreview';
@@ -13,6 +13,7 @@
13
13
  export * from './a2aTeamFlow';
14
14
  export * from './teamConnections';
15
15
  export * from './useA2ATeam';
16
+ export * from './usePeerSandbox';
16
17
  export * from './A2ATeamGraph';
17
18
  export * from './TeamNotebook';
18
19
  export * from './NotebookPreview';
@@ -0,0 +1,13 @@
1
+ import { type AssistantSandbox } from '../../chat/assistant/assistantDetails';
2
+ /**
3
+ * The runtime an A2A address is served from: its base, before
4
+ * `/api/v1/a2a/agents/<id>`.
5
+ */
6
+ export declare function runtimeBaseOfA2AUrl(url: string): string;
7
+ /**
8
+ * The sandbox of the runtime a peer answers from, read again each time
9
+ * `refresh` changes (when a turn ends, it may have started one). Until the
10
+ * runtime says, or when it does not answer, the peer's sandbox is offered
11
+ * as not running, with the runtime it would be on.
12
+ */
13
+ export declare function usePeerSandbox(a2aUrl: string, refresh?: unknown): AssistantSandbox;
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A peer's code sandbox, for its *Code Sandbox Details…*: the codemode
7
+ * sandbox of the runtime it is served from, as that runtime reports it.
8
+ *
9
+ * @module components/teams/usePeerSandbox
10
+ */
11
+ import { useEffect, useState } from 'react';
12
+ import { assistantSandboxOf, } from '../../chat/assistant/assistantDetails';
13
+ /**
14
+ * The runtime an A2A address is served from: its base, before
15
+ * `/api/v1/a2a/agents/<id>`.
16
+ */
17
+ export function runtimeBaseOfA2AUrl(url) {
18
+ return url
19
+ .replace(/\/api\/v1\/a2a\/agents\/[^/]+\/?$/, '')
20
+ .replace(/\/+$/, '');
21
+ }
22
+ /**
23
+ * The sandbox of the runtime a peer answers from, read again each time
24
+ * `refresh` changes (when a turn ends, it may have started one). Until the
25
+ * runtime says, or when it does not answer, the peer's sandbox is offered
26
+ * as not running, with the runtime it would be on.
27
+ */
28
+ export function usePeerSandbox(a2aUrl, refresh) {
29
+ const serverUrl = runtimeBaseOfA2AUrl(a2aUrl);
30
+ const [sandbox, setSandbox] = useState({
31
+ kind: 'runtime',
32
+ status: 'not running',
33
+ serverUrl,
34
+ });
35
+ useEffect(() => {
36
+ let current = true;
37
+ fetch(`${serverUrl}/api/v1/configure/codemode/status`)
38
+ .then(response => (response.ok ? response.json() : null))
39
+ .then((status) => {
40
+ if (!current) {
41
+ return;
42
+ }
43
+ setSandbox(assistantSandboxOf(status?.sandbox, { serverUrl }) ?? {
44
+ kind: 'runtime',
45
+ status: 'not running',
46
+ serverUrl,
47
+ });
48
+ })
49
+ .catch(() => {
50
+ if (current) {
51
+ setSandbox({ kind: 'runtime', status: 'unreachable', serverUrl });
52
+ }
53
+ });
54
+ return () => {
55
+ current = false;
56
+ };
57
+ }, [serverUrl, refresh]);
58
+ return sandbox;
59
+ }
@@ -10,7 +10,7 @@ import { useExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
10
10
  import { useBrowserInference } from '../hooks/useBrowserInference';
11
11
  import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
12
12
  import { connectA2APeer } from '../runtimes/browser/a2aPeer';
13
- import { A2ATeamGraph, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, } from '../components/teams';
13
+ import { A2ATeamGraph, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
14
14
  import { ACCOUNTING_APP_0_0_1, SALES_APP_0_0_1 } from '../specs/apps';
15
15
  import { AgentInspector, classifyToolCall, inspectA2AFetch, useAgentInspector, } from '../components/inspector';
16
16
  import { SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1 } from '../specs/teams/teams';
@@ -89,6 +89,9 @@ function AgentA2ATeam() {
89
89
  accept: NOTEBOOK_AND_WORDS,
90
90
  inspector,
91
91
  });
92
+ // Accounting works in codemode: its sandbox, on the runtime it answers
93
+ // from, read again when a turn ends.
94
+ const accountingSandbox = usePeerSandbox(accountingUrl, team.busy);
92
95
  // Sales' starters, in its balloon while it waits for a question.
93
96
  const suggestions = useMemo(() => (SALES.interface.starters ?? []).map(starter => ({
94
97
  label: starter.label,
@@ -142,7 +145,8 @@ function AgentA2ATeam() {
142
145
  }, peer: {
143
146
  id: ACCOUNTING.id,
144
147
  name: ACCOUNTING.name,
145
- emoji: ACCOUNTING.emoji,
148
+ // Its face as its card says it, once it is reached.
149
+ emoji: peer?.face?.emoji ?? ACCOUNTING.emoji,
146
150
  character: ACCOUNTING.interface.assistant ?? 'wizard',
147
151
  where: accountingWhere,
148
152
  persona: team.peerPersona,
@@ -150,6 +154,7 @@ function AgentA2ATeam() {
150
154
  history: team.peerHistory,
151
155
  connections: ACCOUNTING_CONNECTIONS,
152
156
  inspector,
157
+ sandbox: accountingSandbox,
153
158
  about: {
154
159
  name: ACCOUNTING.name,
155
160
  spec: `${ACCOUNTING.id}:${ACCOUNTING.version}`,