@datalayer/agent-runtimes 1.3.89 → 1.3.91

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 (265) hide show
  1. package/lib/Agent.js +2 -18
  2. package/lib/AgentDocument.js +5 -55
  3. package/lib/AgentNode.js +31 -208
  4. package/lib/AgentNotebook.js +5 -59
  5. package/lib/agent-node/AgentNodeGallery.js +23 -111
  6. package/lib/agents/AgentDetails.js +36 -274
  7. package/lib/agents/AgentRuntimeChat.js +1 -8
  8. package/lib/chat/Chat.js +7 -46
  9. package/lib/chat/ChatFloating.js +15 -100
  10. package/lib/chat/ChatInline.js +5 -47
  11. package/lib/chat/ChatPopupStandalone.js +5 -53
  12. package/lib/chat/ChatSidebar.js +4 -51
  13. package/lib/chat/ChatStandalone.js +4 -45
  14. package/lib/chat/EphemeralSurfaceControl.js +5 -25
  15. package/lib/chat/assistant/AssistantContextMenu.d.ts +8 -0
  16. package/lib/chat/assistant/AssistantContextMenu.js +17 -1
  17. package/lib/chat/assistant/AssistantStage.js +6 -25
  18. package/lib/chat/assistant/BalloonParts.js +8 -40
  19. package/lib/chat/assistant/BalloonVisual.js +2 -2
  20. package/lib/chat/assistant/ConversationBalloon.js +2 -11
  21. package/lib/chat/assistant/DecisionAsk.js +3 -3
  22. package/lib/chat/assistant/SandboxDetailsDialog.js +3 -17
  23. package/lib/chat/assistant/SpeechBalloon.js +14 -78
  24. package/lib/chat/base/ChatBase.js +37 -172
  25. package/lib/chat/display/EmptyState.js +7 -35
  26. package/lib/chat/display/FloatingBrandButton.js +4 -34
  27. package/lib/chat/display/PoweredByTag.js +3 -14
  28. package/lib/chat/document/EphemeralDocument.js +3 -18
  29. package/lib/chat/header/ChatHeader.js +1 -17
  30. package/lib/chat/header/ChatHeaderBase.js +2 -24
  31. package/lib/chat/header/ChatViewModeToggle.js +7 -29
  32. package/lib/chat/indicators/McpStatusIndicator.js +3 -12
  33. package/lib/chat/indicators/SandboxStatusIndicator.js +6 -34
  34. package/lib/chat/indicators/SkillsStatusIndicator.js +3 -12
  35. package/lib/chat/indicators/TypingDots.js +1 -11
  36. package/lib/chat/messages/ChatMessageList.js +58 -185
  37. package/lib/chat/messages/ChatMessages.js +15 -79
  38. package/lib/chat/messages/FormSubmissionMessage.js +2 -33
  39. package/lib/chat/messages/NotebookToolSurfaces.js +12 -41
  40. package/lib/chat/messages/TurnFooter.js +4 -10
  41. package/lib/chat/notebook/EphemeralNotebook.js +2 -10
  42. package/lib/chat/parts/ReasoningPart.js +2 -16
  43. package/lib/chat/parts/TextPart.js +2 -12
  44. package/lib/chat/parts/ToolPart.js +16 -90
  45. package/lib/chat/presence/Presence.js +5 -33
  46. package/lib/chat/prompt/FloatingCard.js +1 -13
  47. package/lib/chat/prompt/InputPrompt.js +1 -6
  48. package/lib/chat/prompt/InputPromptBase.js +4 -17
  49. package/lib/chat/prompt/InputPromptLexical.js +44 -45
  50. package/lib/chat/prompt/InputPromptText.js +1 -1
  51. package/lib/chat/prompt/footer/InPromptFooter.js +1 -8
  52. package/lib/chat/prompt/menus/AgentsMenu.js +1 -1
  53. package/lib/chat/prompt/menus/InlineAgentsMenu.js +1 -1
  54. package/lib/chat/prompt/menus/ModelSelector.js +1 -5
  55. package/lib/chat/prompt/menus/SkillsMenu.js +3 -30
  56. package/lib/chat/prompt/menus/SuggestionsMenu.js +1 -1
  57. package/lib/chat/prompt/menus/ToolsMenu.js +4 -48
  58. package/lib/chat/prompt/plugins/AgentMentionPlugin.js +2 -9
  59. package/lib/chat/prompt/plugins/CommandPlugin.js +2 -9
  60. package/lib/chat/prompt/plugins/MentionChip.js +1 -15
  61. package/lib/chat/prompt/stack.js +1 -19
  62. package/lib/chat/tools/A2AAgentDialog.js +9 -42
  63. package/lib/chat/tools/SubagentActivity.js +5 -22
  64. package/lib/chat/tools/ToolApprovalBanner.js +4 -34
  65. package/lib/chat/tools/ToolApprovalDialog.js +4 -19
  66. package/lib/chat/tools/ToolCallDisplay.js +12 -97
  67. package/lib/chat/usage/ContextPie.js +4 -45
  68. package/lib/chat/words.d.ts +7 -0
  69. package/lib/chat/words.js +12 -0
  70. package/lib/components/a2ui/datalayer/Chart.js +4 -10
  71. package/lib/components/a2ui/datalayer/Chat.js +2 -10
  72. package/lib/components/a2ui/datalayer/Download.js +1 -1
  73. package/lib/components/a2ui/datalayer/Evidence.js +1 -15
  74. package/lib/components/a2ui/datalayer/FileUpload.js +1 -12
  75. package/lib/components/a2ui/datalayer/Table.js +9 -17
  76. package/lib/components/a2ui/datalayer/formWidgets.js +3 -2
  77. package/lib/components/a2ui/datalayer/parts.js +1 -16
  78. package/lib/components/a2ui/styles.js +16 -5
  79. package/lib/components/agents/AgentSummary.js +1 -51
  80. package/lib/components/cards/NotificationEventCard.js +6 -72
  81. package/lib/components/cards/OutputCard.js +4 -48
  82. package/lib/components/cards/ToolApprovalCard.js +2 -35
  83. package/lib/components/code-sandboxes/CodeSandboxCellVariables.js +1 -1
  84. package/lib/components/code-sandboxes/CodeSandboxEnvironmentSelect.js +4 -14
  85. package/lib/components/code-sandboxes/CodeSandboxPicker.js +16 -21
  86. package/lib/components/code-sandboxes/CodeSandboxVariables.js +2 -2
  87. package/lib/components/scheduler/ScheduleMenu.js +5 -10
  88. package/lib/components/specs/SpecPayloadPreview.js +1 -26
  89. package/lib/components/teams/A2ATeamGraph.d.ts +4 -3
  90. package/lib/components/teams/A2ATeamGraph.js +25 -71
  91. package/lib/components/teams/AnswerSurfacesView.js +2 -2
  92. package/lib/components/teams/NotebookPreview.js +1 -10
  93. package/lib/components/teams/NotebookPreviewView.js +2 -3
  94. package/lib/components/teams/SceneTranscript.js +3 -17
  95. package/lib/components/teams/SceneView.js +1 -8
  96. package/lib/components/teams/TeamNotebook.js +5 -13
  97. package/lib/components/teams/TeamNotebookView.js +2 -12
  98. package/lib/components/teams/index.d.ts +1 -0
  99. package/lib/components/teams/index.js +1 -0
  100. package/lib/components/teams/useBalloonRoom.d.ts +39 -0
  101. package/lib/components/teams/useBalloonRoom.js +89 -0
  102. package/lib/config/AgentConfiguration.js +22 -225
  103. package/lib/context/ContextDistribution.js +7 -41
  104. package/lib/context/ContextInspector.js +9 -107
  105. package/lib/context/ContextPanel.js +11 -86
  106. package/lib/context/ContextUsage.js +6 -34
  107. package/lib/context/CostTracker.js +7 -42
  108. package/lib/context/MessageHistory.js +3 -37
  109. package/lib/context/TurnGraphChart.js +9 -41
  110. package/lib/examples/A2UiAgentExample.js +6 -108
  111. package/lib/examples/A2UiComponentsGalleryExample.js +1 -1
  112. package/lib/examples/A2UiContactCardExample.js +1 -1
  113. package/lib/examples/A2UiJupyterOutputExample.js +25 -125
  114. package/lib/examples/A2UiRestaurantExample.js +1 -1
  115. package/lib/examples/A2UiViewerExample.js +1 -1
  116. package/lib/examples/AgUiAgenticExample.js +3 -35
  117. package/lib/examples/AgUiBackendToolRenderingExample.js +2 -25
  118. package/lib/examples/AgUiHaikuGenUiExample.js +2 -32
  119. package/lib/examples/AgUiHumanInTheLoopExample.js +5 -59
  120. package/lib/examples/AgUiSharedStateExample.js +6 -56
  121. package/lib/examples/AgUiToolsBasedGenUiExample.js +25 -120
  122. package/lib/examples/AgentA2AExample.js +1 -15
  123. package/lib/examples/AgentA2ATeamExample.js +6 -30
  124. package/lib/examples/AgentCheckpointsExample.js +11 -145
  125. package/lib/examples/AgentCodeSandboxExample.js +5 -108
  126. package/lib/examples/AgentCodeSandboxesExample.js +3 -30
  127. package/lib/examples/AgentCodemodeExample.js +9 -85
  128. package/lib/examples/AgentCompactionExample.js +3 -64
  129. package/lib/examples/AgentDecideExample.js +1 -13
  130. package/lib/examples/AgentEvalsExample.js +5 -60
  131. package/lib/examples/AgentGuardrailsExample.js +4 -28
  132. package/lib/examples/AgentHooksExample.js +1 -1
  133. package/lib/examples/AgentInferenceProviderExample.js +5 -96
  134. package/lib/examples/AgentMCPExample.js +7 -105
  135. package/lib/examples/AgentMemoryExample.js +5 -74
  136. package/lib/examples/AgentMonitoringExample.js +7 -53
  137. package/lib/examples/AgentNotificationsExample.js +3 -61
  138. package/lib/examples/AgentOtelExample.js +3 -72
  139. package/lib/examples/AgentOutputsExample.js +6 -81
  140. package/lib/examples/AgentParametersExample.js +2 -37
  141. package/lib/examples/AgentSkillsExample.js +3 -45
  142. package/lib/examples/AgentSubagentsExample.js +6 -56
  143. package/lib/examples/AgentToolApprovalsExample.js +1 -11
  144. package/lib/examples/AgentTriggersExample.js +29 -239
  145. package/lib/examples/AgentspecsExample.js +6 -60
  146. package/lib/examples/AssistantExample.js +5 -5
  147. package/lib/examples/AssistantGalleryExample.js +8 -65
  148. package/lib/examples/CellExample.js +1 -6
  149. package/lib/examples/ChatCustomExample.js +2 -31
  150. package/lib/examples/ChatExample.js +1 -9
  151. package/lib/examples/ChatStandaloneExample.js +3 -37
  152. package/lib/examples/CopilotKitLexicalExample.js +1 -24
  153. package/lib/examples/DecksAgent.js +1 -1
  154. package/lib/examples/DocumentPageAgent.js +1 -1
  155. package/lib/examples/HomeExample.js +14 -62
  156. package/lib/examples/Lexical2Example.js +3 -32
  157. package/lib/examples/LexicalAgentExample.js +2 -34
  158. package/lib/examples/LexicalAgentSidebarExample.js +3 -34
  159. package/lib/examples/LoopAppComputerExample.js +1 -1
  160. package/lib/examples/LoopShellExample.js +2 -2
  161. package/lib/examples/LoopStrategyExample.js +15 -87
  162. package/lib/examples/LoopWorkspaceEmbed.js +2 -18
  163. package/lib/examples/LoopWorkspaceExample.js +2 -2
  164. package/lib/examples/NotebookAgentExample.js +2 -33
  165. package/lib/examples/NotebookAgentSidebarExample.js +3 -46
  166. package/lib/examples/NotebookCollaborationExample.js +1 -31
  167. package/lib/examples/NotebookExample.js +1 -18
  168. package/lib/examples/NotebookPageAgent.js +1 -1
  169. package/lib/examples/ScenesExample.js +5 -24
  170. package/lib/examples/VoiceChatExample.js +1 -1
  171. package/lib/examples/components/AuthRequiredView.js +1 -7
  172. package/lib/examples/components/ErrorView.js +1 -8
  173. package/lib/examples/components/ExampleErrorBoundary.js +1 -14
  174. package/lib/examples/components/FooterMetrics.js +1 -7
  175. package/lib/examples/components/Header.js +12 -51
  176. package/lib/examples/components/HeaderControls.js +1 -9
  177. package/lib/examples/components/LexicalEditor.js +1 -8
  178. package/lib/examples/components/MainContent.js +1 -13
  179. package/lib/examples/components/MockFileBrowser.js +4 -40
  180. package/lib/examples/components/SessionTabs.js +1 -1
  181. package/lib/examples/components/TimeTravel.js +1 -13
  182. package/lib/examples/main.js +13 -81
  183. package/lib/examples/scenes/A2UiComponentsGalleryScene.js +3 -34
  184. package/lib/examples/scenes/A2UiContactCardScene.js +3 -35
  185. package/lib/examples/scenes/A2UiRestaurantScene.js +3 -53
  186. package/lib/examples/scenes/A2UiViewerScene.js +3 -34
  187. package/lib/examples/utils/AssistantGalleryGrid.js +6 -35
  188. package/lib/examples2/DatalayerNotebookExample.js +3 -33
  189. package/lib/examples2/NativeNavigationExample.js +4 -10
  190. package/lib/examples2/NotebookMutationsKernel.js +2 -1
  191. package/lib/examples2/NotebookMutationsServiceManager.js +2 -1
  192. package/lib/examples2/ReactRouterExample.js +14 -42
  193. package/lib/hooks/useToolApprovals.d.ts +2 -34
  194. package/lib/hooks/useToolApprovals.js +1 -64
  195. package/lib/identity/AgentIdentity.js +17 -62
  196. package/lib/identity/IdentityConnect.js +13 -59
  197. package/lib/identity/OAuthCallback.js +6 -44
  198. package/lib/lexical/ChatInlinePlugin.js +6 -14
  199. package/lib/loop/apps/AppFeedback.js +12 -3
  200. package/lib/loop/apps/AppRenderer.d.ts +4 -3
  201. package/lib/loop/apps/AppRenderer.js +11 -12
  202. package/lib/loop/apps/appspec.js +6 -3
  203. package/lib/loop/apps/feedback.d.ts +3 -2
  204. package/lib/loop/apps/feedback.js +4 -2
  205. package/lib/loop/core/index.d.ts +19 -0
  206. package/lib/loop/core/index.js +27 -0
  207. package/lib/loop/embed/AppEmbed.d.ts +1 -1
  208. package/lib/loop/embed/AppEmbed.js +2 -1
  209. package/lib/loop/embed/LoopEmbed.js +1 -7
  210. package/lib/loop/embed/embedConfig.d.ts +3 -3
  211. package/lib/loop/embed/embedConfig.js +2 -3
  212. package/lib/loop/embed/embedTheme.d.ts +4 -4
  213. package/lib/loop/embed/embedTheme.js +11 -7
  214. package/lib/loop/plugins/a2a-sidebar/A2ASidebar.js +7 -34
  215. package/lib/loop/plugins/a2ui/A2uiView.js +4 -12
  216. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +1 -16
  217. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +2 -12
  218. package/lib/loop/plugins/agents/AnonymousKeyBadge.js +3 -2
  219. package/lib/loop/plugins/agents/SandboxSelector.js +3 -7
  220. package/lib/loop/plugins/agents/SandboxView.js +4 -27
  221. package/lib/loop/plugins/agents/TeamMemberPicker.js +2 -2
  222. package/lib/loop/plugins/agentspecs/AgentspecPicker.js +3 -2
  223. package/lib/loop/plugins/app-activity/AppActivity.js +1 -1
  224. package/lib/loop/plugins/app-computer/AppComputer.js +32 -27
  225. package/lib/loop/plugins/app-elements/AppElements.js +2 -31
  226. package/lib/loop/plugins/app-page/AppPage.js +26 -8
  227. package/lib/loop/plugins/app-rules/AppRulesCard.js +4 -20
  228. package/lib/loop/plugins/assistant/index.js +1 -13
  229. package/lib/loop/plugins/chat/ChatView.js +89 -130
  230. package/lib/loop/plugins/checkpoints-sidebar/CheckpointsSidebar.js +2 -13
  231. package/lib/loop/plugins/decks/DeckSurface.js +2 -2
  232. package/lib/loop/plugins/document/DocumentView.js +4 -12
  233. package/lib/loop/plugins/document-toolbar/SandboxStatusItem.js +3 -8
  234. package/lib/loop/plugins/graph/GraphView.js +2 -2
  235. package/lib/loop/plugins/input-prompt/ComposerView.js +1 -1
  236. package/lib/loop/plugins/notebook/NotebookView.js +3 -11
  237. package/lib/loop/plugins/page-layout/SplitLayout.js +11 -34
  238. package/lib/loop/plugins/page-layout/TurnPanel.js +9 -35
  239. package/lib/loop/plugins/shell/useEditorPreviews.js +3 -2
  240. package/lib/loop/plugins/subagent-activity/SubagentPulse.js +1 -13
  241. package/lib/loop/plugins/window-frame/WindowFrame.js +10 -44
  242. package/lib/loop/plugins/window-frame/index.js +1 -1
  243. package/lib/loop/shell/LoopWorkspace.js +34 -77
  244. package/lib/loop/shell/PluginToggles.js +4 -15
  245. package/lib/loop/shell/SidebarRail.js +8 -40
  246. package/lib/loop/shell/ViewSwitcher.js +4 -24
  247. package/lib/loop-main.js +2 -3
  248. package/lib/mcp/McpServerManager.js +6 -37
  249. package/lib/portable/index.d.ts +11 -0
  250. package/lib/portable/index.js +20 -0
  251. package/lib/protocols/AGUIAdapter.js +2 -2
  252. package/lib/protocols/BaseProtocolAdapter.d.ts +2 -0
  253. package/lib/protocols/BaseProtocolAdapter.js +4 -0
  254. package/lib/specs/apps.js +6 -0
  255. package/lib/specs/appspecSchema.js +11 -4
  256. package/lib/stories/loop/ReferenceScreens.js +17 -91
  257. package/lib/stories/loop/pictures-main.js +1 -1
  258. package/lib/types/agentspecs.d.ts +5 -2
  259. package/lib/types/protocol.d.ts +2 -0
  260. package/lib/utils/approvals.d.ts +59 -0
  261. package/lib/utils/approvals.js +101 -0
  262. package/lib/voice/VoiceInput.js +3 -39
  263. package/package.json +2 -2
  264. package/lib/shims/primerAddonsBox.d.ts +0 -1
  265. package/lib/shims/primerAddonsBox.js +0 -7
