@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
@@ -2,45 +2,100 @@
2
2
  * Copyright (c) 2025-2026 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import { useContext } from 'react';
6
- import { useQuery, QueryClientContext } from '@tanstack/react-query';
5
+ import { useCallback, useMemo } from 'react';
6
+ import { agentRuntimeStore, useAgentRuntimeCodemodeStatus } from '../stores';
7
+ function parseSkillStatus(value) {
8
+ if (value === 'available' || value === 'enabled' || value === 'loaded') {
9
+ return value;
10
+ }
11
+ return 'available';
12
+ }
13
+ // ---------------------------------------------------------------------------
14
+ // Skills from WS snapshot
15
+ // ---------------------------------------------------------------------------
7
16
  /**
8
- * Hook to fetch available skills from backend.
17
+ * Derive the list of skills from the WS-pushed `codemodeStatus`.
18
+ *
19
+ * The server-side SkillsArea pushes per-skill status (`available`,
20
+ * `enabled`, `loaded`) via the monitoring WebSocket inside the
21
+ * `codemodeStatus.skills` array. This hook reads from the Zustand
22
+ * store — no REST call is made.
9
23
  */
10
- export function useSkills(enabled, baseEndpoint, authToken) {
11
- const queryClient = useContext(QueryClientContext);
12
- if (!queryClient) {
13
- return {
14
- data: undefined,
15
- isLoading: false,
16
- isError: false,
17
- error: null,
18
- refetch: () => Promise.resolve({ data: undefined }),
19
- };
20
- }
21
- // eslint-disable-next-line react-hooks/rules-of-hooks
22
- return useQuery({
23
- queryFn: async () => {
24
- if (!baseEndpoint) {
25
- return { skills: [], total: 0 };
26
- }
27
- // Derive skills endpoint from config endpoint.
28
- const skillsEndpoint = baseEndpoint.replace('/configure', '/skills');
29
- const headers = {
30
- 'Content-Type': 'application/json',
31
- };
32
- if (authToken) {
33
- headers['Authorization'] = `Bearer ${authToken}`;
34
- }
35
- const response = await fetch(skillsEndpoint, { headers });
36
- if (!response.ok) {
37
- throw new Error(`Skills fetch failed: ${response.statusText}`);
38
- }
39
- return response.json();
40
- },
41
- queryKey: ['skills', baseEndpoint || 'jupyter'],
42
- enabled,
43
- staleTime: 5 * 60 * 1000,
44
- retry: 1,
45
- });
24
+ export function useSkills(_enabled, _baseEndpoint, _authToken) {
25
+ const codemodeStatus = useAgentRuntimeCodemodeStatus();
26
+ const data = useMemo(() => {
27
+ if (!codemodeStatus) {
28
+ return undefined;
29
+ }
30
+ const skills = (codemodeStatus.skills ?? []).map(s => ({
31
+ id: s.id ?? s.name,
32
+ name: s.name,
33
+ description: s.description,
34
+ tags: s.tags,
35
+ has_scripts: s.has_scripts,
36
+ has_resources: s.has_resources,
37
+ status: parseSkillStatus(s.status),
38
+ approved: s.approved !== false,
39
+ skill_definition: s.skill_definition ?? null,
40
+ source_variant: s.source_variant,
41
+ module: s.module,
42
+ package: s.package,
43
+ method: s.method,
44
+ path: s.path,
45
+ }));
46
+ return { skills, total: skills.length };
47
+ }, [codemodeStatus]);
48
+ return {
49
+ data,
50
+ isLoading: false,
51
+ isError: false,
52
+ error: null,
53
+ refetch: () => Promise.resolve({ data }),
54
+ };
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Skill enable / disable via WebSocket
58
+ // ---------------------------------------------------------------------------
59
+ export function useSkillActions(agentId) {
60
+ const enableSkill = useCallback((skillId) => {
61
+ const ok = agentRuntimeStore
62
+ .getState()
63
+ .sendRawMessage({ type: 'skill_enable', skillId }, agentId);
64
+ if (!ok) {
65
+ console.warn('[useSkillActions] skill_enable dropped: websocket not ready');
66
+ }
67
+ return ok;
68
+ }, [agentId]);
69
+ const disableSkill = useCallback((skillId) => {
70
+ const ok = agentRuntimeStore
71
+ .getState()
72
+ .sendRawMessage({ type: 'skill_disable', skillId }, agentId);
73
+ if (!ok) {
74
+ console.warn('[useSkillActions] skill_disable dropped: websocket not ready');
75
+ }
76
+ return ok;
77
+ }, [agentId]);
78
+ const approveSkill = useCallback((skillId) => {
79
+ const ok = agentRuntimeStore
80
+ .getState()
81
+ .sendRawMessage({ type: 'skill_approve', skillId }, agentId);
82
+ if (!ok) {
83
+ console.warn('[useSkillActions] skill_approve dropped: websocket not ready');
84
+ }
85
+ return ok;
86
+ }, [agentId]);
87
+ const unapproveSkill = useCallback((skillId) => {
88
+ const ok = agentRuntimeStore
89
+ .getState()
90
+ .sendRawMessage({ type: 'skill_unapprove', skillId }, agentId);
91
+ if (!ok) {
92
+ console.warn('[useSkillActions] skill_unapprove dropped: websocket not ready');
93
+ }
94
+ return ok;
95
+ }, [agentId]);
96
+ return { enableSkill, disableSkill, approveSkill, unapproveSkill };
46
97
  }
98
+ // ---------------------------------------------------------------------------
99
+ // Loaded skills (kept for backward compat with AgentSkillsExample sidebar)
100
+ // ---------------------------------------------------------------------------
101
+ export { useAgentRuntimeLoadedSkills } from '../stores';
@@ -1,45 +1,69 @@
1
1
  import type { ToolApprovalFilters } from '../types/tool-approvals';
2
- export declare function useToolApprovalsQuery(filters?: ToolApprovalFilters): import("@tanstack/react-query").UseQueryResult<import("../types").ToolApproval[], Error>;
2
+ /**
3
+ * Normalised approval record. Both snake_case (server-native) and
4
+ * camelCase (TypeScript-idiomatic) keys are present so existing UI
5
+ * consumers keep working regardless of which naming they read.
6
+ */
7
+ export type ApprovalRecord = {
8
+ id: string;
9
+ agent_id: string;
10
+ agentId: string;
11
+ pod_name: string;
12
+ podName: string;
13
+ tool_name: string;
14
+ toolName: string;
15
+ tool_call_id?: string;
16
+ toolCallId?: string;
17
+ tool_args: Record<string, unknown>;
18
+ toolArgs: Record<string, unknown>;
19
+ status: string;
20
+ note?: string | null;
21
+ created_at: string;
22
+ createdAt: string;
23
+ updated_at?: string;
24
+ updatedAt?: string;
25
+ read?: boolean;
26
+ };
27
+ interface ApprovalsQueryData {
28
+ approvals: ApprovalRecord[];
29
+ total: number;
30
+ }
31
+ interface MutationResult {
32
+ isPending: boolean;
33
+ mutate: (vars: {
34
+ id: string;
35
+ note?: string;
36
+ }) => void;
37
+ mutateAsync: (vars: {
38
+ id: string;
39
+ note?: string;
40
+ }) => Promise<void>;
41
+ }
42
+ export declare function useToolApprovalsQuery(filters?: ToolApprovalFilters): import("@tanstack/react-query").UseQueryResult<ApprovalsQueryData, Error>;
3
43
  export declare function usePendingApprovalCount(): import("@tanstack/react-query").UseQueryResult<{
4
44
  count: number;
5
45
  }, Error>;
6
- export declare function useApproveToolRequest(): import("@tanstack/react-query").UseMutationResult<void, Error, {
7
- id: string;
8
- note?: string;
9
- }, unknown>;
10
- export declare function useRejectToolRequest(): import("@tanstack/react-query").UseMutationResult<void, Error, {
11
- id: string;
12
- note?: string;
13
- }, unknown>;
14
- export declare function useMarkToolApprovalRead(): import("@tanstack/react-query").UseMutationResult<void, Error, {
15
- id: string;
16
- }, unknown>;
17
- export declare function useMarkToolApprovalUnread(): import("@tanstack/react-query").UseMutationResult<void, Error, {
18
- id: string;
19
- }, unknown>;
20
- export declare function useDeleteToolApproval(): import("@tanstack/react-query").UseMutationResult<void, Error, {
21
- id: string;
22
- }, unknown>;
46
+ export declare function useApproveToolRequest(): MutationResult;
47
+ export declare function useRejectToolRequest(): MutationResult;
48
+ export declare function useMarkToolApprovalRead(): MutationResult;
49
+ export declare function useMarkToolApprovalUnread(): MutationResult;
50
+ /**
51
+ * Delete a tool approval.
52
+ *
53
+ * Sends a ``tool_approval_delete`` message over the shared websocket.
54
+ * The local cache is updated only after the server emits
55
+ * ``tool_approval_deleted``.
56
+ */
57
+ export declare function useDeleteToolApproval(): MutationResult;
23
58
  export declare function useToolApprovals(filters?: ToolApprovalFilters): {
24
- approvalsQuery: import("@tanstack/react-query").UseQueryResult<import("../types").ToolApproval[], Error>;
59
+ approvalsQuery: import("@tanstack/react-query").UseQueryResult<ApprovalsQueryData, Error>;
25
60
  pendingCountQuery: import("@tanstack/react-query").UseQueryResult<{
26
61
  count: number;
27
62
  }, Error>;
28
- approve: import("@tanstack/react-query").UseMutationResult<void, Error, {
29
- id: string;
30
- note?: string;
31
- }, unknown>;
32
- reject: import("@tanstack/react-query").UseMutationResult<void, Error, {
33
- id: string;
34
- note?: string;
35
- }, unknown>;
36
- markRead: import("@tanstack/react-query").UseMutationResult<void, Error, {
37
- id: string;
38
- }, unknown>;
39
- markUnread: import("@tanstack/react-query").UseMutationResult<void, Error, {
40
- id: string;
41
- }, unknown>;
42
- remove: import("@tanstack/react-query").UseMutationResult<void, Error, {
43
- id: string;
44
- }, unknown>;
63
+ approve: MutationResult;
64
+ reject: MutationResult;
65
+ markRead: MutationResult;
66
+ markUnread: MutationResult;
67
+ remove: MutationResult;
45
68
  };
69
+ export {};
@@ -3,99 +3,348 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  /**
6
- * Agent tool approval hooks.
6
+ * Agent tool approval hooks (WebSocket-only).
7
+ *
8
+ * All tool-approval interactions flow over the AI Agents websocket stream.
9
+ * There are no REST endpoints — the server publishes approval snapshots and
10
+ * `tool_approval_*` events, and the client sends `tool_approval_decision`
11
+ * messages to approve/reject pending requests.
12
+ *
13
+ * Consumers use the familiar React Query-style API below. Internally the
14
+ * hooks:
15
+ * - subscribe to the shared AI Agents WS once per component tree
16
+ * - seed/update the `['tool-approvals', filters]` query cache from events
17
+ * - send decision messages over the same socket for mutations
7
18
  *
8
19
  * @module hooks/useToolApprovals
9
20
  */
10
- import { useMemo } from 'react';
11
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
12
- import { useCoreStore, useIAMStore } from '@datalayer/core/lib/state';
13
- import { DEFAULT_SERVICE_URLS } from '@datalayer/core/lib/api/constants';
14
- import { toolApprovals } from '../api';
15
- // ─── Auth helpers ────────────────────────────────────────────────────
16
- function useDashboardAuthToken() {
17
- const token = useIAMStore((s) => s.token);
18
- return token ?? '';
19
- }
20
- function useDashboardBaseUrl() {
21
- const config = useCoreStore((s) => s.configuration);
22
- return config?.aiagentsRunUrl ?? DEFAULT_SERVICE_URLS.AI_AGENTS;
21
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
23
+ import { useAIAgentsWebSocket } from './useAIAgentsWebSocket';
24
+ // ─── Helpers ─────────────────────────────────────────────────────────
25
+ const APPROVALS_ROOT_KEY = ['tool-approvals'];
26
+ function str(value) {
27
+ return typeof value === 'string' ? value : value == null ? '' : String(value);
28
+ }
29
+ function normalizeApproval(raw) {
30
+ if (!raw || typeof raw !== 'object')
31
+ return null;
32
+ const rec = raw;
33
+ const id = typeof rec.id === 'string' ? rec.id : undefined;
34
+ if (!id)
35
+ return null;
36
+ const agent = str(rec.agent_id ?? rec.agentId);
37
+ const pod = str(rec.pod_name ?? rec.podName);
38
+ const tool = str(rec.tool_name ?? rec.toolName ?? 'unknown');
39
+ const toolCall = rec.tool_call_id ?? rec.toolCallId;
40
+ const args = rec.tool_args ??
41
+ rec.toolArgs ??
42
+ {};
43
+ const created = str(rec.created_at ?? rec.createdAt);
44
+ const updated = str(rec.updated_at ?? rec.updatedAt);
45
+ return {
46
+ id,
47
+ agent_id: agent,
48
+ agentId: agent,
49
+ pod_name: pod,
50
+ podName: pod,
51
+ tool_name: tool,
52
+ toolName: tool,
53
+ tool_call_id: typeof toolCall === 'string' ? toolCall : undefined,
54
+ toolCallId: typeof toolCall === 'string' ? toolCall : undefined,
55
+ tool_args: args,
56
+ toolArgs: args,
57
+ status: str(rec.status ?? 'pending'),
58
+ note: typeof rec.note === 'string'
59
+ ? rec.note
60
+ : rec.note === null
61
+ ? null
62
+ : undefined,
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
+ function matchesFilter(a, filters) {
71
+ if (!filters)
72
+ return true;
73
+ if (filters.agentId && a.agent_id !== filters.agentId)
74
+ return false;
75
+ if (filters.status && a.status !== filters.status)
76
+ return false;
77
+ if (filters.toolName && a.tool_name !== filters.toolName)
78
+ return false;
79
+ return true;
80
+ }
81
+ function upsertApproval(list, approval) {
82
+ const idx = list.findIndex(item => item.id === approval.id);
83
+ if (idx === -1)
84
+ return [approval, ...list];
85
+ const copy = list.slice();
86
+ copy[idx] = { ...copy[idx], ...approval };
87
+ return copy;
88
+ }
89
+ function removeApproval(list, id) {
90
+ return list.filter(item => item.id !== id);
91
+ }
92
+ function writeSnapshot(queryClient, approvals) {
93
+ const queries = queryClient
94
+ .getQueryCache()
95
+ .findAll({ queryKey: APPROVALS_ROOT_KEY });
96
+ for (const q of queries) {
97
+ const [, second] = q.queryKey;
98
+ if (second === 'pending-count') {
99
+ queryClient.setQueryData(q.queryKey, {
100
+ count: approvals.filter(a => a.status === 'pending').length,
101
+ });
102
+ continue;
103
+ }
104
+ const filters = (second ?? undefined);
105
+ const filtered = approvals.filter(a => matchesFilter(a, filters));
106
+ queryClient.setQueryData(q.queryKey, {
107
+ approvals: filtered,
108
+ total: filtered.length,
109
+ });
110
+ }
111
+ // Ensure there's always a root cache entry so downstream consumers can
112
+ // compute derived values (like pending-count refetches) even when no
113
+ // filter query has been mounted yet.
114
+ queryClient.setQueryData(APPROVALS_ROOT_KEY, {
115
+ approvals,
116
+ total: approvals.length,
117
+ });
118
+ }
119
+ function patchApproval(queryClient, approval, mode) {
120
+ const queries = queryClient
121
+ .getQueryCache()
122
+ .findAll({ queryKey: APPROVALS_ROOT_KEY });
123
+ for (const q of queries) {
124
+ const [, second] = q.queryKey;
125
+ if (second === 'pending-count') {
126
+ // Recompute from the root cache after we finish patching below.
127
+ continue;
128
+ }
129
+ const filters = (second ?? undefined);
130
+ const current = queryClient.getQueryData(q.queryKey)?.approvals ?? [];
131
+ let next;
132
+ if (mode === 'remove') {
133
+ next = removeApproval(current, approval.id);
134
+ }
135
+ else {
136
+ next = matchesFilter(approval, filters)
137
+ ? upsertApproval(current, approval)
138
+ : removeApproval(current, approval.id);
139
+ }
140
+ queryClient.setQueryData(q.queryKey, {
141
+ approvals: next,
142
+ total: next.length,
143
+ });
144
+ }
145
+ // Recompute pending-count from the unfiltered root cache.
146
+ const root = queryClient.getQueryData(APPROVALS_ROOT_KEY)?.approvals;
147
+ if (root) {
148
+ queryClient.setQueryData(['tool-approvals', 'pending-count'], { count: root.filter(a => a.status === 'pending').length });
149
+ }
150
+ }
151
+ // ─── Shared WS bridge ─────────────────────────────────────────────────
152
+ /**
153
+ * Opens a WS connection and streams snapshots + events into the React
154
+ * Query cache. Returns a `send` function so sibling hooks can dispatch
155
+ * decisions over the same socket.
156
+ */
157
+ function useApprovalsSocket() {
158
+ const queryClient = useQueryClient();
159
+ const { connectionState, send } = useAIAgentsWebSocket({
160
+ onMessage: msg => {
161
+ const type = (msg.type ?? msg.event);
162
+ if (!type)
163
+ return;
164
+ // Response to our { type: 'tool-approvals-history' } request.
165
+ // Shape: { type: "tool-approvals-history", data: { approvals: [...] } }
166
+ if (type === 'tool-approvals-history') {
167
+ const data = msg.data;
168
+ const rawList = Array.isArray(data?.approvals) ? data.approvals : [];
169
+ const list = rawList
170
+ .map(normalizeApproval)
171
+ .filter((a) => a !== null)
172
+ .filter(a => a.status !== 'deleted');
173
+ writeSnapshot(queryClient, list);
174
+ return;
175
+ }
176
+ // Incremental broadcast events from datalayer-ai-agents.
177
+ // Shape: { channel: "user:<uid>", event: "tool_approval_*", data: record }
178
+ if (type.startsWith('tool_approval_')) {
179
+ const rawPayload = msg.payload ??
180
+ msg.data ??
181
+ undefined;
182
+ const approval = normalizeApproval(rawPayload);
183
+ if (!approval)
184
+ return;
185
+ patchApproval(queryClient, approval, type === 'tool_approval_deleted' ? 'remove' : 'upsert');
186
+ }
187
+ },
188
+ });
189
+ // Request the full approval history once connected so the sidebar badge
190
+ // and any pending-count consumers always show the correct count.
191
+ const historyAskedRef = useRef(false);
192
+ useEffect(() => {
193
+ if (connectionState !== 'connected') {
194
+ historyAskedRef.current = false;
195
+ return;
196
+ }
197
+ if (historyAskedRef.current)
198
+ return;
199
+ historyAskedRef.current = send({ type: 'tool-approvals-history' });
200
+ }, [connectionState, send]);
201
+ return { send, connectionState };
23
202
  }
24
203
  // ─── Base hooks ──────────────────────────────────────────────────────
25
204
  export function useToolApprovalsQuery(filters) {
26
- const token = useDashboardAuthToken();
27
- const baseUrl = useDashboardBaseUrl();
205
+ useApprovalsSocket();
206
+ const queryClient = useQueryClient();
207
+ const queryKey = useMemo(() => ['tool-approvals', filters], [filters]);
28
208
  return useQuery({
29
- queryKey: ['tool-approvals', filters],
30
- queryFn: () => toolApprovals.getToolApprovals(token, filters, baseUrl),
31
- enabled: !!token,
32
- staleTime: 10_000,
209
+ queryKey,
210
+ // Data is populated via the WS bridge. On invalidation, return the
211
+ // cached value so the fresh WS snapshot is preserved.
212
+ queryFn: async () => queryClient.getQueryData(queryKey) ?? {
213
+ approvals: [],
214
+ total: 0,
215
+ },
216
+ staleTime: Number.POSITIVE_INFINITY,
217
+ refetchOnMount: false,
218
+ refetchOnWindowFocus: false,
219
+ refetchOnReconnect: false,
33
220
  });
34
221
  }
35
222
  export function usePendingApprovalCount() {
36
- const token = useDashboardAuthToken();
37
- const baseUrl = useDashboardBaseUrl();
223
+ useApprovalsSocket();
224
+ const queryClient = useQueryClient();
38
225
  return useQuery({
39
226
  queryKey: ['tool-approvals', 'pending-count'],
40
- queryFn: () => toolApprovals.getPendingApprovalCount(token, baseUrl),
41
- enabled: !!token,
42
- staleTime: 5_000,
227
+ queryFn: async () => {
228
+ const cached = queryClient.getQueryData([
229
+ 'tool-approvals',
230
+ 'pending-count',
231
+ ]);
232
+ if (cached && typeof cached.count === 'number')
233
+ return cached;
234
+ const root = queryClient.getQueryData(APPROVALS_ROOT_KEY);
235
+ const pending = (root?.approvals ?? []).filter(a => a.status === 'pending').length;
236
+ return { count: pending };
237
+ },
238
+ staleTime: Number.POSITIVE_INFINITY,
239
+ refetchOnMount: false,
240
+ refetchOnWindowFocus: false,
241
+ refetchOnReconnect: false,
43
242
  });
44
243
  }
244
+ /** Build a mutation-style object that sends a WS decision. */
245
+ function useDecisionMutation(approved) {
246
+ const { send } = useApprovalsSocket();
247
+ const [isPending, setIsPending] = useState(false);
248
+ const mutateAsync = useCallback(async ({ id, note }) => {
249
+ setIsPending(true);
250
+ try {
251
+ const ok = send({
252
+ type: 'tool_approval_decision',
253
+ approvalId: id,
254
+ approved,
255
+ ...(note ? { note } : {}),
256
+ });
257
+ if (!ok) {
258
+ throw new Error('Approvals WebSocket is not connected; decision was not sent');
259
+ }
260
+ }
261
+ finally {
262
+ setIsPending(false);
263
+ }
264
+ }, [approved, send]);
265
+ const mutate = useCallback((vars) => {
266
+ void mutateAsync(vars);
267
+ }, [mutateAsync]);
268
+ return { isPending, mutate, mutateAsync };
269
+ }
45
270
  export function useApproveToolRequest() {
46
- const token = useDashboardAuthToken();
47
- const baseUrl = useDashboardBaseUrl();
48
- const queryClient = useQueryClient();
49
- return useMutation({
50
- mutationFn: ({ id, note }) => toolApprovals.approveToolRequest(token, id, note, baseUrl),
51
- onSuccess: () => {
52
- queryClient.invalidateQueries({ queryKey: ['tool-approvals'] });
53
- },
54
- });
271
+ return useDecisionMutation(true);
55
272
  }
56
273
  export function useRejectToolRequest() {
57
- const token = useDashboardAuthToken();
58
- const baseUrl = useDashboardBaseUrl();
274
+ return useDecisionMutation(false);
275
+ }
276
+ /**
277
+ * Mark a tool approval as read/unread in the local cache.
278
+ *
279
+ * Read-state isn't part of the websocket contract, so this hook patches
280
+ * only the React Query cache. The patch survives until the next snapshot.
281
+ */
282
+ function useLocalReadMutation(target) {
59
283
  const queryClient = useQueryClient();
60
- return useMutation({
61
- mutationFn: ({ id, note }) => toolApprovals.rejectToolRequest(token, id, note, baseUrl),
62
- onSuccess: () => {
63
- queryClient.invalidateQueries({ queryKey: ['tool-approvals'] });
64
- },
65
- });
284
+ const [isPending, setIsPending] = useState(false);
285
+ const mutateAsync = useCallback(async ({ id }) => {
286
+ setIsPending(true);
287
+ try {
288
+ const queries = queryClient
289
+ .getQueryCache()
290
+ .findAll({ queryKey: APPROVALS_ROOT_KEY });
291
+ for (const q of queries) {
292
+ const [, second] = q.queryKey;
293
+ if (second === 'pending-count')
294
+ continue;
295
+ const current = queryClient.getQueryData(q.queryKey);
296
+ if (!current?.approvals)
297
+ continue;
298
+ queryClient.setQueryData(q.queryKey, {
299
+ ...current,
300
+ approvals: current.approvals.map(a => a.id === id ? { ...a, read: target } : a),
301
+ });
302
+ }
303
+ }
304
+ finally {
305
+ setIsPending(false);
306
+ }
307
+ }, [queryClient, target]);
308
+ const mutate = useCallback((vars) => {
309
+ void mutateAsync(vars);
310
+ }, [mutateAsync]);
311
+ return { isPending, mutate, mutateAsync };
66
312
  }
67
313
  export function useMarkToolApprovalRead() {
68
- const token = useDashboardAuthToken();
69
- const baseUrl = useDashboardBaseUrl();
70
- const queryClient = useQueryClient();
71
- return useMutation({
72
- mutationFn: ({ id }) => toolApprovals.markToolApprovalRead(token, id, baseUrl),
73
- onSuccess: () => {
74
- queryClient.invalidateQueries({ queryKey: ['tool-approvals'] });
75
- },
76
- });
314
+ return useLocalReadMutation(true);
77
315
  }
78
316
  export function useMarkToolApprovalUnread() {
79
- const token = useDashboardAuthToken();
80
- const baseUrl = useDashboardBaseUrl();
81
- const queryClient = useQueryClient();
82
- return useMutation({
83
- mutationFn: ({ id }) => toolApprovals.markToolApprovalUnread(token, id, baseUrl),
84
- onSuccess: () => {
85
- queryClient.invalidateQueries({ queryKey: ['tool-approvals'] });
86
- },
87
- });
317
+ return useLocalReadMutation(false);
88
318
  }
319
+ /**
320
+ * Delete a tool approval.
321
+ *
322
+ * Sends a ``tool_approval_delete`` message over the shared websocket.
323
+ * The local cache is updated only after the server emits
324
+ * ``tool_approval_deleted``.
325
+ */
89
326
  export function useDeleteToolApproval() {
90
- const token = useDashboardAuthToken();
91
- const baseUrl = useDashboardBaseUrl();
92
- const queryClient = useQueryClient();
93
- return useMutation({
94
- mutationFn: ({ id }) => toolApprovals.deleteToolApproval(token, id, baseUrl),
95
- onSuccess: () => {
96
- queryClient.invalidateQueries({ queryKey: ['tool-approvals'] });
97
- },
98
- });
327
+ const { send } = useApprovalsSocket();
328
+ const [isPending, setIsPending] = useState(false);
329
+ const mutateAsync = useCallback(async ({ id }) => {
330
+ setIsPending(true);
331
+ try {
332
+ const ok = send({
333
+ type: 'tool_approval_delete',
334
+ approvalId: id,
335
+ });
336
+ if (!ok) {
337
+ throw new Error('Approvals WebSocket is not connected; delete was not sent');
338
+ }
339
+ }
340
+ finally {
341
+ setIsPending(false);
342
+ }
343
+ }, [send]);
344
+ const mutate = useCallback((vars) => {
345
+ void mutateAsync(vars);
346
+ }, [mutateAsync]);
347
+ return { isPending, mutate, mutateAsync };
99
348
  }
100
349
  // ─── Composite hook ──────────────────────────────────────────────────
101
350
  export function useToolApprovals(filters) {
@@ -6,7 +6,7 @@ export interface Message {
6
6
  }
7
7
  export interface UseVercelAIOptions {
8
8
  /**
9
- * Base URL for the Vercel AI endpoint (e.g., 'http://localhost:8000')
9
+ * Base URL for the Vercel AI endpoint (e.g., `http://localhost:8000`).
10
10
  */
11
11
  baseUrl?: string;
12
12
  /**