@datalayer/agent-runtimes 1.0.3 → 1.0.5

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 (275) hide show
  1. package/README.md +35 -119
  2. package/lib/App.js +1 -1
  3. package/lib/agents/AgentDetails.d.ts +22 -1
  4. package/lib/agents/AgentDetails.js +34 -47
  5. package/lib/api/index.d.ts +0 -1
  6. package/lib/api/index.js +4 -2
  7. package/lib/chat/Chat.d.ts +5 -104
  8. package/lib/chat/Chat.js +4 -4
  9. package/lib/chat/ChatFloating.d.ts +7 -140
  10. package/lib/chat/ChatFloating.js +2 -2
  11. package/lib/chat/ChatPopupStandalone.d.ts +8 -47
  12. package/lib/chat/ChatPopupStandalone.js +3 -3
  13. package/lib/chat/ChatSidebar.d.ts +4 -69
  14. package/lib/chat/ChatSidebar.js +2 -2
  15. package/lib/chat/ChatStandalone.d.ts +4 -54
  16. package/lib/chat/ChatStandalone.js +3 -3
  17. package/lib/chat/base/ChatBase.js +1118 -141
  18. package/lib/chat/header/ChatHeaderBase.d.ts +11 -6
  19. package/lib/chat/header/ChatHeaderBase.js +18 -16
  20. package/lib/chat/indicators/McpStatusIndicator.d.ts +7 -4
  21. package/lib/chat/indicators/McpStatusIndicator.js +7 -32
  22. package/lib/chat/indicators/SandboxStatusIndicator.d.ts +4 -1
  23. package/lib/chat/indicators/SandboxStatusIndicator.js +9 -9
  24. package/lib/chat/indicators/SkillsStatusIndicator.d.ts +7 -0
  25. package/lib/chat/indicators/SkillsStatusIndicator.js +88 -0
  26. package/lib/chat/indicators/index.d.ts +1 -0
  27. package/lib/chat/indicators/index.js +1 -0
  28. package/lib/chat/messages/ChatMessageList.d.ts +1 -1
  29. package/lib/chat/messages/ChatMessageList.js +110 -102
  30. package/lib/chat/prompt/InputFooter.d.ts +19 -6
  31. package/lib/chat/prompt/InputFooter.js +71 -18
  32. package/lib/chat/prompt/InputPrompt.d.ts +3 -1
  33. package/lib/chat/prompt/InputPrompt.js +4 -4
  34. package/lib/chat/prompt/InputPromptFooter.js +1 -1
  35. package/lib/chat/prompt/InputPromptLexical.d.ts +3 -1
  36. package/lib/chat/prompt/InputPromptLexical.js +12 -5
  37. package/lib/chat/prompt/InputPromptText.d.ts +3 -1
  38. package/lib/chat/prompt/InputPromptText.js +2 -2
  39. package/lib/chat/tools/ToolApprovalBanner.js +1 -1
  40. package/lib/chat/tools/ToolCallDisplay.d.ts +3 -1
  41. package/lib/chat/tools/ToolCallDisplay.js +2 -2
  42. package/lib/chat/usage/TokenUsageBar.js +20 -2
  43. package/lib/client/AgentRuntimesClientContext.d.ts +53 -0
  44. package/lib/client/AgentRuntimesClientContext.js +55 -0
  45. package/lib/client/AgentsMixin.d.ts +48 -19
  46. package/lib/client/AgentsMixin.js +115 -30
  47. package/lib/client/IAgentRuntimesClient.d.ts +215 -0
  48. package/lib/client/IAgentRuntimesClient.js +5 -0
  49. package/lib/client/SdkAgentRuntimesClient.d.ts +151 -0
  50. package/lib/client/SdkAgentRuntimesClient.js +134 -0
  51. package/lib/client/index.d.ts +4 -1
  52. package/lib/client/index.js +3 -1
  53. package/lib/components/NotificationEventCard.js +55 -26
  54. package/lib/components/OutputCard.js +21 -7
  55. package/lib/components/ToolApprovalCard.js +20 -2
  56. package/lib/config/AgentConfiguration.js +3 -3
  57. package/lib/context/ContextDistribution.d.ts +3 -1
  58. package/lib/context/ContextDistribution.js +8 -27
  59. package/lib/context/ContextInspector.d.ts +3 -1
  60. package/lib/context/ContextInspector.js +19 -67
  61. package/lib/context/ContextPanel.d.ts +3 -1
  62. package/lib/context/ContextPanel.js +104 -64
  63. package/lib/context/ContextUsage.d.ts +3 -1
  64. package/lib/context/ContextUsage.js +3 -3
  65. package/lib/context/CostTracker.d.ts +9 -3
  66. package/lib/context/CostTracker.js +26 -47
  67. package/lib/context/CostUsageChart.d.ts +12 -0
  68. package/lib/context/CostUsageChart.js +378 -0
  69. package/lib/context/GraphFlowChart.d.ts +16 -0
  70. package/lib/context/GraphFlowChart.js +182 -0
  71. package/lib/context/TokenUsageChart.d.ts +8 -1
  72. package/lib/context/TokenUsageChart.js +349 -211
  73. package/lib/context/TurnGraphChart.d.ts +39 -0
  74. package/lib/context/TurnGraphChart.js +538 -0
  75. package/lib/context/otelWsPool.d.ts +20 -0
  76. package/lib/context/otelWsPool.js +69 -0
  77. package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
  78. package/lib/examples/A2UiComponentGalleryExample.js +315 -522
  79. package/lib/examples/A2UiContactCardExample.d.ts +0 -18
  80. package/lib/examples/A2UiContactCardExample.js +154 -411
  81. package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
  82. package/lib/examples/A2UiRestaurantExample.js +114 -212
  83. package/lib/examples/A2UiViewerExample.d.ts +0 -18
  84. package/lib/examples/A2UiViewerExample.js +283 -532
  85. package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
  86. package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
  87. package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
  88. package/lib/examples/AgentCheckpointsExample.js +14 -34
  89. package/lib/examples/AgentCodemodeExample.d.ts +4 -6
  90. package/lib/examples/AgentCodemodeExample.js +591 -175
  91. package/lib/examples/AgentEvalsExample.js +13 -23
  92. package/lib/examples/AgentGuardrailsExample.js +371 -71
  93. package/lib/examples/AgentHooksExample.d.ts +3 -0
  94. package/lib/examples/AgentHooksExample.js +104 -0
  95. package/lib/examples/AgentMCPExample.d.ts +3 -0
  96. package/lib/examples/AgentMCPExample.js +480 -0
  97. package/lib/examples/AgentMemoryExample.js +14 -24
  98. package/lib/examples/AgentMonitoringExample.js +261 -206
  99. package/lib/examples/AgentNotificationsExample.js +50 -24
  100. package/lib/examples/AgentOtelExample.js +2 -3
  101. package/lib/examples/AgentOutputsExample.d.ts +11 -6
  102. package/lib/examples/AgentOutputsExample.js +383 -88
  103. package/lib/examples/AgentParametersExample.d.ts +3 -0
  104. package/lib/examples/AgentParametersExample.js +246 -0
  105. package/lib/examples/AgentSandboxExample.d.ts +2 -2
  106. package/lib/examples/AgentSandboxExample.js +69 -47
  107. package/lib/examples/AgentSkillsExample.js +92 -106
  108. package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
  109. package/lib/examples/AgentSubagentsExample.d.ts +14 -0
  110. package/lib/examples/AgentSubagentsExample.js +228 -0
  111. package/lib/examples/AgentToolApprovalsExample.js +30 -493
  112. package/lib/examples/AgentTriggersExample.js +1067 -246
  113. package/lib/examples/ChatCustomExample.js +11 -24
  114. package/lib/examples/ChatExample.js +9 -34
  115. package/lib/examples/CopilotKitLexicalExample.js +2 -1
  116. package/lib/examples/CopilotKitNotebookExample.js +2 -1
  117. package/lib/examples/HomeExample.d.ts +15 -0
  118. package/lib/examples/HomeExample.js +77 -0
  119. package/lib/examples/Lexical2Example.js +4 -2
  120. package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
  121. package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
  122. package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
  123. package/lib/examples/LexicalAgentSidebarExample.js +261 -0
  124. package/lib/examples/NotebookAgentExample.d.ts +9 -0
  125. package/lib/examples/NotebookAgentExample.js +192 -0
  126. package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
  127. package/lib/examples/NotebookAgentSidebarExample.js +221 -0
  128. package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
  129. package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
  130. package/lib/examples/NotebookExample.d.ts +4 -7
  131. package/lib/examples/NotebookExample.js +14 -146
  132. package/lib/examples/components/AuthRequiredView.d.ts +6 -0
  133. package/lib/examples/components/AuthRequiredView.js +33 -0
  134. package/lib/examples/components/ErrorView.d.ts +14 -0
  135. package/lib/examples/components/ErrorView.js +20 -0
  136. package/lib/examples/components/ExampleWrapper.d.ts +7 -0
  137. package/lib/examples/components/ExampleWrapper.js +25 -6
  138. package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
  139. package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
  140. package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
  141. package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
  142. package/lib/examples/components/index.d.ts +5 -0
  143. package/lib/examples/components/index.js +5 -0
  144. package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
  145. package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
  146. package/lib/examples/example-selector.d.ts +17 -4
  147. package/lib/examples/example-selector.js +107 -41
  148. package/lib/examples/index.d.ts +9 -6
  149. package/lib/examples/index.js +9 -6
  150. package/lib/examples/main.d.ts +1 -0
  151. package/lib/examples/main.js +218 -27
  152. package/lib/examples/utils/a2ui.d.ts +18 -0
  153. package/lib/examples/utils/a2ui.js +69 -0
  154. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
  155. package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
  156. package/lib/examples/utils/agentId.d.ts +18 -0
  157. package/lib/examples/utils/agentId.js +54 -0
  158. package/lib/examples/utils/agents/earthquake-detector.json +11 -11
  159. package/lib/examples/utils/agents/sales-forecaster.json +11 -11
  160. package/lib/examples/utils/agents/social-post-generator.json +11 -11
  161. package/lib/examples/utils/agents/stock-market.json +11 -11
  162. package/lib/examples/utils/examplesStore.js +82 -27
  163. package/lib/hooks/index.d.ts +8 -8
  164. package/lib/hooks/index.js +7 -7
  165. package/lib/hooks/useA2A.d.ts +2 -3
  166. package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
  167. package/lib/hooks/useAIAgentsWebSocket.js +118 -12
  168. package/lib/hooks/useAcp.d.ts +1 -2
  169. package/lib/hooks/useAgUi.d.ts +1 -1
  170. package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
  171. package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
  172. package/lib/hooks/useAgentsCatalog.js +1 -1
  173. package/lib/hooks/useAgentsService.d.ts +2 -2
  174. package/lib/hooks/useAgentsService.js +7 -7
  175. package/lib/hooks/useCheckpoints.js +1 -1
  176. package/lib/hooks/useConfig.d.ts +4 -1
  177. package/lib/hooks/useConfig.js +10 -3
  178. package/lib/hooks/useContextSnapshot.d.ts +9 -4
  179. package/lib/hooks/useContextSnapshot.js +9 -37
  180. package/lib/hooks/useMonitoring.js +3 -0
  181. package/lib/hooks/useSandbox.d.ts +20 -8
  182. package/lib/hooks/useSandbox.js +105 -40
  183. package/lib/hooks/useSkills.d.ts +23 -5
  184. package/lib/hooks/useSkills.js +94 -39
  185. package/lib/hooks/useToolApprovals.d.ts +60 -36
  186. package/lib/hooks/useToolApprovals.js +318 -69
  187. package/lib/hooks/useVercelAI.d.ts +1 -1
  188. package/lib/index.d.ts +2 -1
  189. package/lib/index.js +1 -0
  190. package/lib/inference/index.d.ts +0 -1
  191. package/lib/middleware/index.d.ts +0 -1
  192. package/lib/protocols/AGUIAdapter.js +6 -0
  193. package/lib/protocols/VercelAIAdapter.d.ts +9 -0
  194. package/lib/protocols/VercelAIAdapter.js +144 -26
  195. package/lib/shims/json5.d.ts +4 -0
  196. package/lib/shims/json5.js +8 -0
  197. package/lib/specs/agents/agents.d.ts +10 -0
  198. package/lib/specs/agents/agents.js +752 -24
  199. package/lib/specs/envvars.d.ts +1 -0
  200. package/lib/specs/envvars.js +11 -0
  201. package/lib/specs/events.d.ts +1 -0
  202. package/lib/specs/events.js +1 -0
  203. package/lib/specs/index.d.ts +1 -0
  204. package/lib/specs/index.js +1 -0
  205. package/lib/specs/personas.d.ts +41 -0
  206. package/lib/specs/personas.js +168 -0
  207. package/lib/specs/skills.d.ts +2 -1
  208. package/lib/specs/skills.js +23 -5
  209. package/lib/specs/tools.js +3 -0
  210. package/lib/stores/agentRuntimeStore.d.ts +204 -0
  211. package/lib/stores/agentRuntimeStore.js +636 -0
  212. package/lib/stores/index.d.ts +1 -1
  213. package/lib/stores/index.js +1 -1
  214. package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
  215. package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
  216. package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
  217. package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
  218. package/lib/tools/index.d.ts +0 -2
  219. package/lib/tools/index.js +0 -1
  220. package/lib/types/agentspecs.d.ts +50 -1
  221. package/lib/types/chat.d.ts +309 -8
  222. package/lib/types/context.d.ts +27 -0
  223. package/lib/types/cost.d.ts +2 -2
  224. package/lib/types/index.d.ts +2 -0
  225. package/lib/types/index.js +2 -0
  226. package/lib/types/mcp.d.ts +8 -0
  227. package/lib/types/models.d.ts +2 -2
  228. package/lib/types/personas.d.ts +25 -0
  229. package/lib/types/personas.js +5 -0
  230. package/lib/types/skills.d.ts +43 -1
  231. package/lib/types/stream.d.ts +110 -0
  232. package/lib/types/stream.js +36 -0
  233. package/lib/types/tools.d.ts +2 -0
  234. package/lib/utils/utils.d.ts +9 -5
  235. package/lib/utils/utils.js +9 -5
  236. package/package.json +13 -9
  237. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  238. package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
  239. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
  240. package/scripts/codegen/generate_agents.py +106 -7
  241. package/scripts/codegen/generate_events.py +47 -17
  242. package/scripts/codegen/generate_personas.py +319 -0
  243. package/scripts/codegen/generate_skills.py +9 -9
  244. package/scripts/codegen/generate_tools.py +20 -0
  245. package/scripts/sync-jupyter.sh +26 -7
  246. package/style/primer-primitives.css +1 -6
  247. package/lib/api/tool-approvals.d.ts +0 -62
  248. package/lib/api/tool-approvals.js +0 -145
  249. package/lib/examples/LexicalSidebarExample.js +0 -163
  250. package/lib/examples/NotebookSidebarExample.js +0 -119
  251. package/lib/examples/NotebookSimpleExample.d.ts +0 -6
  252. package/lib/examples/NotebookSimpleExample.js +0 -22
  253. package/lib/examples/ag-ui/index.d.ts +0 -10
  254. package/lib/examples/ag-ui/index.js +0 -16
  255. package/lib/hooks/useAgentsRegistry.d.ts +0 -10
  256. package/lib/hooks/useAgentsRegistry.js +0 -20
  257. package/lib/stores/agentsStore.d.ts +0 -123
  258. package/lib/stores/agentsStore.js +0 -270
  259. package/scripts/codegen/__pycache__/generate_envvars.cpython-313.pyc +0 -0
  260. package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
  261. package/scripts/codegen/__pycache__/generate_guardrails.cpython-313.pyc +0 -0
  262. package/scripts/codegen/__pycache__/generate_mcp_servers.cpython-313.pyc +0 -0
  263. package/scripts/codegen/__pycache__/generate_memory.cpython-313.pyc +0 -0
  264. package/scripts/codegen/__pycache__/generate_models.cpython-313.pyc +0 -0
  265. package/scripts/codegen/__pycache__/generate_notifications.cpython-313.pyc +0 -0
  266. package/scripts/codegen/__pycache__/generate_outputs.cpython-313.pyc +0 -0
  267. package/scripts/codegen/__pycache__/generate_skills.cpython-313.pyc +0 -0
  268. package/scripts/codegen/__pycache__/generate_teams.cpython-313.pyc +0 -0
  269. package/scripts/codegen/__pycache__/generate_tools.cpython-313.pyc +0 -0
  270. package/scripts/codegen/__pycache__/generate_triggers.cpython-313.pyc +0 -0
  271. /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
  272. /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
  273. /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
  274. /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
  275. /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.js +0 -0