@@ -256,52 +256,21 @@ export function McpServerManager({ baseUrl, agentId, enableCodemode = false, sel
256
256
  }, [selectedServers, onSelectedServersChange]);
257
257
  const isLoading = catalogQuery.isLoading;
258
258
  const isMutating = enableMutation.isPending || disableMutation.isPending;
259
- return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs(Box, { sx: {
260
- display: 'flex',
261
- alignItems: 'center',
262
- justifyContent: 'space-between',
263
- }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(ToolsIcon, { size: 16 }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: "MCP Server Management" })] }), _jsx(IconButton, { icon: SyncIcon, "aria-label": "Refresh server lists", size: "small", onClick: handleRefresh, disabled: isLoading || isMutating })] }), error && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: error }) })), enableCodemode && (_jsx(Flash, { variant: "default", children: _jsxs(Text, { sx: { fontSize: 0 }, children: [_jsx("strong", { children: "Codemode enabled:" }), " Adding or removing MCP servers will regenerate the Codemode tool registry."] }) })), isLoading && (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, py: 3 }, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading MCP servers..." })] })), !isLoading && assignedConfigServers.length > 0 && (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Assigned Configured Servers (", assignedConfigServers.length, ")"] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "These servers are from ~/.datalayer/mcp.json and assigned to this agent." }), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: assignedConfigServers.map(server => (_jsx(ServerCard, { server: server, variant: "config", disabled: disabled, isSelected: true, onSelect: onSelectedServersChange ? handleServerSelect : undefined, onRemove: () => handleRemoveServer(server.id, true) }, server.id))) })] })), !isLoading && assignedCatalogServers.length > 0 && (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Assigned Catalog Servers (", assignedCatalogServers.length, ")"] }), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: assignedCatalogServers.map(server => (_jsx(ServerCard, { server: server, variant: "active", disabled: disabled || isMutating, isSelected: true, onSelect: onSelectedServersChange ? handleServerSelect : undefined, onRemove: () => handleDisableServer(server.id), isRemoving: disableMutation.isPending &&
264
- disableMutation.variables === server.id }, server.id))) })] })), !isLoading && (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Available from Catalog (", availableCatalogServers.length, ")"] }), availableCatalogServers.length > 3 && (_jsx(TextInput, { leadingVisual: SearchIcon, placeholder: "Search servers...", value: searchQuery, onChange: e => setSearchQuery(e.target.value), sx: { width: '100%' } })), filteredCatalogServers.length === 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: searchQuery
259
+ return (_jsxs(Box, { display: "flex", flexDirection: "column", gap: 4, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(ToolsIcon, { size: 16 }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: "MCP Server Management" })] }), _jsx(IconButton, { icon: SyncIcon, "aria-label": "Refresh server lists", size: "small", onClick: handleRefresh, disabled: isLoading || isMutating })] }), error && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: error }) })), enableCodemode && (_jsx(Flash, { variant: "default", children: _jsxs(Text, { sx: { fontSize: 0 }, children: [_jsx("strong", { children: "Codemode enabled:" }), " Adding or removing MCP servers will regenerate the Codemode tool registry."] }) })), isLoading && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, py: 3, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading MCP servers..." })] })), !isLoading && assignedConfigServers.length > 0 && (_jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Assigned Configured Servers (", assignedConfigServers.length, ")"] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "These servers are from ~/.datalayer/mcp.json and assigned to this agent." }), _jsx(Box, { display: "flex", flexDirection: "column", gap: 2, children: assignedConfigServers.map(server => (_jsx(ServerCard, { server: server, variant: "config", disabled: disabled, isSelected: true, onSelect: onSelectedServersChange ? handleServerSelect : undefined, onRemove: () => handleRemoveServer(server.id, true) }, server.id))) })] })), !isLoading && assignedCatalogServers.length > 0 && (_jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Assigned Catalog Servers (", assignedCatalogServers.length, ")"] }), _jsx(Box, { display: "flex", flexDirection: "column", gap: 2, children: assignedCatalogServers.map(server => (_jsx(ServerCard, { server: server, variant: "active", disabled: disabled || isMutating, isSelected: true, onSelect: onSelectedServersChange ? handleServerSelect : undefined, onRemove: () => handleDisableServer(server.id), isRemoving: disableMutation.isPending &&
260
+ disableMutation.variables === server.id }, server.id))) })] })), !isLoading && (_jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Available from Catalog (", availableCatalogServers.length, ")"] }), availableCatalogServers.length > 3 && (_jsx(TextInput, { leadingVisual: SearchIcon, placeholder: "Search servers...", value: searchQuery, onChange: e => setSearchQuery(e.target.value), sx: { width: '100%' } })), filteredCatalogServers.length === 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: searchQuery
265
261
  ? 'No servers match your search.'
266
- : 'All catalog servers are already enabled.' })) : (_jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: filteredCatalogServers.map(server => (_jsx(ServerCard, { server: server, variant: "catalog", disabled: disabled || isMutating, onAdd: () => handleEnableServer(server.id), isAdding: enableMutation.isPending &&
262
+ : 'All catalog servers are already enabled.' })) : (_jsx(Box, { display: "flex", flexDirection: "column", gap: 2, children: filteredCatalogServers.map(server => (_jsx(ServerCard, { server: server, variant: "catalog", disabled: disabled || isMutating, onAdd: () => handleEnableServer(server.id), isAdding: enableMutation.isPending &&
267
263
  enableMutation.variables === server.id }, server.id))) }))] }))] }));
