@datalayer/agent-runtimes 1.3.81 → 1.3.83

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/assistant/SpeechBalloon.js +16 -1
  2. package/lib/chat/base/ChatBase.js +40 -4
  3. package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
  4. package/lib/chat/indicators/McpStatusIndicator.js +13 -6
  5. package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
  6. package/lib/chat/indicators/SkillsStatusIndicator.js +7 -3
  7. package/lib/chat/prompt/InputPrompt.d.ts +4 -1
  8. package/lib/chat/prompt/InputPrompt.js +2 -2
  9. package/lib/chat/prompt/InputPromptBase.d.ts +4 -1
  10. package/lib/chat/prompt/InputPromptBase.js +2 -2
  11. package/lib/chat/prompt/InputPromptLexical.d.ts +7 -1
  12. package/lib/chat/prompt/InputPromptLexical.js +5 -3
  13. package/lib/components/a2ui/datalayer/Form.js +19 -2
  14. package/lib/components/teams/index.d.ts +1 -0
  15. package/lib/components/teams/index.js +1 -0
  16. package/lib/components/teams/usePeerSandbox.d.ts +13 -0
  17. package/lib/components/teams/usePeerSandbox.js +59 -0
  18. package/lib/examples/AgentA2ATeamExample.js +7 -2
  19. package/lib/loop/apps/AppComposer.d.ts +31 -0
  20. package/lib/loop/apps/AppComposer.js +67 -0
  21. package/lib/loop/apps/AppRenderer.d.ts +16 -5
  22. package/lib/loop/apps/AppRenderer.js +49 -16
  23. package/lib/loop/apps/appspec.d.ts +8 -1
  24. package/lib/loop/apps/appspec.js +88 -23
  25. package/lib/loop/apps/checks.js +125 -8
  26. package/lib/loop/apps/composer.d.ts +37 -0
  27. package/lib/loop/apps/composer.js +61 -0
  28. package/lib/loop/apps/index.d.ts +2 -0
  29. package/lib/loop/apps/index.js +2 -0
  30. package/lib/loop/core/index.d.ts +31 -0
  31. package/lib/loop/core/index.js +22 -0
  32. package/lib/loop/embed/AppEmbed.d.ts +46 -7
  33. package/lib/loop/embed/AppEmbed.js +108 -26
  34. package/lib/loop/embed/LoopEmbed.js +2 -0
  35. package/lib/loop/embed/element.js +19 -5
  36. package/lib/loop/embed/embedConfig.d.ts +10 -1
  37. package/lib/loop/embed/embedConfig.js +12 -1
  38. package/lib/loop/embed/embedSession.d.ts +79 -0
  39. package/lib/loop/embed/embedSession.js +136 -0
  40. package/lib/loop/embed/embedTheme.d.ts +12 -9
  41. package/lib/loop/embed/embedTheme.js +32 -22
  42. package/lib/loop/index.d.ts +4 -3
  43. package/lib/loop/index.js +4 -3
  44. package/lib/loop/plugins/agent-capacity/index.d.ts +3 -1
  45. package/lib/loop/plugins/agent-capacity/index.js +2 -1
  46. package/lib/loop/plugins/app-page/appPageModel.d.ts +14 -5
  47. package/lib/loop/plugins/app-page/appPageModel.js +80 -88
  48. package/lib/loop/plugins/chat/ChatView.js +31 -4
  49. package/lib/loop/plugins/chat/index.d.ts +6 -1
  50. package/lib/loop/presets.d.ts +3 -1
  51. package/lib/loop/presets.js +2 -1
  52. package/lib/loop/shell/LoopWorkspace.js +3 -0
  53. package/lib/protocols/BrowserAgentAdapter.d.ts +18 -0
  54. package/lib/protocols/BrowserAgentAdapter.js +35 -2
  55. package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +8 -0
  56. package/lib/runtimes/browser/a2aPeer.d.ts +11 -0
  57. package/lib/runtimes/browser/a2aPeer.js +15 -1
  58. package/lib/runtimes/browser/protocol.d.ts +3 -0
  59. package/lib/runtimes/browser/protocol.js +2 -1
  60. package/lib/specs/agents/agents.js +6 -3
  61. package/lib/specs/apps.js +216 -176
  62. package/lib/specs/appspecSchema.js +205 -84
  63. package/lib/specs/cogs.js +3 -2
  64. package/lib/types/agentspecs.d.ts +92 -7
  65. package/lib/types/chat.d.ts +29 -1
  66. package/package.json +1 -1
  67. package/scripts/codegen/generate_agents.py +4 -2
  68. package/scripts/codegen/generate_apps.py +0 -1
  69. package/scripts/codegen/generate_backend_tools.py +4 -12
  70. package/scripts/codegen/generate_cogs.py +3 -1
  71. package/scripts/codegen/generate_ui_plugins.py +22 -9
  72. package/scripts/voice/transcribe.mjs +29 -21