@@ -9,19 +9,24 @@
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
11
  import type { ChatViewMode, HeaderButtonsConfig } from '../../types/chat';
12
- import type { SandboxStatusData } from '../../types/context';
12
+ import type { SandboxWsStatus } from '../../types/sandbox';
13
13
  export interface ChatBaseHeaderProps {
14
14
  title?: string;
15
+ subtitle?: string;
15
16
  brandIcon?: ReactNode;
16
17
  headerContent?: ReactNode;
17
18
  headerActions?: ReactNode;
18
19
  showInformation?: boolean;
19
20
  onInformationClick?: () => void;
20
21
  padding: number;
21
- /** Sandbox status from the backend */
22
- sandboxStatus?: SandboxStatusData;
23
- /** Callback to interrupt sandbox code execution */
24
- onSandboxInterrupt: () => void;
22
+ /** API base URL passed to SandboxStatusIndicator */
23
+ sandboxApiBase?: string;
24
+ /** Auth token passed to SandboxStatusIndicator */
25
+ sandboxAuthToken?: string;
26
+ /** Agent ID passed to SandboxStatusIndicator for agent-scoped status */
27
+ sandboxAgentId?: string;
28
+ /** Optional sandbox status override for immediate indicator updates */
29
+ sandboxStatusData?: SandboxWsStatus | null;
25
30
  /** Header button configuration */
26
31
  headerButtons?: HeaderButtonsConfig;
27
32
  /** Current count of messages (used to conditionally show clear button) */
@@ -35,4 +40,4 @@ export interface ChatBaseHeaderProps {
35
40
  /** Callback when view mode changes */
36
41
  onChatViewModeChange?: (mode: ChatViewMode) => void;
37
42
  }
38
- export declare function ChatBaseHeader({ title, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxStatus, onSandboxInterrupt, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }: ChatBaseHeaderProps): import("react/jsx-runtime").JSX.Element;
43
+ export declare function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxApiBase, sandboxAuthToken, sandboxAgentId, sandboxStatusData, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }: ChatBaseHeaderProps): import("react/jsx-runtime").JSX.Element;
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Heading, IconButton, Truncate } from '@primer/react';
2
+ import { Heading, IconButton, Text, Truncate } from '@primer/react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
- import { PlusIcon, TrashIcon, GearIcon, CircleIcon, SquareFillIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
4
+ import { PlusIcon, TrashIcon, GearIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
5
5
  import { AiAgentIcon } from '@datalayer/icons-react';
6
+ import { SandboxStatusIndicator } from '../indicators/SandboxStatusIndicator';
6
7
  // ---------------------------------------------------------------------------
7
8
  // Component
8
9
  // ---------------------------------------------------------------------------
9
- export function ChatBaseHeader({ title, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxStatus, onSandboxInterrupt, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }) {
10
+ export function ChatBaseHeader({ title, subtitle, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, sandboxApiBase, sandboxAuthToken, sandboxAgentId, sandboxStatusData, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, }) {
10
11
  return (_jsx(Box, { sx: {
11
12
  display: 'flex',
12
13
  flexDirection: 'column',
@@ -23,21 +24,22 @@ export function ChatBaseHeader({ title, brandIcon, headerContent, headerActions,
23
24
  gap: 2,
24
25
  minWidth: 0,
25
26
  flex: '1 1 auto',
26
- }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), title && (_jsx(Heading, { as: "h3", sx: {
27
- fontSize: 2,
28
- fontWeight: 'semibold',
27
+ }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), (title || subtitle) && (_jsxs(Box, { sx: {
28
+ display: 'flex',
29
+ flexDirection: 'column',
29
30
  minWidth: 0,
30
31
  maxWidth: '100%',
31
- }, children: _jsx(Truncate, { title: title, maxWidth: "28ch", children: title }) })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [sandboxStatus?.available &&
32
- sandboxStatus?.sandbox_running &&
33
- (sandboxStatus.is_executing ? (_jsx(IconButton, { icon: SquareFillIcon, "aria-label": "Interrupt code execution", variant: "invisible", size: "small", sx: { color: 'danger.fg' }, onClick: onSandboxInterrupt })) : (_jsx(Box, { sx: {
34
- display: 'flex',
35
- alignItems: 'center',
36
- justifyContent: 'center',
37
- width: 28,
38
- height: 28,
39
- color: 'fg.subtle',
40
- }, title: "Code sandbox ready", children: _jsx(CircleIcon, { size: 12 }) }))), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
32
+ }, children: [title && (_jsx(Heading, { as: "h3", sx: {
33
+ fontSize: 2,
34
+ fontWeight: 'semibold',
35
+ minWidth: 0,
36
+ maxWidth: '100%',
37
+ }, children: _jsx(Truncate, { title: title, maxWidth: "28ch", children: title }) })), subtitle && (_jsx(Text, { sx: {
38
+ fontSize: 0,
39
+ color: 'fg.muted',
40
+ minWidth: 0,
41
+ maxWidth: '100%',
42
+ }, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [_jsx(SandboxStatusIndicator, { apiBase: sandboxApiBase, authToken: sandboxAuthToken, agentId: sandboxAgentId, statusOverride: sandboxStatusData }), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
41
43
  display: 'inline-flex',
42
44
  alignItems: 'center',
43
45
  bg: 'neutral.muted',
@@ -1,9 +1,12 @@
1
+ import type { McpToolsetsStatusResponse } from '../../types/mcp';
1
2
  export interface McpStatusIndicatorProps {
2
- /** API base URL (e.g. "http://127.0.0.1:8765"). Defaults to
3
- * the current host on non-localhost, or localhost:8765. */
3
+ /** Pre-fetched MCP status data pushed via WebSocket. When provided the
4
+ * component will NOT poll the REST endpoint. */
5
+ data?: McpToolsetsStatusResponse | null;
6
+ /** @deprecated — Only used when `data` is not provided. */
4
7
  apiBase?: string;
5
- /** Optional auth token for authenticated requests (e.g. K8s ingress). */
8
+ /** @deprecated Only used when `data` is not provided. */
6
9
  authToken?: string;
7
10
  }
8
- export declare function McpStatusIndicator({ apiBase, authToken, }: McpStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
11
+ export declare function McpStatusIndicator({ data: wsData, apiBase, authToken, }: McpStatusIndicatorProps): import("react/jsx-runtime").JSX.Element;
9
12
  export default McpStatusIndicator;
@@ -21,19 +21,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
21
21
  import { useEffect, useMemo } from 'react';
22
22
  import { Tooltip } from '@primer/react';
23
23
  import { Box } from '@datalayer/primer-addons';
24
- import { useQuery } from '@tanstack/react-query';
25
24
  import { MCP_STATUS_COLORS, MCP_STATUS_LABELS } from '../../types/mcp';
26
25
  /* ── Helpers ───────────────────────────────────────────── */
27
- function getApiBase(apiBase) {
28
- if (apiBase)
29
- return apiBase;
30
- if (typeof window === 'undefined')
31
- return '';
32
- const host = window.location.hostname;
33
- return host === 'localhost' || host === '127.0.0.1'
34
- ? 'http://127.0.0.1:8765'
35
- : '';
36
- }
37
26
  function deriveAggregate(servers) {
38
27
  if (!servers || servers.length === 0)
39
28
  return 'none';
@@ -47,7 +36,7 @@ function deriveAggregate(servers) {
47
36
  }
48
37
  function buildTooltipText(aggregate, servers) {
49
38
  if (aggregate === 'none')
50
- return MCP_STATUS_LABELS.none;
39
+ return 'No MCP Server defined';
51
40
  const lines = [MCP_STATUS_LABELS[aggregate]];
52
41
  for (const s of servers) {
53
42
  let detail = `• ${s.id}: ${s.status}`;
@@ -79,29 +68,15 @@ function useInjectKeyframes() {
79
68
  }, []);
80
69
  }
81
70
  /* ── Component ─────────────────────────────────────────── */
82
- export function McpStatusIndicator({ apiBase, authToken, }) {
71
+ export function McpStatusIndicator({ data: wsData, apiBase, authToken, }) {
83
72
  useInjectKeyframes();
84
- const { data } = useQuery({
85
- queryKey: ['mcp-toolsets-status', apiBase],
86
- queryFn: async () => {
87
- const base = getApiBase(apiBase);
88
- const headers = {};
89
- if (authToken) {
90
- headers['Authorization'] = `Bearer ${authToken}`;
91
- }
92
- const response = await fetch(`${base}/api/v1/configure/mcp-toolsets-status`, { headers });
93
- if (!response.ok)
94
- throw new Error('Failed to fetch MCP status');
95
- return response.json();
96
- },
97
- refetchInterval: 5000,
98
- });
99
- const servers = data?.servers ?? [];
73
+ // REST polling removed — data comes exclusively via WS `agent.snapshot`.
74
+ const effectiveData = wsData;
75
+ const servers = effectiveData?.servers ?? [];
100
76
  const aggregate = useMemo(() => deriveAggregate(servers), [servers]);
101
77
  const tooltipText = useMemo(() => buildTooltipText(aggregate, servers), [aggregate, servers]);
102
- // Hide when no servers are configured at all.
103
- if (aggregate === 'none')
104
- return null;
78
+ // Show a subtle gray dot when no MCP servers are configured.
79
+ // The tooltip tells the user none are defined.
105
80
  return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, style: {
106
81
  display: 'inline-flex',
107
82
  alignItems: 'center',
@@ -1,3 +1,4 @@
1
+ import type { SandboxWsStatus } from '../../types/sandbox';
1
2
  export interface SandboxStatusIndicatorProps {
2
3
  /** API base URL (e.g. "http://127.0.0.1:8765"). */
3
4
  apiBase?: string;
@@ -5,6 +6,8 @@ export interface SandboxStatusIndicatorProps {
5
6
  authToken?: string;
6
7
  /** Agent ID to scope sandbox status to a specific agent. */
7
8
  agentId?: string;
9
+ /** Optional status override to update indicator immediately from parent UI. */
10
+ statusOverride?: SandboxWsStatus | null;
8
11
  }
9
- export declare function SandboxStatusIndicator({ apiBase, authToken, agentId, }: SandboxStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export declare function SandboxStatusIndicator({ apiBase, authToken, agentId, statusOverride, }: SandboxStatusIndicatorProps): import("react/jsx-runtime").JSX.Element;
10
13
  export default SandboxStatusIndicator;
@@ -81,7 +81,7 @@ function useInjectKeyframes() {
81
81
  }, []);
82
82
  }
83
83
  /* ── Component ─────────────────────────────────────────── */
84
- export function SandboxStatusIndicator({ apiBase, authToken, agentId, }) {
84
+ export function SandboxStatusIndicator({ apiBase, authToken, agentId, statusOverride, }) {
85
85
  useInjectKeyframes();
86
86
  const [status, setStatus] = useState(null);
87
87
  const wsRef = useRef(null);
@@ -138,17 +138,17 @@ export function SandboxStatusIndicator({ apiBase, authToken, agentId, }) {
138
138
  }
139
139
  }, []);
140
140
  // ---- Derived display values ----
141
- const aggregate = useMemo(() => deriveAggregate(status), [status]);
141
+ const effectiveStatus = statusOverride ?? status;
142
+ const aggregate = useMemo(() => deriveAggregate(effectiveStatus), [effectiveStatus]);
142
143
  const tooltipText = useMemo(() => {
143
- if (!status)
144
- return SANDBOX_STATUS_LABELS.unavailable;
144
+ if (!effectiveStatus)
145
+ return 'No Sandbox defined';
145
146
  const label = SANDBOX_STATUS_LABELS[aggregate];
146
- const variant = status.variant;
147
+ const variant = effectiveStatus.variant;
147
148
  return `${label} (${variant})`;
148
- }, [aggregate, status]);
149
- // Hide when sandbox is not available at all.
150
- if (aggregate === 'unavailable')
151
- return null;
149
+ }, [aggregate, effectiveStatus]);
150
+ // Show a subtle gray dot when sandbox is unavailable.
151
+ // The tooltip tells the user none is configured.
152
152
  return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, onClick: aggregate === 'executing' ? sendInterrupt : undefined, style: {
153
153
  display: 'inline-flex',
154
154
  alignItems: 'center',
@@ -0,0 +1,7 @@
1
+ export interface SkillsStatusIndicatorProps {
2
+ skillsCount: number;
3
+ enabledCount: number;
4
+ loading?: boolean;
5
+ }
6
+ export declare function SkillsStatusIndicator({ skillsCount, enabledCount, loading, }: SkillsStatusIndicatorProps): import("react/jsx-runtime").JSX.Element;
7
+ export default SkillsStatusIndicator;
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * SkillsStatusIndicator — Round status dot representing skill state.
8
+ *
9
+ * Aggregate logic:
10
+ * - no skills configured -> none (gray)
11
+ * - skills loading -> loading (amber, pulsing)
12
+ * - some enabled -> active (green)
13
+ * - none enabled -> inactive (gray)
14
+ */
15
+ import { useEffect, useMemo } from 'react';
16
+ import { Tooltip } from '@primer/react';
17
+ import { Box } from '@datalayer/primer-addons';
18
+ const SKILLS_STATUS_COLORS = {
19
+ none: '#8b949e',
20
+ loading: '#d29922',
21
+ inactive: '#8b949e',
22
+ active: '#3fb950',
23
+ };
24
+ const SKILLS_PULSE_KEYFRAMES = `
25
+ @keyframes skills-pulse {
26
+ 0%, 100% { opacity: 1; }
27
+ 50% { opacity: 0.4; }
28
+ }
29
+ `;
30
+ function useInjectKeyframes() {
31
+ useEffect(() => {
32
+ const id = '__skills-pulse-keyframes__';
33
+ if (document.getElementById(id))
34
+ return;
35
+ const style = document.createElement('style');
36
+ style.id = id;
37
+ style.textContent = SKILLS_PULSE_KEYFRAMES;
38
+ document.head.appendChild(style);
39
+ }, []);
40
+ }
41
+ function deriveAggregate(skillsCount, enabledCount, loading) {
42
+ if (skillsCount <= 0)
43
+ return 'none';
44
+ if (loading)
45
+ return 'loading';
46
+ if (enabledCount > 0)
47
+ return 'active';
48
+ return 'inactive';
49
+ }
50
+ function buildTooltip(aggregate, skillsCount, enabledCount) {
51
+ if (aggregate === 'none')
52
+ return 'No Skills defined';
53
+ if (aggregate === 'loading') {
54
+ return `Skills loading (${enabledCount}/${skillsCount} enabled)`;
55
+ }
56
+ if (aggregate === 'active') {
57
+ return `Skills active (${enabledCount}/${skillsCount} enabled)`;
58
+ }
59
+ return `Skills available (${enabledCount}/${skillsCount} enabled)`;
60
+ }
61
+ export function SkillsStatusIndicator({ skillsCount, enabledCount, loading = false, }) {
62
+ useInjectKeyframes();
63
+ const aggregate = useMemo(() => deriveAggregate(skillsCount, enabledCount, loading), [skillsCount, enabledCount, loading]);
64
+ const tooltipText = useMemo(() => buildTooltip(aggregate, skillsCount, enabledCount), [aggregate, skillsCount, enabledCount]);
65
+ return (_jsx(Tooltip, { text: tooltipText, direction: "n", children: _jsx("button", { type: "button", "aria-label": tooltipText, style: {
66
+ display: 'inline-flex',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ width: 28,
70
+ height: 28,
71
+ padding: 0,
72
+ border: 'none',
73
+ background: 'none',
74
+ cursor: 'default',
75
+ lineHeight: 0,
76
+ }, children: _jsx(Box, { as: "span", sx: {
77
+ display: 'inline-block',
78
+ width: 12,
79
+ height: 12,
80
+ borderRadius: '50%',
81
+ bg: SKILLS_STATUS_COLORS[aggregate],
82
+ flexShrink: 0,
83
+ ...(aggregate === 'loading' && {
84
+ animation: 'skills-pulse 1.5s ease-in-out infinite',
85
+ }),
86
+ } }) }) }));
87
+ }
88
+ export default SkillsStatusIndicator;
@@ -15,3 +15,4 @@ export { McpStatusIndicator, type McpStatusIndicatorProps, } from './McpStatusIn
15
15
  export type { McpServerStatus, McpAggregateStatus } from '../../types/mcp';
16
16
  export { SandboxStatusIndicator, type SandboxStatusIndicatorProps, } from './SandboxStatusIndicator';
17
17
  export type { SandboxAggregateStatus, SandboxWsStatus, } from '../../types/sandbox';
18
+ export { SkillsStatusIndicator, type SkillsStatusIndicatorProps, } from './SkillsStatusIndicator';
@@ -17,3 +17,4 @@
17
17
  */
18
18
  export { McpStatusIndicator, } from './McpStatusIndicator';
19
19
  export { SandboxStatusIndicator, } from './SandboxStatusIndicator';
20
+ export { SkillsStatusIndicator, } from './SkillsStatusIndicator';
@@ -7,7 +7,7 @@
7
7
  import { type ReactNode, type RefObject } from 'react';
8
8
  import type { DisplayItem, AvatarConfig, RenderToolResult } from '../../types/chat';
9
9
  export interface ToolApprovalConfig {
10
- /** Base API URL for the agent runtime (e.g., http://localhost:8765/api/v1) */
10
+ /** Base API URL for the agent runtime (e.g., `http://localhost:8765/api/v1`). */
11
11
  apiBaseUrl: string;
12
12
  /** Auth token for API calls */
13
13
  authToken?: string;
@@ -9,125 +9,133 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
9
9
  *
10
10
  * @module chat/messages/MessageList
11
11
  */
12
- import { useState, useEffect, useCallback, } from 'react';
12
+ import { useState, useCallback, useMemo, } from 'react';
13
13
  import { Text } from '@primer/react';
14
14
  import { Box } from '@datalayer/primer-addons';
15
15
  import { Streamdown } from 'streamdown';
16
16
  import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/streamdownStyles';
17
17
  import { ToolCallDisplay } from '../tools/ToolCallDisplay';
18
18
  import { isToolCallMessage, getMessageText } from '../../utils';
19
+ import { useAgentRuntimeStore } from '../../stores/agentRuntimeStore';
19
20
  // ---------------------------------------------------------------------------
20
- // DefaultToolCallRenderer — handles tool approval for a single tool call
21
+ // Normalize helper
21
22
  // ---------------------------------------------------------------------------
22
- function DefaultToolCallRenderer({ item, approvalConfig, }) {
23
+ function normalizeName(name) {
24
+ return name
25
+ .replace(/:[0-9]+\.[0-9]+\.[0-9]+.*$/, '')
26
+ .replace(/[-_]/g, '')
27
+ .toLowerCase();
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // DefaultToolCallRenderer — reads approvals from the Zustand store,
31
+ // sends decisions over the shared WebSocket (no REST polling).
32
+ // ---------------------------------------------------------------------------
33
+ function DefaultToolCallRenderer({ item, onRespond, }) {
23
34
  const resultObject = item.result && typeof item.result === 'object'
24
35
  ? item.result
25
36
  : undefined;
26
37
  // Detect pending approval from tool result (pydantic-deferred mode)
27
38
  const isPendingFromResult = item.status === 'inProgress' && resultObject?.pending_approval === true;
39
+ // Extract the approval_id carried in the tool result by the adapter.
40
+ // This is always available when the SSE stream contained a
41
+ // `tool-approval-request` event, even when the Zustand store has not been
42
+ // populated (e.g. no monitoring WS open).
43
+ const resultApprovalId = typeof resultObject?.approval_id === 'string'
44
+ ? resultObject.approval_id
45
+ : undefined;
28
46
  // In ai-agents-wrapper mode, the server blocks waiting for approval and
29
47
  // never sends a tool-result with pending_approval. The tool stays in
30
- // 'executing' status. We proactively poll the approval API to detect if
31
- // a pending approval record exists for this tool.
48
+ // 'executing' status.
32
49
  const isExecuting = item.status === 'executing';
33
- const [matchedApprovalId, setMatchedApprovalId] = useState(null);
34
50
  const [decision, setDecision] = useState();
35
- const [loading, setLoading] = useState(false);
36
- // Should we poll the approval API?
37
- const shouldPoll = (isPendingFromResult || isExecuting) &&
38
- !!approvalConfig?.apiBaseUrl &&
39
- !decision;
40
- // Normalize tool name for matching (strip version suffix, dashes/underscores, lowercase)
41
- const normalizedToolName = item.toolName
42
- .replace(/:[0-9]+\.[0-9]+\.[0-9]+.*$/, '')
43
- .replace(/[-_]/g, '')
44
- .toLowerCase();
45
- // Poll for matching approval record when tool is executing or pending approval
46
- useEffect(() => {
47
- if (!shouldPoll || !approvalConfig?.apiBaseUrl)
51
+ const normalizedToolName = useMemo(() => normalizeName(item.toolName), [item.toolName]);
52
+ // Skill tools (`run_skill_script`, `load_skill`, `read_skill_resource`) are
53
+ // gated by the server under a synthetic approval ``tool_name`` of the form
54
+ // ``skill:<skill_id>`` (see ``SkillsGuardrailCapability._request_skill_approval``).
55
+ // Capture the skill id carried in the tool-call arguments so we can match
56
+ // the inline approval button against that synthetic approval entry.
57
+ const skillApprovalKey = useMemo(() => {
58
+ const SKILL_TOOLS = new Set([
59
+ 'run_skill_script',
60
+ 'load_skill',
61
+ 'read_skill_resource',
62
+ ]);
63
+ if (!SKILL_TOOLS.has(item.toolName))
64
+ return null;
65
+ const a = (item.args ?? {});
66
+ const raw = a.skill_name ?? a.skill ?? a.name;
67
+ if (typeof raw !== 'string' || raw.length === 0)
68
+ return null;
69
+ // Strip optional ``<name>:<version>`` suffix to the base skill id.
70
+ const base = raw.split(':', 1)[0] || raw;
71
+ return `skill:${base}`.toLowerCase();
72
+ }, [item.toolName, item.args]);
73
+ // Read pending approvals from the Zustand store (fed by the agent-runtime WS).
74
+ const approvals = useAgentRuntimeStore(s => s.approvals);
75
+ // Prefer an exact match by approval id (when present in SSE tool result).
76
+ const matchedByResultId = useMemo(() => {
77
+ if (!resultApprovalId)
78
+ return null;
79
+ return approvals.find(a => a.id === resultApprovalId) ?? null;
80
+ }, [approvals, resultApprovalId]);
81
+ // Match the synthetic ``skill:<id>`` approval for skill tool calls. This
82
+ // runs whenever the tool call is a skill tool so the inline Approve/Deny
83
+ // buttons surface even when the server is blocked in
84
+ // ``ToolApprovalManager.request_and_wait`` (status stays ``executing`` and
85
+ // no ``pending_approval=True`` result is ever emitted).
86
+ const matchedBySkill = useMemo(() => {
87
+ if (!skillApprovalKey)
88
+ return null;
89
+ return (approvals.find(a => a.tool_name?.toLowerCase() === skillApprovalKey) ??
90
+ null);
91
+ }, [approvals, skillApprovalKey]);
92
+ // Fallback to matching by tool name for inline approval flows that don't
93
+ // always provide approval_id in the tool result payload.
94
+ const matchedByName = useMemo(() => {
95
+ if (!isPendingFromResult && !isExecuting)
96
+ return null;
97
+ return (approvals.find(a => normalizeName(a.tool_name) === normalizedToolName) ??
98
+ null);
99
+ }, [approvals, normalizedToolName, isPendingFromResult, isExecuting]);
100
+ const matchedApproval = matchedByResultId ?? matchedBySkill ?? matchedByName;
101
+ // Prefer the store-matched approval id, fall back to the id from the tool
102
+ // result (SSE path). This ensures the approve/deny buttons work even when
103
+ // the monitoring WS is not connected.
104
+ const effectiveApprovalId = matchedApproval?.id ?? resultApprovalId ?? null;
105
+ // Reflect approval decisions that happened outside this card (e.g. sidebar).
106
+ const externalDecision = matchedApproval?.status === 'approved'
107
+ ? 'approved'
108
+ : matchedApproval?.status === 'rejected'
109
+ ? 'denied'
110
+ : undefined;
111
+ const makeDecision = useCallback((action) => {
112
+ if (!effectiveApprovalId)
48
113
  return;
49
- let cancelled = false;
50
- const poll = async () => {
51
- try {
52
- const headers = {
53
- 'Content-Type': 'application/json',
54
- };
55
- if (approvalConfig.authToken) {
56
- headers['Authorization'] = `Bearer ${approvalConfig.authToken}`;
57
- }
58
- const res = await fetch(`${approvalConfig.apiBaseUrl}/tool-approvals?status=pending`, { headers });
59
- if (!res.ok || cancelled)
60
- return;
61
- const data = await res.json();
62
- const approvals = Array.isArray(data)
63
- ? data
64
- : (data.approvals ??
65
- data.requests ??
66
- []);
67
- // Match by normalized tool name
68
- const match = approvals.find(a => {
69
- const aNormalized = String(a.tool_name || '')
70
- .replace(/:[0-9]+\.[0-9]+\.[0-9]+.*$/, '')
71
- .replace(/[-_]/g, '')
72
- .toLowerCase();
73
- return aNormalized === normalizedToolName && a.status === 'pending';
74
- });
75
- if (match && !cancelled) {
76
- setMatchedApprovalId(match.id);
77
- }
78
- }
79
- catch {
80
- // Retry on next interval
81
- }
82
- };
83
- poll();
84
- const interval = setInterval(poll, 3000);
85
- return () => {
86
- cancelled = true;
87
- clearInterval(interval);
88
- };
89
- }, [
90
- shouldPoll,
91
- approvalConfig?.apiBaseUrl,
92
- approvalConfig?.authToken,
93
- normalizedToolName,
94
- ]);
95
- const makeDecision = useCallback(async (action) => {
96
- if (!matchedApprovalId || !approvalConfig?.apiBaseUrl)
97
- return;
98
- setLoading(true);
99
- try {
100
- const headers = {
101
- 'Content-Type': 'application/json',
102
- };
103
- if (approvalConfig.authToken) {
104
- headers['Authorization'] = `Bearer ${approvalConfig.authToken}`;
105
- }
106
- const res = await fetch(`${approvalConfig.apiBaseUrl}/tool-approvals/${matchedApprovalId}/${action}`, {
107
- method: 'POST',
108
- headers,
109
- body: JSON.stringify({}),
110
- });
111
- if (res.ok) {
112
- setDecision(action === 'approve' ? 'approved' : 'denied');
113
- }
114
- }
115
- catch {
116
- // Allow retry
117
- }
118
- finally {
119
- setLoading(false);
120
- }
121
- }, [matchedApprovalId, approvalConfig?.apiBaseUrl, approvalConfig?.authToken]);
114
+ const approved = action === 'approve';
115
+ // Use ONLY the adapter continuation path (SSE/POST). Sending both WS and
116
+ // continuation decisions can trigger duplicate approval cycles.
117
+ setDecision(approved ? 'approved' : 'denied');
118
+ onRespond({
119
+ type: 'tool-approval-decision',
120
+ approved,
121
+ approvalId: effectiveApprovalId,
122
+ toolName: item.toolName,
123
+ });
124
+ }, [effectiveApprovalId, onRespond, item.toolName]);
122
125
  // Show approval UI when we have a confirmed pending approval (from result
123
- // or discovered via polling) or after a decision has been made.
124
- const hasApproval = isPendingFromResult || !!matchedApprovalId;
125
- const approvalState = decision || (hasApproval ? 'pending' : undefined);
126
- return (_jsx(ToolCallDisplay, { toolCallId: item.toolCallId, toolName: item.toolName, args: item.args, result: item.result, status: item.status, error: item.error, executionError: item.executionError, codeError: item.codeError, exitCode: item.exitCode, approvalRequired: hasApproval, approvalState: approvalState, onApprove: matchedApprovalId && !decision
127
- ? () => void makeDecision('approve')
128
- : undefined, onDeny: matchedApprovalId && !decision
129
- ? () => void makeDecision('reject')
130
- : undefined, approvalLoading: loading }));
126
+ // or discovered via the store) or after a decision has been made.
127
+ const hasApproval = isPendingFromResult || !!effectiveApprovalId || !!externalDecision;
128
+ const approvalState = decision || externalDecision || (hasApproval ? 'pending' : undefined);
129
+ const approvalDecisionSource = decision
130
+ ? 'inline'
131
+ : externalDecision
132
+ ? 'external'
133
+ : undefined;
134
+ return (_jsx(ToolCallDisplay, { toolCallId: item.toolCallId, toolName: item.toolName, args: item.args, result: item.result, status: item.status, error: item.error, executionError: item.executionError, codeError: item.codeError, exitCode: item.exitCode, approvalRequired: hasApproval, approvalState: approvalState, approvalDecisionSource: approvalDecisionSource, onApprove: effectiveApprovalId && !decision && !externalDecision
135
+ ? () => makeDecision('approve')
136
+ : undefined, onDeny: effectiveApprovalId && !decision && !externalDecision
137
+ ? () => makeDecision('reject')
138
+ : undefined, approvalLoading: false }));
131
139
  }
132
140
  // ---------------------------------------------------------------------------
133
141
  // Component
@@ -170,7 +178,7 @@ export function ChatMessageList({ displayItems, isLoading, isStreaming, showLoad
170
178
  return (_jsxs(_Fragment, { children: [displayItems.map((item, index) => {
171
179
  // ---- Tool call item ----
172
180
  if (isToolCallMessage(item)) {
173
- const respond = item.status === 'executing'
181
+ const respond = item.status === 'executing' || item.status === 'inProgress'
174
182
  ? createRespondCallback(item.toolCallId)
175
183
  : undefined;
176
184
  const toolUI = renderToolResult ? (renderToolResult({
@@ -182,7 +190,7 @@ export function ChatMessageList({ displayItems, isLoading, isStreaming, showLoad
182
190
  status: item.status,
183
191
  error: item.error,
184
192
  respond,
185
- })) : (_jsx(DefaultToolCallRenderer, { item: item, approvalConfig: approvalConfig }));
193
+ })) : (_jsx(DefaultToolCallRenderer, { item: item, approvalConfig: approvalConfig, onRespond: createRespondCallback(item.toolCallId) }));
186
194
  if (toolUI === null || toolUI === undefined)
187
195
  return null;
188
196
  return (_jsx(Box, { sx: {