268
264
  }
269
265
  function ServerCard({ server, variant, disabled = false, isSelected = false, onSelect, onAdd, onRemove, isAdding = false, isRemoving = false, }) {
270
266
  // Check availability based on isAvailable field from the server
271
267
  const isAvailable = server.isAvailable !== false;
272
268
  const missingEnvVars = !isAvailable && server.requiredEnvVars && server.requiredEnvVars.length > 0;
273
- return (_jsxs(Box, { sx: {
274
- display: 'flex',
275
- alignItems: 'flex-start',
276
- gap: 2,
277
- padding: 2,
278
- borderRadius: 2,
279
- backgroundColor: 'canvas.subtle',
280
- border: '1px solid',
281
- borderColor: isSelected ? 'accent.emphasis' : 'border.default',
282
- opacity: disabled || !isAvailable ? 0.6 : 1,
283
- cursor: onSelect && !disabled && isAvailable ? 'pointer' : 'default',
284
- }, onClick: () => {
269
+ return (_jsxs(Box, { display: "flex", alignItems: "flex-start", gap: 2, padding: 2, borderRadius: 2, backgroundColor: "canvas.subtle", border: "1px solid", borderColor: isSelected ? 'accent.emphasis' : 'border.default', opacity: disabled || !isAvailable ? 0.6 : 1, cursor: onSelect && !disabled && isAvailable ? 'pointer' : 'default', onClick: () => {
285
270
  if (onSelect && !disabled && isAvailable) {
286
271
  onSelect(server, !isSelected);
287
272
  }
288
- }, children: [_jsx(Box, { sx: {
289
- display: 'flex',
290
- alignItems: 'center',
291
- justifyContent: 'center',
292
- width: 32,
293
- height: 32,
294
- borderRadius: 2,
295
- backgroundColor: variant === 'config' ? 'attention.subtle' : 'accent.subtle',
296
- flexShrink: 0,
297
- }, children: _jsx(ServerIcon, { size: 16 }) }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 1 }, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: server.name }), variant === 'active' && (_jsx(Label, { variant: "success", size: "small", children: "Active" })), variant === 'config' && (_jsx(Label, { variant: "attention", size: "small", children: "Config" })), !isAvailable && (_jsx(Label, { variant: "danger", size: "small", children: "Missing env vars" })), isSelected && (_jsxs(Label, { variant: "accent", size: "small", children: [_jsx(CheckIcon, { size: 12 }), " Selected"] }))] }), server.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', mb: 1 }, children: server.description })), missingEnvVars && (_jsxs(Box, { sx: {
298
- fontSize: 0,
299
- color: 'danger.fg',
300
- mb: 1,
301
- p: 1,
302
- bg: 'danger.subtle',
303
- borderRadius: 1,
304
- }, children: [_jsx(Text, { sx: { fontWeight: 'semibold', display: 'block', mb: 1 }, children: "Missing environment variables:" }), server.requiredEnvVars.map(envVar => (_jsx(Text, { as: "code", sx: {
273
+ }, children: [_jsx(Box, { display: "flex", alignItems: "center", justifyContent: "center", width: 32, height: 32, borderRadius: 2, backgroundColor: variant === 'config' ? 'attention.subtle' : 'accent.subtle', flexShrink: 0, children: _jsx(ServerIcon, { size: 16 }) }), _jsxs(Box, { flex: 1, minWidth: 0, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, mb: 1, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: server.name }), variant === 'active' && (_jsx(Label, { variant: "success", size: "small", children: "Active" })), variant === 'config' && (_jsx(Label, { variant: "attention", size: "small", children: "Config" })), !isAvailable && (_jsx(Label, { variant: "danger", size: "small", children: "Missing env vars" })), isSelected && (_jsxs(Label, { variant: "accent", size: "small", children: [_jsx(CheckIcon, { size: 12 }), " Selected"] }))] }), server.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', mb: 1 }, children: server.description })), missingEnvVars && (_jsxs(Box, { fontSize: 0, color: "danger.fg", mb: 1, p: 1, bg: "danger.subtle", borderRadius: 1, children: [_jsx(Text, { sx: { fontWeight: 'semibold', display: 'block', mb: 1 }, children: "Missing environment variables:" }), server.requiredEnvVars.map(envVar => (_jsx(Text, { as: "code", sx: {
305
274
  display: 'inline-block',
306
275
  mr: 1,
307
276
  mb: 1,
@@ -317,7 +286,7 @@ function ServerCard({ server, variant, disabled = false, isSelected = false, onS
317
286
  overflow: 'hidden',
318
287
  textOverflow: 'ellipsis',
319
288
  whiteSpace: 'nowrap',
320
- }, children: ["Tools: ", server.tools.map(t => t.name).join(', ')] })), server.transport && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Transport: ", server.transport] }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [variant === 'catalog' && onAdd && (_jsx(Button, { variant: "primary", size: "small", leadingVisual: isAdding ? Spinner : PlusIcon, onClick: e => {
289
+ }, children: ["Tools: ", server.tools.map(t => t.name).join(', ')] })), server.transport && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Transport: ", server.transport] }))] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 1, flexShrink: 0, children: [variant === 'catalog' && onAdd && (_jsx(Button, { variant: "primary", size: "small", leadingVisual: isAdding ? Spinner : PlusIcon, onClick: e => {
321
290
  e.stopPropagation();