@@ -132,7 +132,22 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
132
132
  borderColor: current ? 'accent.muted' : 'border.default',
133
133
  transform: side === 'above' ? 'rotate(45deg)' : 'rotate(225deg)',
134
134
  },
135
- }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'flex-start', gap: 1 }, children: [current && !approval ? (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", onClick: onOpen, sx: {
135
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'flex-start', gap: 1 }, children: [current && !approval ? (
136
+ // Not a <button>: the body draws the chat's tool card, a button of
137
+ // its own, and a click on that card is the card's alone.
138
+ _jsx(Box, { role: "button", tabIndex: 0, "data-balloon-peek": "", onClick: (event) => {
139
+ const inner = event.target.closest('button');
140
+ if (inner && event.currentTarget.contains(inner)) {
141
+ return;
142
+ }
143
+ onOpen();
144
+ }, onKeyDown: (event) => {
145
+ if (event.target === event.currentTarget &&
146
+ (event.key === 'Enter' || event.key === ' ')) {
147
+ event.preventDefault();
148
+ onOpen();
149
+ }
150
+ }, sx: {
136
151
  flex: 1,
137
152
  minWidth: 0,
138
153
  m: 0,
@@ -511,7 +511,7 @@ export function ChatBase(props) {
511
511
  // ---------------------------------------------------------------------------
512
512
  // ChatBaseInner — contains all actual logic
513
513
  // ---------------------------------------------------------------------------
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, 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,
515
515
  // Mode selection
516
516
  useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady, onLoadingChange, onContextSnapshot, enableStreaming = false,
517
517
  // Extended props
@@ -521,7 +521,7 @@ onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
521
521
  // Identity/Authorization props
522
522
  onAuthorizationRequired: _onAuthorizationRequired, connectedIdentities,
523
523
  // Conversation persistence
524
- runtimeId, historyEndpoint, historyAuthToken: _historyAuthToken,
524
+ runtimeId, thread, historyEndpoint, historyAuthToken: _historyAuthToken,
525
525
  // Pending prompt
526
526
  pendingPrompt, contextSnapshot: externalContextSnapshot, mcpStatusData, sandboxStatusData,
527
527
  // Tool approval banner
@@ -1574,7 +1574,20 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1574
1574
  const suppressAssistantTextForToolOnlyRef = useRef(false);
1575
1575
  const hideMessagesAfterToolUIRef = useRef(hideMessagesAfterToolUI);
1576
1576
  hideMessagesAfterToolUIRef.current = hideMessagesAfterToolUI;
1577
- 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
+ }, []);
1578
1591
  const messagesContainerRef = useRef(null);
1579
1592
  const messagesEndRef = useRef(null);
1580
1593
  const inputRef = useRef(null);
@@ -2132,6 +2145,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
2132
2145
  // Set when the next load must come from a fresh snapshot, not the one in
2133
2146
  // the store: what the store holds is the transcript before the change.
2134
2147
  const freshSnapshotRef = useRef(false);
2148
+ // The given thread drawn, by its id: once.
2149
+ const threadSeededRef = useRef(null);
2135
2150
  useEffect(() => {
2136
2151
  if (historyScopeId !== prevHistoryScopeRef.current) {
2137
2152
  if (historyScopeId) {
@@ -2145,6 +2160,24 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
2145
2160
  }
2146
2161
  if (!historyScopeId)
2147
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
+ }
2148
2181
  if (historyVersion !== historyVersionRef.current) {
2149
2182
  historyVersionRef.current = historyVersion;
2150
2183
  if (historyVersion > 0) {
@@ -3011,6 +3044,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3011
3044
  console.warn('[ChatBase] frontendTools count:', frontendTools?.length ?? 0, 'toolsForRequest:', toolsForRequest.map(t => t.name));
3012
3045
  const enabledMcpToolNames = getEnabledMcpToolNames();
3013
3046
  const enabledSkillIds = getEnabledSkillIds();
3047
+ // The thread is opened by this run: its host keeps it (D-13).
3048
+ noteThreadStarted();
3014
3049
  await adapterRef.current.sendMessage(userMessage, {
3015
3050
  threadId: threadIdRef.current,
3016
3051
  messages: allMessages,
@@ -3058,6 +3093,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3058
3093
  enableStreaming,
3059
3094
  getEnabledMcpToolNames,
3060
3095
  getEnabledSkillIds,
3096
+ noteThreadStarted,
3061
3097
  ]);
3062
3098
  // Send pending prompt once history loaded and adapter/handler available
3063
3099
  useEffect(() => {
@@ -3627,7 +3663,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3627
3663
  somebody typing, and what they would have typed is the message the
3628
3664
  chip sends, not the label on it.
3629
3665
  */
3630
- 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,
3631
3667
  // The runtime's memory of what was sent to this agent, for the arrow
3632
3668
  // keys — part of the same initial state as the models and tools.
3633
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,
@@ -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;
@@ -12,6 +12,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  * its action is dispatched, so the action's context reads them. A form the
13
13
  * schema refuses says why beside each field and sends nothing.
14
14
  *
15
+ * A form without an action is a set of settings (C-16) — an application's
16
+ * `interface.settings` drawn beside its conversation: no button, its values
17
+ * written where `values` points as they are filled, for the next run to take;
18
+ * the runtime checks them against the same schema.
19
+ *
15
20
  * @module components/a2ui/datalayer/Form
16
21
  */
17
22
  import { useEffect, useMemo, useState } from 'react';
@@ -34,11 +39,23 @@ export function FormView({ props }) {
34
39
  useEffect(() => {
35
40
  setLocal(JSON.parse(shownKey));
36
41
  }, [shownKey]);
37
- const uiSchema = useMemo(() => ({ 'ui:submitButtonOptions': { submitText: submitLabel } }), [submitLabel]);
42
+ // Without an action, nothing is sent: no button, the values written as filled.
43
+ const live = action === undefined;
44
+ const uiSchema = useMemo(() => ({
45
+ 'ui:submitButtonOptions': live
46
+ ? { norender: true }
47
+ : { submitText: submitLabel },
48
+ }), [live, submitLabel]);
38
49
  const problem = schema.type !== undefined && schema.type !== 'object'
39
50
  ? 'Its fields are not an object schema: a form asks for several named values.'
40
51
  : null;
41
- return (_jsx(BlockFrame, { title: title, label: "Form", weight: props.weight, testId: "a2ui-form", children: problem ? (_jsx(Problem, { children: problem })) : (_jsx(SchemaForm, { schema: schema, uiSchema: uiSchema, formData: values, validator: validator, liveValidate: touched, onBlur: () => setTouched(true), onError: () => setTouched(true), showErrorList: false, onChange: event => setLocal((event.formData ?? {})), onSubmit: event => {
52
+ return (_jsx(BlockFrame, { title: title, label: "Form", weight: props.weight, testId: "a2ui-form", children: problem ? (_jsx(Problem, { children: problem })) : (_jsx(SchemaForm, { schema: schema, uiSchema: uiSchema, formData: values, validator: validator, liveValidate: touched, onBlur: () => setTouched(true), onError: () => setTouched(true), showErrorList: false, onChange: event => {
53
+ const filled = (event.formData ?? {});
54
+ setLocal(filled);
55
+ if (live) {
56
+ setValues(filled);
57
+ }
58
+ }, onSubmit: event => {
42
59
  setValues((event.formData ?? {}));
43
60
  action?.();
44
61
  } })) }));
@@ -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}`,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * An application's commands and modes in the composer (LOOP P-19).
3
+ *
4
+ * - Each of its **commands** is a workspace command (`LoopCommand`), listed
5
+ * in the composer's `/` menu (`composer`): picking it writes `/<name> `,
6
+ * and sending runs it — its prompt goes to the agent, the words typed
7
+ * after it in place of `{input}`.
8
+ * - Its **modes** are switches beside the composer (`LoopSlots.promptAction`),
9
+ * one menu each; the option picked goes with every run as
10
+ * `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
11
+ * agent its instructions, on the model it names, for that run.
12
+ *
13
+ * @module loop/apps/AppComposer
14
+ */
15
+ import type { JSX } from 'react';
16
+ import type { AppSpec } from '../../types/agentspecs';
17
+ /** The plugin's name, before the application's id. */
18
+ export declare const APP_COMPOSER_PLUGIN_NAME = "@datalayer/loop-plugin-app-composer";
19
+ /**
20
+ * The plugin that puts an application's commands in the composer's `/` menu
21
+ * and its modes beside the composer. Nothing for an application with
22
+ * neither: its plugin contributes nothing.
23
+ */
24
+ export declare function defineAppComposerPlugin(app: AppSpec): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, {
25
+ components: {
26
+ id: string;
27
+ slot: "loop.promptAction";
28
+ order: number;
29
+ Component: () => JSX.Element | null;
30
+ }[];
31
+ }>;