322
291
  onAdd();
323
292
  }, disabled: disabled || !isAvailable || isAdding, children: "Add" })), variant === 'active' && onRemove && (_jsx(Button, { variant: "danger", size: "small", leadingVisual: isRemoving ? Spinner : TrashIcon, onClick: e => {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * What any JavaScript runtime can import from Agent Runtimes: no React, DOM,
3
+ * Jupyter or Node built-in. Re-exports only; the mobile app reaches Agent
4
+ * Runtimes through this entry alone.
5
+ *
6
+ * @module portable
7
+ */
8
+ export { messageOfApproval, normalizeApproval, whyAsked, type ApprovalArgs, type ApprovalRecord, type MessageAsked, } from '../utils/approvals';
9
+ export { AGUIAdapter, type AGUIAdapterConfig } from '../protocols/AGUIAdapter';
10
+ export { createActivityMessage, createAssistantMessage, createUserMessage, generateMessageId, type ChatMessage, type ChatMessageDelta, type ChatMessageInput, type ChatThread, type ContentPart, type MessageRole, type ToolCallStatus, } from '../types/messages';
11
+ export type { ProtocolAdapterConfig, ProtocolConnectionState, ProtocolEvent, ProtocolEventHandler, } from '../types/protocol';
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /*
6
+ * Copyright (c) 2026 Datalayer, Inc.
7
+ * Distributed under the terms of the Modified BSD License.
8
+ */
9
+ /**
10
+ * What any JavaScript runtime can import from Agent Runtimes: no React, DOM,
11
+ * Jupyter or Node built-in. Re-exports only; the mobile app reaches Agent
12
+ * Runtimes through this entry alone.
13
+ *
14
+ * @module portable
15
+ */
16
+ export { messageOfApproval, normalizeApproval, whyAsked, } from '../utils/approvals';
17
+ // The AG-UI stream client and the messages it builds; a phone passes a
18
+ // streaming `fetch` in the adapter's config.
19
+ export { AGUIAdapter } from '../protocols/AGUIAdapter';
20
+ export { createActivityMessage, createAssistantMessage, createUserMessage, generateMessageId, } from '../types/messages';
@@ -99,7 +99,7 @@ export class AGUIAdapter extends BaseProtocolAdapter {
99
99
  // e.g., http://localhost:8765/api/v1/ag-ui/default -> http://localhost:8765/api/v1/ag-ui/terminate
100
100
  const baseUrl = new URL(this.aguiConfig.baseUrl);
101
101
  const terminateUrl = `${baseUrl.origin}/api/v1/ag-ui/terminate`;
102
- const response = await fetch(terminateUrl, {
102
+ const response = await this.fetchOf()(terminateUrl, {
103
103
  method: 'POST',
104
104
  headers: {
105
105
  'Content-Type': 'application/json',
@@ -205,7 +205,7 @@ export class AGUIAdapter extends BaseProtocolAdapter {
205
205
  console.log('[AGUIAdapter] Sending with identities:', options.identities.map(i => i.provider));
206
206
  }
207
207
  try {
208
- const response = await fetch(this.aguiConfig.baseUrl, {
208
+ const response = await this.fetchOf()(this.aguiConfig.baseUrl, {
209
209
  method: 'POST',
210
210
  headers: this.buildHeaders({
211
211
  Accept: 'text/event-stream',
@@ -70,6 +70,8 @@ export declare abstract class BaseProtocolAdapter implements ProtocolAdapter {
70
70
  * Build headers for HTTP requests
71
71
  */
72
72
  protected buildHeaders(additionalHeaders?: Record<string, string>): Record<string, string>;
73
+ /** The fetch the caller gave, else the global one; read per call so a test can stub the global. */
74
+ protected fetchOf(): typeof fetch;
73
75
  /**
74
76
  * Create abort signal with timeout
75
77
  */
@@ -110,6 +110,10 @@ export class BaseProtocolAdapter {
110
110
  }
111
111
  return { ...headers, ...additionalHeaders };
112
112
  }
113
+ /** The fetch the caller gave, else the global one; read per call so a test can stub the global. */
114
+ fetchOf() {
115
+ return this.config.fetch ?? ((input, init) => fetch(input, init));
116
+ }
113
117
  /**
114
118
  * Create abort signal with timeout
115
119
  */
package/lib/specs/apps.js CHANGED
@@ -3515,6 +3515,7 @@ export const APP_SOURCES = {
3515
3515
  },
3516
3516
  ],
3517
3517
  interface: {
3518
+ accent: 'green',
3518
3519
  welcome: "Ask me for a report from the books: open invoices, aged balances, a trial balance or a customer's ledger. I read Odoo; I change nothing.",
3519
3520
  starters: [
3520
3521
  {
@@ -3869,6 +3870,7 @@ export const APP_SOURCES = {
3869
3870
  agent: 'jupyter-data-analyst:0.0.1',
3870
3871
  contents: ['The dataset under investigation'],
3871
3872
  interface: {
3873
+ accent: 'green',
3872
3874
  components: [
3873
3875
  'Card',
3874
3876
  'Column',
@@ -4230,6 +4232,7 @@ export const APP_SOURCES = {
4230
4232
  },
4231
4233
  ],
4232
4234
  interface: {
4235
+ accent: 'green',
4233
4236
  welcome: 'I sort your mail and draft the replies. I ask before I send, and I never delete.',
4234
4237
  starters: [
4235
4238
  {
@@ -4321,6 +4324,7 @@ export const APP_SOURCES = {
4321
4324
  'The Models page: how each model is billed (standard or credits) and who hosts it',
4322
4325
  ],
4323
4326
  interface: {
4327
+ accent: 'green',
4324
4328
  components: [
4325
4329
  'Card',
4326
4330
  'Column',
@@ -5247,6 +5251,7 @@ export const APP_SOURCES = {
5247
5251
  agent: 'jupyter-data-analyst:0.0.1',
5248
5252
  contents: ['The benchmark run: task results, traces, cost and latency'],
5249
5253
  interface: {
5254
+ accent: 'green',
5250
5255
  components: [
5251
5256
  'Card',
5252
5257
  'Column',
@@ -5354,6 +5359,7 @@ export const APP_SOURCES = {
5354
5359
  agent: 'jupyter-data-analyst:0.0.1',
5355
5360
  contents: ['Order history', 'Supplier price lists', 'Delivery records'],
5356
5361
  interface: {
5362
+ accent: 'green',
5357
5363
  components: [
5358
5364
  'Card',
5359
5365
  'Column',
@@ -435,9 +435,16 @@ export const APPSPEC_SCHEMA = {
435
435
  description: "`chat`, `page` or `split`; the kind's own when unsaid",
436
436
  },
437
437
  accent: {
438
- $ref: '#/$defs/Accent',
439
- default: 'green',
440
- description: "The application's one colour",
438
+ anyOf: [
439
+ {
440
+ $ref: '#/$defs/Accent',
441
+ },
442
+ {
443
+ type: 'null',
444
+ },
445
+ ],
446
+ default: null,
447
+ description: "The application's one colour, over whichever theme it runs in; unsaid, it wears the theme's own colours",
441
448
  },
442
449
  theme: {
443
450
  anyOf: [
@@ -449,7 +456,7 @@ export const APPSPEC_SCHEMA = {
449
456
  },
450
457
  ],
451
458
  default: null,
452
- description: "The theme it runs in by default, at its address, embedded, in the Studio's Preview and as an example: a `variant` and, optionally, a colour `mode`. The person's own when unsaid. Its `accent` colours the `loop` theme only",
459
+ description: "The theme it runs in by default, at its address, embedded, in the Studio's Preview and as an example: a `variant` and, optionally, a colour `mode`. The person's own when unsaid. Its `accent`, when it names one, colours it, whichever theme it is",
453
460
  },
454
461
  welcome: {
455
462
  default: '',
@@ -20,8 +20,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  * @module stories/loop/ReferenceScreens
21
21
  */
22
22
  import { useEffect, useRef, useState } from 'react';
23
- import { Box, Button, Heading, Text } from '@primer/react';
24
- import { DatalayerThemeProvider, loopAccentStyles, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
23
+ import { Button, Heading, Text } from '@primer/react';
24
+ import { Box, DatalayerThemeProvider, setupPrimerPortals, themeAccentVars, themeConfigs, } from '@datalayer/primer-addons';
25
25
  import { ChatBaseHeader } from '../../chat/header/ChatHeaderBase';
26
26
  import { ChatMessageList } from '../../chat/messages/ChatMessageList';
27
27
  import { InputPrompt } from '../../chat/prompt/InputPrompt';
@@ -84,29 +84,12 @@ export function ReferenceTheme({ theme, mode, children, }) {
84
84
  document.documentElement.setAttribute('data-reference-mode', mode);
85
85
  }, [theme, mode]);
86
86
  return (_jsx(DatalayerThemeProvider, { colorMode: mode, theme: config.primerTheme, themeStyles: config.themeStyles, baseStyles: theme === 'loop'
87
- ? loopAccentStyles('green', mode)
88
- : undefined, children: _jsx(Box, { "data-reference-stage": "", sx: {
89
- position: 'fixed',
90
- inset: 0,
91
- p: 4,
92
- bg: 'var(--loop-stage, var(--bgColor-muted))',
93
- color: 'fg.default',
94
- display: 'flex',
95
- }, children: children }) }));
87
+ ? themeAccentVars('green', mode)
88
+ : undefined, children: _jsx(Box, { "data-reference-stage": "", position: "fixed", inset: 0, p: 4, bg: theme === 'loop' ? 'var(--theme-stage)' : 'var(--bgColor-muted)', color: "fg.default", display: "flex", children: children }) }));
96
89
  }
97
90
  /** One rounded surface on the stage: the theme's frame. */
98
91
  function Frame({ children }) {
99
- return (_jsx(Box, { sx: {
100
- flex: 1,
101
- minWidth: 0,
102
- display: 'flex',
103
- overflow: 'hidden',
104
- bg: 'canvas.default',
105
- borderRadius: 'var(--theme-radius-frame, 12px)',
106
- border: 'var(--theme-hairline, 1px) solid',
107
- borderColor: 'border.muted',
108
- boxShadow: 'var(--theme-shadow, none)',
109
- }, children: children }));
92
+ return (_jsx(Box, { flex: 1, minWidth: 0, display: "flex", overflow: "hidden", bg: "canvas.default", borderRadius: "var(--theme-radius-frame, 12px)", border: "var(--theme-hairline, 1px) solid", borderColor: "border.muted", boxShadow: "var(--theme-shadow, none)", children: children }));
110
93
  }
111
94
  // ---------------------------------------------------------------------------
112
95
  // Fixed data
@@ -188,47 +171,30 @@ const AVATARS = {
188
171
  function Conversation({ name, face, presence, items, composer = true, mcpServers, }) {
189
172
  const endRef = useRef(null);
190
173
  const [input, setInput] = useState('');
191
- return (_jsxs(Box, { sx: {
192
- flex: 1,
193
- minWidth: 0,
194
- display: 'flex',
195
- flexDirection: 'column',
196
- height: '100%',
197
- }, children: [_jsx(ChatBaseHeader, { title: name, brandIcon: _jsx(PresenceFace, { face: face, size: 20, state: presence }), headerContent: _jsx(PresenceLine, { state: presence }), padding: 3, messageCount: items.length, onNewChat: () => undefined, onClear: () => undefined }), _jsx(Box, { sx: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(ChatMessageList, { displayItems: items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 3, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined, mcpServers: mcpServers }) }), composer && (_jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Send a message", autoFocus: false, padding: 3, onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false }))] }));
174
+ return (_jsxs(Box, { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", height: "100%", children: [_jsx(ChatBaseHeader, { title: name, brandIcon: _jsx(PresenceFace, { face: face, size: 20, state: presence }), headerContent: _jsx(PresenceLine, { state: presence }), padding: 3, messageCount: items.length, onNewChat: () => undefined, onClear: () => undefined }), _jsx(Box, { flex: 1, minHeight: 0, overflow: "hidden", children: _jsx(ChatMessageList, { displayItems: items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 3, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined, mcpServers: mcpServers }) }), composer && (_jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Send a message", autoFocus: false, padding: 3, onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false }))] }));
198
175
  }
199
176
  // ---------------------------------------------------------------------------
200
177
  // The four screens
201
178
  // ---------------------------------------------------------------------------
202
179
  /** A conversation. */
203
180
  export function ConversationScreen() {
204
- return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 640, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: CONVERSATION }) }) }) }));
181
+ return (_jsx(Frame, { children: _jsx(Box, { flex: 1, display: "flex", justifyContent: "center", children: _jsx(Box, { width: "100%", maxWidth: 640, display: "flex", children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: CONVERSATION }) }) }) }));
205
182
  }
206
183
  /** A conversation beside its work: the split, one hairline between them. */
207
184
  export function BesideWorkScreen() {
208
- return (_jsxs(Frame, { children: [_jsx(Box, { sx: { width: 400, flexShrink: 0, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: BESIDE_WORK }) }), _jsx(Box, { sx: {
209
- width: 'var(--theme-hairline, 1px)',
210
- bg: 'border.muted',
211
- flexShrink: 0,
212
- } }), _jsxs(Box, { "data-reference-work": "", sx: {
213
- flex: 1,
214
- minWidth: 0,
215
- p: 4,
216
- display: 'flex',
217
- flexDirection: 'column',
218
- gap: 3,
219
- }, children: [_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Draft \u00B7 reply" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, fontWeight: 600 }, children: "Your order 1042" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "To ada@example.com" }), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(Text, { as: "p", sx: { m: 0 }, children: "Dear Ada," }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "We are sorry your order arrived four days late. It left our warehouse on Monday and is with you by Thursday, 8 October." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "Your next order ships free, with no code to enter." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "The support desk" })] }), _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 'auto' }, children: [_jsx(Button, { variant: "primary", children: "Send" }), _jsx(Button, { children: "Edit" })] })] })] }));
185
+ return (_jsxs(Frame, { children: [_jsx(Box, { width: 400, flexShrink: 0, display: "flex", children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: BESIDE_WORK }) }), _jsx(Box, { width: "var(--theme-hairline, 1px)", bg: "border.muted", flexShrink: 0 }), _jsxs(Box, { "data-reference-work": "", flex: 1, minWidth: 0, p: 4, display: "flex", flexDirection: "column", gap: 3, children: [_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Draft \u00B7 reply" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, fontWeight: 600 }, children: "Your order 1042" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "To ada@example.com" }), _jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Text, { as: "p", sx: { m: 0 }, children: "Dear Ada," }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "We are sorry your order arrived four days late. It left our warehouse on Monday and is with you by Thursday, 8 October." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "Your next order ships free, with no code to enter." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "The support desk" })] }), _jsxs(Box, { display: "flex", gap: 2, mt: "auto", children: [_jsx(Button, { variant: "primary", children: "Send" }), _jsx(Button, { children: "Edit" })] })] })] }));
220
186
  }
221
187
  /** The activity of a worker: what it did on its own, as it did it. */
222
188
  export function WorkerActivityScreen() {
223
- return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 680, display: 'flex' }, children: _jsx(Conversation, { name: "Inbox triage", face: "\uD83D\uDCEC", presence: "working", items: WORKER_ACTIVITY, composer: false }) }) }) }));
189
+ return (_jsx(Frame, { children: _jsx(Box, { flex: 1, display: "flex", justifyContent: "center", children: _jsx(Box, { width: "100%", maxWidth: 680, display: "flex", children: _jsx(Conversation, { name: "Inbox triage", face: "\uD83D\uDCEC", presence: "working", items: WORKER_ACTIVITY, composer: false }) }) }) }));
224
190
  }
225
191
  /** An approval: the action, the rule that asks, and the one decision. */
226
192
  export function ApprovalScreen() {
227
- return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 640, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "waiting", items: APPROVAL }) }) }) }));
193
+ return (_jsx(Frame, { children: _jsx(Box, { flex: 1, display: "flex", justifyContent: "center", children: _jsx(Box, { width: "100%", maxWidth: 640, display: "flex", children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "waiting", items: APPROVAL }) }) }) }));
228
194
  }
229
195
  /** Tool calls, each led by the mark of whoever the tool belongs to. */
230
196
  export function ToolMarksScreen() {
231
- return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 680, display: 'flex' }, children: _jsx(Conversation, { name: "Bookkeeper", face: "\uD83E\uDDEE", presence: "idle", items: TOOL_MARKS, composer: false, mcpServers: TOOL_MARKS_SERVERS }) }) }) }));
197
+ return (_jsx(Frame, { children: _jsx(Box, { flex: 1, display: "flex", justifyContent: "center", children: _jsx(Box, { width: "100%", maxWidth: 680, display: "flex", children: _jsx(Conversation, { name: "Bookkeeper", face: "\uD83E\uDDEE", presence: "idle", items: TOOL_MARKS, composer: false, mcpServers: TOOL_MARKS_SERVERS }) }) }) }));
232
198
  }
233
199
  export const REFERENCE_SCREEN_COMPONENTS = {
234
200
  conversation: ConversationScreen,
@@ -284,20 +250,7 @@ export function AssistantScreen({ picture, }) {
284
250
  ? 'working'
285
251
  : picture;
286
252
  const balloon = BALLOONS[picture];
287
- return (_jsxs(Box, { sx: { flex: 1, position: 'relative' }, children: [picture === 'open' && _jsx(OpenConversation, {}), picture === 'aside' && (_jsxs(Box, { role: "dialog", "aria-label": "A dialog over the assistant", sx: {
288
- position: 'fixed',
289
- right: 24,
290
- bottom: 24,
291
- width: 280,
292
- height: 160,
293
- p: 3,
294
- zIndex: 2000,
295
- bg: 'canvas.overlay',
296
- border: 'var(--theme-hairline, 1px) solid',
297
- borderColor: 'border.default',
298
- borderRadius: 'var(--theme-radius-card, 6px)',
299
- boxShadow: 'var(--theme-shadow, none)',
300
- }, children: [_jsx(Text, { sx: { fontWeight: 600 }, children: "A dialog" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1 }, children: "The assistant steps aside while this is open." })] })), _jsx(AssistantStage, { character: "paperclip", state: state, place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: picture === 'open', onToggle: () => undefined, balloon: balloon, balloonDisplay: picture === 'current' ? 'current' : 'history', insist: !!balloon, onDismiss: () => undefined })] }));
253
+ return (_jsxs(Box, { flex: 1, position: "relative", children: [picture === 'open' && _jsx(OpenConversation, {}), picture === 'aside' && (_jsxs(Box, { role: "dialog", "aria-label": "A dialog over the assistant", position: "fixed", right: 24, bottom: 24, width: 280, height: 160, p: 3, zIndex: 2000, bg: "canvas.overlay", border: "var(--theme-hairline, 1px) solid", borderColor: "border.default", borderRadius: "var(--theme-radius-card, 6px)", boxShadow: "var(--theme-shadow, none)", children: [_jsx(Text, { sx: { fontWeight: 600 }, children: "A dialog" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1 }, children: "The assistant steps aside while this is open." })] })), _jsx(AssistantStage, { character: "paperclip", state: state, place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: picture === 'open', onToggle: () => undefined, balloon: balloon, balloonDisplay: picture === 'current' ? 'current' : 'history', insist: !!balloon, onDismiss: () => undefined })] }));
301
254
  }
302
255
  /** What the open picture's conversation holds. */
303
256
  const OPEN_CONVERSATION = [
@@ -316,37 +269,10 @@ function OpenConversation() {
316
269
  const [input, setInput] = useState('');
317
270
  const height = conversationBalloonHeight(window.innerHeight);
318
271
  const left = window.innerWidth - 48 - CONVERSATION_BALLOON_WIDTH;
319
- return (_jsxs(Box, { "data-conversation-balloon": "", sx: {
320
- position: 'fixed',
321
- right: '48px',
322
- bottom: `${48 + 88 + 16}px`,
323
- width: `${CONVERSATION_BALLOON_WIDTH}px`,
324
- height: `${height}px`,
325
- zIndex: 1001,
326
- display: 'flex',
327
- flexDirection: 'column',
328
- bg: 'canvas.default',
329
- border: '1px solid',
330
- borderColor: 'border.default',
331
- boxShadow: 'shadow.extra-large',
332
- ...conversationBalloonSx({
333
- edge: 'bottom',
334
- at: balloonTailAt(window.innerWidth - 48 - 44, left),
335
- }, 'canvas.subtle'),
336
- }, children: [_jsx(ConversationBalloonClose, { onClose: () => undefined }), _jsx(ConversationBalloonHeader, { count: OPEN_CONVERSATION.length - 1 }), _jsxs(Box, { sx: {
337
- flex: 1,
338
- minHeight: 0,
339
- display: 'flex',
340
- flexDirection: 'column',
341
- borderRadius: 'var(--theme-radius-bubble, 16px)',
342
- overflow: 'hidden',
343
- }, children: [_jsx(Box, { sx: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(ChatMessageList, { displayItems: OPEN_CONVERSATION, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: { ...AVATARS, showAvatars: true }, padding: 2, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined }) }), _jsx(Box, { sx: {
344
- px: 3,
345
- pb: 1,
346
- display: 'flex',
347
- justifyContent: 'flex-end',
348
- fontSize: 0,
349
- }, children: _jsx(DecisionAsk, { ask: async () => ({}) }) }), _jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Type a message...", autoFocus: false, padding: 2, promptVariant: "lexical", onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, showAgentsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false })] })] }));
272
+ return (_jsxs(Box, { "data-conversation-balloon": "", position: "fixed", right: "48px", bottom: `${48 + 88 + 16}px`, width: `${CONVERSATION_BALLOON_WIDTH}px`, height: `${height}px`, zIndex: 1001, display: "flex", flexDirection: "column", bg: "canvas.default", border: "1px solid", borderColor: "border.default", boxShadow: "shadow.extra-large", sx: conversationBalloonSx({
273
+ edge: 'bottom',
274
+ at: balloonTailAt(window.innerWidth - 48 - 44, left),
275
+ }, 'canvas.subtle'), children: [_jsx(ConversationBalloonClose, { onClose: () => undefined }), _jsx(ConversationBalloonHeader, { count: OPEN_CONVERSATION.length - 1 }), _jsxs(Box, { flex: 1, minHeight: 0, display: "flex", flexDirection: "column", borderRadius: "var(--theme-radius-bubble, 16px)", overflow: "hidden", children: [_jsx(Box, { flex: 1, minHeight: 0, overflow: "auto", children: _jsx(ChatMessageList, { displayItems: OPEN_CONVERSATION, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: { ...AVATARS, showAvatars: true }, padding: 2, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined }) }), _jsx(Box, { px: 3, pb: 1, display: "flex", justifyContent: "flex-end", fontSize: 0, children: _jsx(DecisionAsk, { ask: async () => ({}) }) }), _jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Type a message...", autoFocus: false, padding: 2, promptVariant: "lexical", onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, showAgentsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false })] })] }));
350
276
  }
351
277
  // ---------------------------------------------------------------------------
352
278
  // The characters (T-25, T-24)
@@ -364,5 +290,5 @@ characterReactor.start();
364
290
  export function CharacterScreen({ character, ready, }) {
365
291
  const stageRef = useRef(null);
366
292
  const drawn = assistantCharacterNamed(characterReactor, character);
367
- return (_jsxs(Box, { sx: { flex: 1, position: 'relative' }, children: [_jsx(AssistantStage, { character: drawn, state: "idle", place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: () => undefined, onDismiss: () => undefined }), ready] }));
293
+ return (_jsxs(Box, { flex: 1, position: "relative", children: [_jsx(AssistantStage, { character: drawn, state: "idle", place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: () => undefined, onDismiss: () => undefined }), ready] }));
368
294
  }
@@ -68,7 +68,7 @@ function Ready() {
68
68
  */
69
69
  function GalleryScreen() {
70
70
  const { characters, ready } = useGalleryCharacters();
71
- return (_jsxs(Box, { sx: { flex: 1, bg: 'canvas.default', p: 2, overflow: 'hidden' }, children: [_jsx(AssistantGalleryGrid, { characters: characters }), ready && _jsx(Ready, {})] }));
71
+ return (_jsxs(Box, { flex: 1, bg: "canvas.default", p: 2, overflow: "hidden", children: [_jsx(AssistantGalleryGrid, { characters: characters }), ready && _jsx(Ready, {})] }));
72
72
  }
73
73
  function Page() {
74
74
  if (gallery) {
@@ -670,8 +670,11 @@ export interface AppUploadsSpec {
670
670
  /** What the user of an application sees. */
671
671
  export interface AppInterfaceSpec {
672
672
  layout: AppLayout;
673
- /** Its one colour, over the `loop` theme only. */
674
- accent: AppAccent;
673
+ /**
674
+ * Its one colour, over whichever theme it runs in; unsaid, it wears the
675
+ * theme's own colours (decided 2026-10-07).
676
+ */
677
+ accent?: AppAccent;
675
678
  /** The theme it runs in by default; the person's own when unsaid (T-30). */
676
679
  theme?: AppThemeSpec;
677
680
  welcome: string;
@@ -67,6 +67,8 @@ export interface ProtocolAdapterConfig {
67
67
  maxReconnectAttempts?: number;
68
68
  /** Request timeout in milliseconds */
69
69
  timeout?: number;
70
+ /** The fetch to use; a phone passes one whose response body streams (e.g. `expo/fetch`). Default: the global `fetch`. */
71
+ fetch?: typeof fetch;
70
72
  /** Additional protocol-specific options */
71
73
  options?: Record<string, unknown>;
72
74
  }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Normalised approval record. Both snake_case (server-native) and
3
+ * camelCase (TypeScript-idiomatic) keys are present so existing UI
4
+ * consumers keep working regardless of which naming they read.
5
+ */
6
+ export type ApprovalRecord = {
7
+ id: string;
8
+ agent_id: string;
9
+ agentId: string;
10
+ runtime_name: string;
11
+ runtimeName: string;
12
+ tool_name: string;
13
+ toolName: string;
14
+ tool_call_id?: string;
15
+ toolCallId?: string;
16
+ tool_args: Record<string, unknown>;
17
+ toolArgs: Record<string, unknown>;
18
+ status: string;
19
+ note?: string | null;
20
+ requester_uid?: string;
21
+ requesterUid?: string;
22
+ requested_by?: string;
23
+ requestedBy?: string;
24
+ resolved_by?: string;
25
+ resolvedBy?: string;
26
+ resolved_at?: string;
27
+ resolvedAt?: string;
28
+ created_at: string;
29
+ createdAt: string;
30
+ updated_at?: string;
31
+ updatedAt?: string;
32
+ read?: boolean;
33
+ };
34
+ /** An approval as the server or the `/ws` stream sends it; null without an id. */
35
+ export declare function normalizeApproval(raw: unknown): ApprovalRecord | null;
36
+ /** An approval's arguments, under either key: the web reads `tool_args`, mobile `toolArgs`. */
37
+ export type ApprovalArgs = {
38
+ tool_args?: Record<string, unknown>;
39
+ toolArgs?: Record<string, unknown>;
40
+ };
41
+ /** A message it asks to send, as the approval carries it, whole (LOOP W-05). */
42
+ export type MessageAsked = {
43
+ to: string;
44
+ cc: string;
45
+ bcc: string;
46
+ subject: string;
47
+ body: string;
48
+ /** It forwards a message received, named by its id. */
49
+ forwards: boolean;
50
+ /** A reply, in the conversation it names. */
51
+ replies: boolean;
52
+ };
53
+ /**
54
+ * The message a send asks for, to show whole before *Approve* (LOOP W-05):
55
+ * an approval whose arguments say to whom and what. `null` for anything else.
56
+ */
57
+ export declare function messageOfApproval(approval: ApprovalArgs): MessageAsked | null;
58
+ /** Why it was asked: the rule (`_rule`) or the check (`_check`). */
59
+ export declare const whyAsked: (approval: ApprovalArgs) => string;
@@ -0,0 +1,101 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ function str(value) {
6
+ return typeof value === 'string' ? value : value == null ? '' : String(value);
7
+ }
8
+ /** An approval as the server or the `/ws` stream sends it; null without an id. */
9
+ export function normalizeApproval(raw) {
10
+ if (!raw || typeof raw !== 'object')
11
+ return null;
12
+ const rec = raw;
13
+ const idSource = rec.id ?? rec.approval_id ?? rec.approvalId;
14
+ const id = typeof idSource === 'string' ? idSource : undefined;
15
+ if (!id)
16
+ return null;
17
+ const agent = str(rec.agent_id ?? rec.agentId);
18
+ const pod = str(rec.runtime_name ?? rec.runtimeName);
19
+ const tool = str(rec.tool_name ?? rec.toolName ?? 'unknown');
20
+ const toolCall = rec.tool_call_id ?? rec.toolCallId;
21
+ const args = rec.tool_args ??
22
+ rec.toolArgs ??
23
+ {};
24
+ const created = str(rec.created_at ?? rec.createdAt);
25
+ const updated = str(rec.updated_at ?? rec.updatedAt);
26
+ const resolvedBy = str(rec.resolved_by ?? rec.resolvedBy);
27
+ const requesterUid = str(rec.requester_uid ?? rec.requesterUid);
28
+ const requestedBy = str(rec.requested_by ?? rec.requestedBy);
29
+ const resolvedAtRaw = str(rec.resolved_at ?? rec.resolvedAt);
30
+ const status = str(rec.status ?? 'pending');
31
+ // Keep reviewer metadata from WS payloads. If this is dropped during
32
+ // normalization, downstream pages receive status=approved/rejected but no
33
+ // reviewer/timestamp context and can incorrectly render "review pending".
34
+ // Some producers only set updated_at on decision; for non-pending statuses,
35
+ // fall back to updated_at so UI can still show a decision time.
36
+ const resolvedAt = resolvedAtRaw || (status !== 'pending' ? updated || undefined : undefined);
37
+ return {
38
+ id,
39
+ agent_id: agent,
40
+ agentId: agent,
41
+ runtime_name: pod,
42
+ runtimeName: pod,
43
+ tool_name: tool,
44
+ toolName: tool,
45
+ tool_call_id: typeof toolCall === 'string' ? toolCall : undefined,
46
+ toolCallId: typeof toolCall === 'string' ? toolCall : undefined,
47
+ tool_args: args,
48
+ toolArgs: args,
49
+ status,
50
+ note: typeof rec.note === 'string'
51
+ ? rec.note
52
+ : rec.note === null
53
+ ? null
54
+ : undefined,
55
+ requester_uid: requesterUid || undefined,
56
+ requesterUid: requesterUid || undefined,
57
+ requested_by: requestedBy || undefined,
58
+ requestedBy: requestedBy || undefined,
59
+ resolved_by: resolvedBy || undefined,
60
+ resolvedBy: resolvedBy || undefined,
61
+ resolved_at: resolvedAt,
62
+ resolvedAt: resolvedAt,
63
+ created_at: created,
64
+ createdAt: created,
65
+ updated_at: updated || undefined,
66
+ updatedAt: updated || undefined,
67
+ read: typeof rec.read === 'boolean' ? rec.read : undefined,
68
+ };
69
+ }
70
+ const argsOf = (approval) => approval.tool_args ?? approval.toolArgs ?? {};
71
+ const said = (approval, key) => {
72
+ const value = argsOf(approval)[key];
73
+ return typeof value === 'string' ? value : '';
74
+ };
75
+ const addressed = (value) => Array.isArray(value)
76
+ ? value.filter(item => typeof item === 'string').join(', ')
77
+ : typeof value === 'string'
78
+ ? value
79
+ : '';
80
+ /**
81
+ * The message a send asks for, to show whole before *Approve* (LOOP W-05):
82
+ * an approval whose arguments say to whom and what. `null` for anything else.
83
+ */
84
+ export function messageOfApproval(approval) {
85
+ const args = argsOf(approval);
86
+ const to = addressed(args.to);
87
+ if (!to || typeof args.body !== 'string') {
88
+ return null;
89
+ }
90
+ return {
91
+ to,
92
+ cc: addressed(args.cc),
93
+ bcc: addressed(args.bcc),
94
+ subject: said(approval, 'subject'),
95
+ body: args.body,
96
+ forwards: Boolean(said(approval, 'forward_message_id')),
97
+ replies: Boolean(said(approval, 'thread_id') || said(approval, 'in_reply_to')),
98
+ };
99
+ }
100
+ /** Why it was asked: the rule (`_rule`) or the check (`_check`). */
101
+ export const whyAsked = (approval) => said(approval, '_rule') || said(approval, '_check');