@datalayer/agent-runtimes 1.0.19 → 1.0.20

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 (141) hide show
  1. package/README.md +11 -3
  2. package/lib/AgentNode.js +141 -40
  3. package/lib/App.js +2 -2
  4. package/lib/agent-node/AgentNodeGallery.js +188 -25
  5. package/lib/api/runtimes/runtimes.d.ts +57 -0
  6. package/lib/api/runtimes/runtimes.js +55 -1
  7. package/lib/chat/Chat.d.ts +1 -1
  8. package/lib/chat/Chat.js +2 -2
  9. package/lib/chat/base/ChatBase.js +50 -13
  10. package/lib/chat/document/EphemeralDocument.d.ts +5 -1
  11. package/lib/chat/document/EphemeralDocument.js +7 -5
  12. package/lib/chat/header/ChatHeaderBase.d.ts +3 -1
  13. package/lib/chat/header/ChatHeaderBase.js +5 -3
  14. package/lib/chat/messages/ChatMessageList.d.ts +19 -0
  15. package/lib/chat/messages/ChatMessageList.js +180 -9
  16. package/lib/chat/notebook/EphemeralNotebook.d.ts +5 -1
  17. package/lib/chat/notebook/EphemeralNotebook.js +9 -6
  18. package/lib/chat/prompt/InputPrompt.js +1 -1
  19. package/lib/chat/tools/SubagentActivity.d.ts +20 -0
  20. package/lib/chat/tools/SubagentActivity.js +154 -0
  21. package/lib/chat/tools/index.d.ts +1 -0
  22. package/lib/chat/tools/index.js +1 -0
  23. package/lib/chat/usage/TokenUsageBar.js +3 -3
  24. package/lib/client/AgentRuntimesClient.d.ts +5 -0
  25. package/lib/client/AgentsMixin.js +3 -1
  26. package/lib/client/mixins/RuntimesMixin.d.ts +12 -0
  27. package/lib/client/mixins/RuntimesMixin.js +24 -1
  28. package/lib/components/AgentSummary.d.ts +1 -0
  29. package/lib/components/AgentSummary.js +66 -10
  30. package/lib/components/runtimes/RuntimePickerBase.js +64 -29
  31. package/lib/components/runtimes/RuntimePickerCell.js +1 -1
  32. package/lib/components/runtimes/RuntimePickerNotebook.js +1 -1
  33. package/lib/components/runtimes/RuntimeSimplePicker.js +3 -2
  34. package/lib/components/runtimes/RuntimeUtils.js +2 -2
  35. package/lib/context/GraphFlowChart.js +1 -1
  36. package/lib/context/TokenUsageChart.js +1 -1
  37. package/lib/context/TurnGraphChart.js +2 -2
  38. package/lib/examples/A2UiAgentExample.js +4 -4
  39. package/lib/examples/A2UiComponentsGalleryExample.js +3 -3
  40. package/lib/examples/A2UiContactCardExample.js +3 -3
  41. package/lib/examples/A2UiRestaurantExample.js +2 -2
  42. package/lib/examples/A2UiViewerExample.js +3 -3
  43. package/lib/examples/AgUiAgenticExample.js +3 -3
  44. package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
  45. package/lib/examples/AgUiHaikuGenUiExample.js +3 -3
  46. package/lib/examples/AgUiHumanInTheLoopExample.js +5 -5
  47. package/lib/examples/AgUiSharedStateExample.js +3 -3
  48. package/lib/examples/AgUiToolsBasedGenUiExample.js +7 -5
  49. package/lib/examples/AgentCheckpointsExample.js +6 -5
  50. package/lib/examples/{AgentSandboxExample.d.ts → AgentCodeSandboxExample.d.ts} +4 -4
  51. package/lib/examples/{AgentSandboxExample.js → AgentCodeSandboxExample.js} +10 -9
  52. package/lib/examples/AgentCodeSandboxesExample.js +2 -2
  53. package/lib/examples/AgentCodemodeExample.js +3 -3
  54. package/lib/examples/AgentCompactionExample.d.ts +12 -0
  55. package/lib/examples/AgentCompactionExample.js +244 -0
  56. package/lib/examples/AgentEvalsExample.js +10 -20
  57. package/lib/examples/AgentGuardrailsExample.js +2 -2
  58. package/lib/examples/AgentHooksExample.js +1 -1
  59. package/lib/examples/AgentInferenceProviderExample.js +6 -8
  60. package/lib/examples/AgentLoopExample.js +3 -3
  61. package/lib/examples/AgentMCPExample.js +3 -3
  62. package/lib/examples/AgentMemoryExample.d.ts +4 -2
  63. package/lib/examples/AgentMemoryExample.js +93 -10
  64. package/lib/examples/AgentMonitoringExample.js +2 -2
  65. package/lib/examples/AgentNotificationsExample.js +1 -1
  66. package/lib/examples/AgentOtelExample.d.ts +2 -3
  67. package/lib/examples/AgentOtelExample.js +7 -12
  68. package/lib/examples/AgentOutputsExample.js +5 -5
  69. package/lib/examples/AgentParametersExample.js +3 -3
  70. package/lib/examples/AgentSkillsExample.js +4 -4
  71. package/lib/examples/AgentSubagentsExample.d.ts +2 -2
  72. package/lib/examples/AgentSubagentsExample.js +43 -34
  73. package/lib/examples/AgentToolApprovalsExample.js +1 -1
  74. package/lib/examples/AgentTriggersExample.js +9 -11
  75. package/lib/examples/AgentspecsExample.js +13 -5
  76. package/lib/examples/CellExample.d.ts +6 -0
  77. package/lib/examples/CellExample.js +84 -0
  78. package/lib/examples/ChatCustomExample.js +1 -1
  79. package/lib/examples/ChatExample.js +1 -1
  80. package/lib/examples/ChatStandaloneExample.js +2 -2
  81. package/lib/examples/HomeExample.js +29 -28
  82. package/lib/examples/Lexical2Example.js +2 -2
  83. package/lib/examples/LexicalAgentExample.js +1 -1
  84. package/lib/examples/LexicalAgentSidebarExample.js +2 -2
  85. package/lib/examples/NotebookAgentSidebarExample.d.ts +1 -1
  86. package/lib/examples/NotebookAgentSidebarExample.js +5 -4
  87. package/lib/examples/NotebookCollaborationExample.js +3 -3
  88. package/lib/examples/NotebookExample.js +2 -2
  89. package/lib/examples/components/ExampleWrapper.js +5 -0
  90. package/lib/examples/components/Header.js +2 -2
  91. package/lib/examples/components/HeaderControls.js +1 -1
  92. package/lib/examples/components/LexicalEditor.js +1 -1
  93. package/lib/examples/components/MainContent.js +1 -1
  94. package/lib/examples/components/MockFileBrowser.js +8 -8
  95. package/lib/examples/components/TimeTravel.js +1 -1
  96. package/lib/examples/components/haiku/HaikuDisplay.js +2 -2
  97. package/lib/examples/example-selector.js +4 -3
  98. package/lib/examples/hooks/useExampleAgentRuntime.js +11 -1
  99. package/lib/examples/hooks/useExampleAgentRuntimes.d.ts +7 -0
  100. package/lib/examples/hooks/useExampleAgentRuntimes.js +82 -0
  101. package/lib/examples/index.d.ts +2 -1
  102. package/lib/examples/index.js +2 -1
  103. package/lib/examples/main.js +359 -46
  104. package/lib/examples/utils/agentSummaryStore.d.ts +17 -0
  105. package/lib/examples/utils/themedProvider.js +32 -2
  106. package/lib/examples/utils/useExampleAgentRuntimesUrl.js +2 -1
  107. package/lib/examples2/NotebookMutationsKernel.js +6 -3
  108. package/lib/examples2/NotebookMutationsServiceManager.js +6 -3
  109. package/lib/examples2/ReactRouterExample.js +5 -5
  110. package/lib/examples2/main.js +38 -0
  111. package/lib/hooks/useAIAgentsWebSocket.js +35 -0
  112. package/lib/hooks/useAgentRuntimes.d.ts +22 -0
  113. package/lib/hooks/useAgentRuntimes.js +192 -5
  114. package/lib/hooks/useCheckpoints.js +2 -2
  115. package/lib/hooks/useToolApprovals.d.ts +10 -4
  116. package/lib/hooks/useToolApprovals.js +11 -5
  117. package/lib/models/Runtime.d.ts +10 -8
  118. package/lib/models/RuntimeDTO.d.ts +5 -3
  119. package/lib/models/RuntimeDTO.js +3 -3
  120. package/lib/runtimes/actions.js +3 -1
  121. package/lib/runtimes/apis.d.ts +7 -0
  122. package/lib/specs/agents/agents.d.ts +51 -0
  123. package/lib/specs/agents/agents.js +3389 -94
  124. package/lib/specs/models.d.ts +2 -0
  125. package/lib/specs/models.js +37 -0
  126. package/lib/stores/agentRuntimeStore.d.ts +32 -1
  127. package/lib/stores/agentRuntimeStore.js +65 -1
  128. package/lib/stores/index.d.ts +1 -1
  129. package/lib/stores/index.js +1 -1
  130. package/lib/types/agents.d.ts +9 -3
  131. package/lib/types/agentspecs.d.ts +2 -0
  132. package/lib/types/chat.d.ts +12 -0
  133. package/lib/types/models.d.ts +5 -0
  134. package/lib/types/stream.d.ts +58 -1
  135. package/package.json +4 -4
  136. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
  137. package/scripts/codegen/generate_agents.py +7 -0
  138. package/scripts/codegen/generate_models.py +22 -9
  139. package/scripts/gen_workers.py +340 -0
  140. package/lib/examples/CellSimpleExample.d.ts +0 -6
  141. package/lib/examples/CellSimpleExample.js +0 -20
@@ -20,7 +20,8 @@ export function resolveExampleAgentRuntimesUrl(target, override) {
20
20
  if (override) {
21
21
  return normalizeBaseUrl(override) ?? override;
22
22
  }
23
- const localUrl = normalizeBaseUrl(import.meta.env.VITE_BASE_URL) ?? LOCAL_DEFAULT;
23
+ const localUrl = normalizeBaseUrl(import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL) ??
24
+ LOCAL_DEFAULT;
24
25
  const cloudUrl = normalizeBaseUrl(import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL) ??
25
26
  normalizeBaseUrl(import.meta.env.VITE_DATALAYER_RUNTIMES_URL) ??
26
27
  CLOUD_DEFAULT;
@@ -7,11 +7,13 @@ import { useState } from 'react';
7
7
  import { createRoot } from 'react-dom/client';
8
8
  import { Box, SegmentedControl, Label, Text } from '@primer/react';
9
9
  import { ServiceManager } from '@jupyterlab/services';
10
- import { createLiteServiceManager, createServerSettings, setJupyterServerUrl, getJupyterServerUrl, getJupyterServerToken, ServiceManagerLess, loadJupyterConfig, DEFAULT_JUPYTER_SERVER_URL, useNotebookStore, Notebook, SpinnerCentered, JupyterReactTheme, } from '@datalayer/jupyter-react';
10
+ import { createLiteServiceManager, createServerSettings, setJupyterServerUrl, getJupyterServerUrl, getJupyterServerToken, setJupyterServerToken, ServiceManagerLess, loadJupyterConfig, useNotebookStore, Notebook, SpinnerCentered, JupyterReactTheme, } from '@datalayer/jupyter-react';
11
11
  import { useCoreStore } from '../state';
12
12
  import { createDatalayerServiceManager } from '../services/DatalayerServiceManager';
13
13
  import nbformatExample from './notebooks/NotebookExample1.ipynb.json';
14
14
  const NOTEBOOK_ID = 'notebook-mutations-id';
15
+ const LOCAL_JUPYTER_SERVER_URL = 'http://0.0.0.0:8888/api/jupyter-server';
16
+ const LOCAL_JUPYTER_SERVER_TOKEN = '60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6';
15
17
  loadJupyterConfig();
16
18
  const SERVICE_MANAGER_LESS = new ServiceManagerLess();
17
19
  const NotebookMutationsKernel = () => {
@@ -36,6 +38,7 @@ const NotebookMutationsKernel = () => {
36
38
  };
37
39
  const changeIndex = (index) => {
38
40
  setIndex(index);
41
+ setJupyterServerToken(getJupyterServerToken() || LOCAL_JUPYTER_SERVER_TOKEN);
39
42
  switch (index) {
40
43
  case 0: {
41
44
  setNbformat(getCurrentNotebookContent());
@@ -45,7 +48,7 @@ const NotebookMutationsKernel = () => {
45
48
  break;
46
49
  }
47
50
  case 1: {
48
- setJupyterServerUrl(location.protocol + '//' + location.host);
51
+ setJupyterServerUrl(LOCAL_JUPYTER_SERVER_URL);
49
52
  createLiteServiceManager().then(liteServiceManager => {
50
53
  setServiceManager(liteServiceManager);
51
54
  setNbformat(getCurrentNotebookContent());
@@ -55,7 +58,7 @@ const NotebookMutationsKernel = () => {
55
58
  break;
56
59
  }
57
60
  case 2: {
58
- setJupyterServerUrl(DEFAULT_JUPYTER_SERVER_URL);
61
+ setJupyterServerUrl(LOCAL_JUPYTER_SERVER_URL);
59
62
  setNbformat(getCurrentNotebookContent());
60
63
  setReadonly(false);
61
64
  setLite(false);
@@ -7,11 +7,13 @@ import { useState } from 'react';
7
7
  import { createRoot } from 'react-dom/client';
8
8
  import { Box, SegmentedControl, Label, Text } from '@primer/react';
9
9
  import { ServiceManager } from '@jupyterlab/services';
10
- import { createLiteServiceManager, createServerSettings, setJupyterServerUrl, getJupyterServerUrl, getJupyterServerToken, ServiceManagerLess, loadJupyterConfig, DEFAULT_JUPYTER_SERVER_URL, useNotebookStore, Notebook, SpinnerCentered, JupyterReactTheme, } from '@datalayer/jupyter-react';
10
+ import { createLiteServiceManager, createServerSettings, setJupyterServerUrl, getJupyterServerUrl, getJupyterServerToken, setJupyterServerToken, ServiceManagerLess, loadJupyterConfig, useNotebookStore, Notebook, SpinnerCentered, JupyterReactTheme, } from '@datalayer/jupyter-react';
11
11
  import { useCoreStore } from '../state';
12
12
  import { createDatalayerServiceManager } from '../services/DatalayerServiceManager';
13
13
  import nbformatExample from './notebooks/NotebookExample1.ipynb.json';
14
14
  const NOTEBOOK_ID = 'notebook-mutations-id';
15
+ const LOCAL_JUPYTER_SERVER_URL = 'http://0.0.0.0:8888/api/jupyter-server';
16
+ const LOCAL_JUPYTER_SERVER_TOKEN = '60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6';
15
17
  loadJupyterConfig();
16
18
  const SERVICE_MANAGER_LESS = new ServiceManagerLess();
17
19
  const NotebookMutationsServiceManager = () => {
@@ -36,6 +38,7 @@ const NotebookMutationsServiceManager = () => {
36
38
  };
37
39
  const changeIndex = (index) => {
38
40
  setIndex(index);
41
+ setJupyterServerToken(getJupyterServerToken() || LOCAL_JUPYTER_SERVER_TOKEN);
39
42
  switch (index) {
40
43
  case 0: {
41
44
  setNbformat(getCurrentNotebookContent());
@@ -45,7 +48,7 @@ const NotebookMutationsServiceManager = () => {
45
48
  break;
46
49
  }
47
50
  case 1: {
48
- setJupyterServerUrl(location.protocol + '//' + location.host);
51
+ setJupyterServerUrl(LOCAL_JUPYTER_SERVER_URL);
49
52
  createLiteServiceManager().then(liteServiceManager => {
50
53
  setServiceManager(liteServiceManager);
51
54
  setNbformat(getCurrentNotebookContent());
@@ -55,7 +58,7 @@ const NotebookMutationsServiceManager = () => {
55
58
  break;
56
59
  }
57
60
  case 2: {
58
- setJupyterServerUrl(DEFAULT_JUPYTER_SERVER_URL);
61
+ setJupyterServerUrl(LOCAL_JUPYTER_SERVER_URL);
59
62
  setNbformat(getCurrentNotebookContent());
60
63
  setReadonly(false);
61
64
  setLite(false);
@@ -76,23 +76,23 @@ const Layout = () => {
76
76
  mb: 3,
77
77
  }, children: _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsx(NavLink, { to: "/", style: ({ isActive }) => ({
78
78
  fontWeight: isActive ? 'bold' : 'normal',
79
- color: isActive ? '#0969da' : 'inherit',
79
+ color: isActive ? '#16A085' : 'inherit',
80
80
  textDecoration: 'none',
81
81
  }), children: "Home" }), _jsx(NavLink, { to: "/dashboard", style: ({ isActive }) => ({
82
82
  fontWeight: isActive ? 'bold' : 'normal',
83
- color: isActive ? '#0969da' : 'inherit',
83
+ color: isActive ? '#16A085' : 'inherit',
84
84
  textDecoration: 'none',
85
85
  }), children: "Dashboard" }), _jsx(NavLink, { to: "/products", style: ({ isActive }) => ({
86
86
  fontWeight: isActive ? 'bold' : 'normal',
87
- color: isActive ? '#0969da' : 'inherit',
87
+ color: isActive ? '#16A085' : 'inherit',
88
88
  textDecoration: 'none',
89
89
  }), children: "Products" }), _jsx(NavLink, { to: "/users/john-doe", style: ({ isActive }) => ({
90
90
  fontWeight: isActive ? 'bold' : 'normal',
91
- color: isActive ? '#0969da' : 'inherit',
91
+ color: isActive ? '#16A085' : 'inherit',
92
92
  textDecoration: 'none',
93
93
  }), children: "User Profile" }), _jsx(NavLink, { to: "/settings", style: ({ isActive }) => ({
94
94
  fontWeight: isActive ? 'bold' : 'normal',
95
- color: isActive ? '#0969da' : 'inherit',
95
+ color: isActive ? '#16A085' : 'inherit',
96
96
  textDecoration: 'none',
97
97
  }), children: "Settings" })] }) }), _jsx(NavigationLogger, {}), _jsx(NavigationControls, {}), _jsx(Box, { sx: {
98
98
  p: 3,
@@ -15,6 +15,8 @@ import { createDatalayerServiceManager } from '../services/DatalayerServiceManag
15
15
  import { BOOTSTRAP_USER_ONBOARDING } from '@datalayer/core/lib/models/UserOnboarding';
16
16
  import nbformatExample from './notebooks/NotebookExample1.ipynb.json';
17
17
  const DEFAULT_RUNTIMES_URL = 'https://r1.datalayer.run';
18
+ const DEFAULT_LOCAL_JUPYTER_SERVER_URL = 'http://0.0.0.0:8888/api/jupyter-server';
19
+ const DEFAULT_LOCAL_JUPYTER_SERVER_TOKEN = '60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6';
18
20
  const resolveRuntimesUrl = (configured) => {
19
21
  const envRuntimeUrl = import.meta.env.VITE_DATALAYER_RUNTIMES_URL;
20
22
  const envBaseUrl = import.meta.env.VITE_DATALAYER_URL;
@@ -27,6 +29,34 @@ const resolveRuntimesUrl = (configured) => {
27
29
  }
28
30
  return candidate.replace(/\/$/, '');
29
31
  };
32
+ const isProd1JupyterServerUrl = (value) => {
33
+ if (!value) {
34
+ return false;
35
+ }
36
+ try {
37
+ return new URL(value).hostname === 'prod1.datalayer.run';
38
+ }
39
+ catch {
40
+ return value.includes('prod1.datalayer.run');
41
+ }
42
+ };
43
+ const resolveLocalJupyterServerUrl = () => {
44
+ const envLocalUrl = import.meta.env.VITE_JUPYTER_SERVER_URL?.trim();
45
+ if (envLocalUrl) {
46
+ return envLocalUrl.replace(/\/$/, '');
47
+ }
48
+ const configured = getJupyterServerUrl();
49
+ if (configured && !isProd1JupyterServerUrl(configured)) {
50
+ return configured;
51
+ }
52
+ return DEFAULT_LOCAL_JUPYTER_SERVER_URL;
53
+ };
54
+ const ensureLocalJupyterToken = () => {
55
+ const token = (getJupyterServerToken() || '').trim();
56
+ if (!token) {
57
+ setJupyterServerToken(DEFAULT_LOCAL_JUPYTER_SERVER_TOKEN);
58
+ }
59
+ };
30
60
  // Load configurations from DOM
31
61
  const loadConfigurations = () => {
32
62
  // Load Datalayer configuration
@@ -148,6 +178,8 @@ const NotebookOnlyApp = () => {
148
178
  }
149
179
  catch (error) {
150
180
  console.error('Failed to create DatalayerServiceManager:', error);
181
+ setJupyterServerUrl(resolveLocalJupyterServerUrl());
182
+ ensureLocalJupyterToken();
151
183
  const serverSettings = createServerSettings(getJupyterServerUrl(), getJupyterServerToken());
152
184
  const manager = new ServiceManager({ serverSettings });
153
185
  await manager.ready;
@@ -155,6 +187,8 @@ const NotebookOnlyApp = () => {
155
187
  }
156
188
  }
157
189
  else {
190
+ setJupyterServerUrl(resolveLocalJupyterServerUrl());
191
+ ensureLocalJupyterToken();
158
192
  const serverSettings = createServerSettings(getJupyterServerUrl(), getJupyterServerToken());
159
193
  const manager = new ServiceManager({ serverSettings });
160
194
  await manager.ready;
@@ -238,6 +272,8 @@ export const ExampleApp = () => {
238
272
  catch (error) {
239
273
  console.error('Failed to create DatalayerServiceManager:', error);
240
274
  // Fall back to regular ServiceManager
275
+ setJupyterServerUrl(resolveLocalJupyterServerUrl());
276
+ ensureLocalJupyterToken();
241
277
  const serverSettings = createServerSettings(getJupyterServerUrl(), getJupyterServerToken());
242
278
  const manager = new ServiceManager({ serverSettings });
243
279
  await manager.ready;
@@ -248,6 +284,8 @@ export const ExampleApp = () => {
248
284
  }
249
285
  else {
250
286
  // Use regular ServiceManager (no Datalayer token)
287
+ setJupyterServerUrl(resolveLocalJupyterServerUrl());
288
+ ensureLocalJupyterToken();
251
289
  const serverSettings = createServerSettings(getJupyterServerUrl(), getJupyterServerToken());
252
290
  const manager = new ServiceManager({ serverSettings });
253
291
  await manager.ready;
@@ -43,6 +43,10 @@ function redactWsUrl(wsUrl) {
43
43
  }
44
44
  // ─── Hook ────────────────────────────────────────────────────────────
45
45
  const RECONNECT_DELAY_MS = 3_000;
46
+ // Buffer outgoing messages sent while the socket is briefly down (e.g. a user
47
+ // approves during an auto-reconnect window) and flush them once reconnected.
48
+ const OUTBOUND_QUEUE_TTL_MS = 30_000;
49
+ const OUTBOUND_QUEUE_MAX = 50;
46
50
  const WS_DEFAULT_PATH = `${API_BASE_PATHS.AI_AGENTS}/ws`;
47
51
  const isDevTraceEnabled = () => {
48
52
  try {
@@ -86,6 +90,7 @@ export function useAIAgentsWebSocket(options) {
86
90
  const [reconnectAttempt, setReconnectAttempt] = useState(0);
87
91
  const wsRef = useRef(null);
88
92
  const reconnectTimer = useRef(null);
93
+ const outboundQueueRef = useRef([]);
89
94
  // Keep a ref of channels so we can re-subscribe on reconnect without
90
95
  // tearing down the socket when the array reference changes.
91
96
  const channelsRef = useRef(options?.channels ?? []);
@@ -143,6 +148,23 @@ export function useAIAgentsWebSocket(options) {
143
148
  if (channels.length > 0) {
144
149
  ws.send(JSON.stringify({ subscribe: { channels } }));
145
150
  }
151
+ // Flush messages buffered while the socket was down (dropping any
152
+ // that have exceeded their TTL) so decisions aren't lost on a blip.
153
+ const now = Date.now();
154
+ const queued = outboundQueueRef.current;
155
+ outboundQueueRef.current = [];
156
+ for (const item of queued) {
157
+ if (item.expiresAt < now)
158
+ continue;
159
+ try {
160
+ ws.send(typeof item.payload === 'string'
161
+ ? item.payload
162
+ : JSON.stringify(item.payload));
163
+ }
164
+ catch {
165
+ // Drop on serialization/send failure.
166
+ }
167
+ }
146
168
  };
147
169
  ws.onmessage = ev => {
148
170
  let raw;
@@ -289,6 +311,19 @@ export function useAIAgentsWebSocket(options) {
289
311
  send: (payload) => {
290
312
  const ws = wsRef.current;
291
313
  if (!ws || ws.readyState !== WebSocket.OPEN) {
314
+ // Not open: buffer for delivery on the next (auto)reconnect so a click
315
+ // during a transient drop isn't lost. Only when a reconnect is coming.
316
+ if (enabled && token && autoReconnectRef.current) {
317
+ const queue = outboundQueueRef.current;
318
+ if (queue.length >= OUTBOUND_QUEUE_MAX) {
319
+ queue.shift();
320
+ }
321
+ queue.push({
322
+ payload,
323
+ expiresAt: Date.now() + OUTBOUND_QUEUE_TTL_MS,
324
+ });
325
+ return true;
326
+ }
292
327
  return false;
293
328
  }
294
329
  try {
@@ -94,6 +94,28 @@ export declare const agentQueryKeys: {
94
94
  export declare function markRuntimePodDeleted(podName: string): void;
95
95
  /** Clear a deleted-pod tombstone (pod legitimately exists again). */
96
96
  export declare function clearRuntimePodDeleted(podName: string): void;
97
+ export interface AgentSandboxConnectionInfo {
98
+ baseUrl: string;
99
+ agentId?: string;
100
+ agentBaseUrl?: string;
101
+ runtimeEnvironment?: RuntimeEnvironmentDetails;
102
+ }
103
+ export interface RuntimeEnvironmentDetails {
104
+ environmentName?: string;
105
+ environmentTitle?: string;
106
+ cpu?: string;
107
+ memory?: string;
108
+ gpu?: string;
109
+ }
110
+ export declare function getServiceManagerRuntimeEnvironmentDetails(manager: ServiceManager.IManager | null | undefined): RuntimeEnvironmentDetails | undefined;
111
+ /**
112
+ * Connect a Jupyter ServiceManager to an already-running agent sandbox.
113
+ *
114
+ * This is the preferred cloud path when an agent runtime already exists:
115
+ * resolve sandbox status from the agent API, then bind directly to the
116
+ * returned Jupyter ingress URL/token instead of creating a new runtime.
117
+ */
118
+ export declare function createServiceManagerFromAgentSandbox(info: AgentSandboxConnectionInfo, authToken?: string): Promise<ServiceManager.IManager>;
97
119
  /**
98
120
  * Hook for managing agent runtimes.
99
121
  *
@@ -12,6 +12,7 @@
12
12
  * @module hooks/useAgentRuntimes
13
13
  */
14
14
  import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
15
+ import { ServerConnection } from '@jupyterlab/services';
15
16
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
16
17
  import { create } from 'zustand';
17
18
  import { persist, createJSONStorage } from 'zustand/middleware';
@@ -21,6 +22,7 @@ import { runtimesStore } from '../state';
21
22
  import { agentRuntimeStore, useAgentRuntimeStore, useAgentRuntimeConnection, useAgentRuntimeStatus, useAgentRuntimeError, useAgentRuntimeIsLaunching, } from '../stores/agentRuntimeStore';
22
23
  import { parseAgentStreamMessage, } from '../types/stream';
23
24
  import { DEFAULT_AGENT_CONFIG } from '../types/config';
25
+ import { ServiceManager } from '@jupyterlab/services/lib/manager';
24
26
  import { disposeSandboxServiceManagers } from '../services/sandboxServiceManagers';
25
27
  // ═══════════════════════════════════════════════════════════════════════════
26
28
  // Constants
@@ -115,6 +117,173 @@ const RUNTIME_STATUS_MAP = {
115
117
  archived: 'archived',
116
118
  running: 'running',
117
119
  };
120
+ const RUNTIME_ENVIRONMENT_META_KEY = '__datalayerRuntimeEnvironmentDetails';
121
+ function resolveSandboxIngress(payload) {
122
+ const direct = String(payload.jupyter_url || '').trim();
123
+ if (direct) {
124
+ return direct;
125
+ }
126
+ const nested = String(payload.sandbox?.jupyter_url || '').trim();
127
+ if (nested) {
128
+ return nested;
129
+ }
130
+ return '';
131
+ }
132
+ function resolveSandboxToken(payload) {
133
+ const direct = String(payload.jupyter_token || payload.token || '').trim();
134
+ if (direct) {
135
+ return direct;
136
+ }
137
+ return String(payload.sandbox?.jupyter_token || payload.sandbox?.token || '').trim();
138
+ }
139
+ function toOptionalString(value) {
140
+ const normalized = String(value || '').trim();
141
+ return normalized || undefined;
142
+ }
143
+ function resolveGpuLabel(resources) {
144
+ if (!resources) {
145
+ return undefined;
146
+ }
147
+ const gpuCount = toOptionalString(resources.gpu) ||
148
+ toOptionalString(resources.gpu_count) ||
149
+ toOptionalString(resources['nvidia.com/gpu']);
150
+ const gpuType = toOptionalString(resources.gpu_type);
151
+ const gpuMemory = toOptionalString(resources.gpu_memory);
152
+ const parts = [gpuCount, gpuType, gpuMemory].filter(Boolean);
153
+ return parts.length > 0 ? parts.join(' ') : undefined;
154
+ }
155
+ function resolveRuntimeEnvironmentDetails(payload, seed) {
156
+ const environment = payload?.environment;
157
+ const resources = environment?.resources;
158
+ const environmentName = toOptionalString(seed?.environmentName) ||
159
+ toOptionalString(environment?.name);
160
+ const environmentTitle = toOptionalString(seed?.environmentTitle) ||
161
+ toOptionalString(environment?.title);
162
+ const cpu = toOptionalString(seed?.cpu) ||
163
+ toOptionalString(environment?.cpu) ||
164
+ toOptionalString(resources?.cpu);
165
+ const memory = toOptionalString(seed?.memory) ||
166
+ toOptionalString(environment?.memory) ||
167
+ toOptionalString(resources?.memory);
168
+ const gpu = toOptionalString(seed?.gpu) ||
169
+ toOptionalString(environment?.gpu) ||
170
+ resolveGpuLabel(resources);
171
+ if (!environmentName && !environmentTitle && !cpu && !memory && !gpu) {
172
+ return undefined;
173
+ }
174
+ return {
175
+ environmentName,
176
+ environmentTitle,
177
+ cpu,
178
+ memory,
179
+ gpu,
180
+ };
181
+ }
182
+ export function getServiceManagerRuntimeEnvironmentDetails(manager) {
183
+ if (!manager) {
184
+ return undefined;
185
+ }
186
+ const carrier = manager;
187
+ return carrier[RUNTIME_ENVIRONMENT_META_KEY];
188
+ }
189
+ function isInternalHost(value) {
190
+ const host = String(value || '')
191
+ .trim()
192
+ .toLowerCase();
193
+ return host === '127.0.0.1' || host === '0.0.0.0' || host === 'localhost';
194
+ }
195
+ /**
196
+ * Connect a Jupyter ServiceManager to an already-running agent sandbox.
197
+ *
198
+ * This is the preferred cloud path when an agent runtime already exists:
199
+ * resolve sandbox status from the agent API, then bind directly to the
200
+ * returned Jupyter ingress URL/token instead of creating a new runtime.
201
+ */
202
+ export async function createServiceManagerFromAgentSandbox(info, authToken) {
203
+ const fallbackIngress = String(info.baseUrl || '').trim();
204
+ if (!fallbackIngress) {
205
+ throw new Error('Cloud agent sandbox base URL is missing.');
206
+ }
207
+ let resolvedIngress = fallbackIngress;
208
+ let tokenFromStatus = '';
209
+ let payloadFromStatus = null;
210
+ const agentBaseUrl = String(info.agentBaseUrl || '')
211
+ .trim()
212
+ .replace(/\/$/, '');
213
+ const agentId = String(info.agentId || '').trim();
214
+ const bearer = String(authToken || '').trim();
215
+ if (agentBaseUrl && agentId && bearer) {
216
+ const statusCandidates = [
217
+ `${agentBaseUrl}/api/v1/runtime/status?agent_id=${encodeURIComponent(agentId)}`,
218
+ ];
219
+ for (const statusUrl of statusCandidates) {
220
+ try {
221
+ const response = await fetch(statusUrl, {
222
+ headers: {
223
+ Authorization: `Bearer ${bearer}`,
224
+ },
225
+ });
226
+ if (!response.ok) {
227
+ continue;
228
+ }
229
+ const payload = (await response
230
+ .json()
231
+ .catch(() => null));
232
+ if (!payload) {
233
+ continue;
234
+ }
235
+ payloadFromStatus = payload;
236
+ const ingressFromStatus = resolveSandboxIngress(payload);
237
+ if (ingressFromStatus) {
238
+ try {
239
+ const parsed = new URL(ingressFromStatus);
240
+ // Cloud sandboxes can report an internal localhost URL in runtime
241
+ // status; do not override the known cloud ingress with that host.
242
+ if (!isInternalHost(parsed.hostname)) {
243
+ resolvedIngress = ingressFromStatus;
244
+ }
245
+ }
246
+ catch {
247
+ resolvedIngress = ingressFromStatus;
248
+ }
249
+ }
250
+ tokenFromStatus = resolveSandboxToken(payload);
251
+ break;
252
+ }
253
+ catch {
254
+ // Keep fallback ingress/token when status endpoint is unavailable.
255
+ }
256
+ }
257
+ }
258
+ let ingress = resolvedIngress.replace(/\/$/, '');
259
+ let tokenFromIngress = '';
260
+ try {
261
+ const parsed = new URL(resolvedIngress);
262
+ tokenFromIngress = String(parsed.searchParams.get('token') ||
263
+ parsed.searchParams.get('jupyter_token') ||
264
+ '').trim();
265
+ parsed.searchParams.delete('token');
266
+ parsed.searchParams.delete('jupyter_token');
267
+ ingress = parsed.toString().replace(/\/$/, '');
268
+ }
269
+ catch {
270
+ // Keep raw ingress when URL parsing fails.
271
+ }
272
+ const serverSettings = ServerConnection.makeSettings({
273
+ baseUrl: ingress,
274
+ wsUrl: ingress.replace(/^http/, 'ws'),
275
+ token: tokenFromIngress || tokenFromStatus || authToken || '',
276
+ appendToken: true,
277
+ });
278
+ const manager = new ServiceManager({ serverSettings });
279
+ await manager.ready;
280
+ const runtimeEnvironment = resolveRuntimeEnvironmentDetails(payloadFromStatus, info.runtimeEnvironment);
281
+ if (runtimeEnvironment) {
282
+ const carrier = manager;
283
+ carrier[RUNTIME_ENVIRONMENT_META_KEY] = runtimeEnvironment;
284
+ }
285
+ return manager;
286
+ }
118
287
  /**
119
288
  * Map a raw backend runtime record to AgentRuntimeData.
120
289
  */
@@ -129,8 +298,17 @@ function toAgentRuntimeData(raw) {
129
298
  const volume_uids = rawVolumeUids
130
299
  .map((uid) => String(uid || '').trim())
131
300
  .filter(Boolean);
301
+ const environment = raw.environment;
132
302
  return {
133
303
  ...raw,
304
+ environment: {
305
+ name: String(environment?.name || '').trim(),
306
+ title: String(environment?.title || '').trim() || undefined,
307
+ cpu: String(environment?.cpu || '').trim() || undefined,
308
+ memory: String(environment?.memory || '').trim() || undefined,
309
+ gpu: String(environment?.gpu || '').trim() || undefined,
310
+ resources: environment?.resources,
311
+ },
134
312
  status: normalizedStatus,
135
313
  name: raw.given_name || raw.pod_name,
136
314
  id: raw.pod_name,
@@ -201,7 +379,6 @@ export function useAgentRuntimes(options = {}) {
201
379
  }
202
380
  if (runtimeCreationTarget === 'local-agent-runtimes') {
203
381
  return (import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL ||
204
- import.meta.env.VITE_BASE_URL ||
205
382
  'http://localhost:8765');
206
383
  }
207
384
  return (configuration?.runtimesUrl ||
@@ -411,7 +588,7 @@ export function useAgentRuntimes(options = {}) {
411
588
  const runtimesResponse = await listRuntimes(token, runtimesUrl);
412
589
  const runtimes = runtimesResponse.runtimes || [];
413
590
  const aiAgentRuntimes = runtimes.filter(rt => {
414
- if (rt.environment_name !== 'ai-agents-env') {
591
+ if (rt.environment?.name !== 'ai-agents-env') {
415
592
  return false;
416
593
  }
417
594
  if (!agentSpecId) {
@@ -431,7 +608,7 @@ export function useAgentRuntimes(options = {}) {
431
608
  }
432
609
  storeConnectAgent({
433
610
  podName: latestRuntime.pod_name,
434
- environmentName: latestRuntime.environment_name,
611
+ environmentName: latestRuntime.environment.name,
435
612
  jupyterBaseUrl: latestRuntime.ingress,
436
613
  });
437
614
  // Ensure auto-create fires for this reconnected runtime.
@@ -572,7 +749,7 @@ export function useAgentRuntimesQuery(scope, queryOptions) {
572
749
  });
573
750
  if (resp.success && resp.runtimes) {
574
751
  const agentRuntimes = resp.runtimes
575
- .filter(rt => rt.environment_name === 'ai-agents-env')
752
+ .filter(rt => rt.environment?.name === 'ai-agents-env')
576
753
  .map(toAgentRuntimeData)
577
754
  // Drop stale echoes of pods deleted from this client so consumers
578
755
  // never reconnect a ServiceManager to a dead ingress (CORS spam).
@@ -643,7 +820,9 @@ export function useCreateAgentRuntime() {
643
820
  url: `${configuration.runtimesUrl}/api/runtimes/v1/runtimes`,
644
821
  method: 'POST',
645
822
  body: {
646
- environment_name: data.environmentName || 'ai-agents-env',
823
+ environment: {
824
+ name: data.environmentName || 'ai-agents-env',
825
+ },
647
826
  given_name: data.givenName || 'Agent',
648
827
  credits_limit: data.creditsLimit || 10,
649
828
  type: data.type || 'notebook',
@@ -954,6 +1133,14 @@ export function useAgentRuntimeWebSocket(options) {
954
1133
  state.applySnapshot(parsed.payload);
955
1134
  return;
956
1135
  }
1136
+ if (parsed.type === 'agent.subagent') {
1137
+ state.appendSubagentEvent(parsed.payload);
1138
+ return;
1139
+ }
1140
+ if (parsed.type === 'agent.compaction') {
1141
+ state.setCompaction(parsed.payload);
1142
+ return;
1143
+ }
957
1144
  };
958
1145
  ws.onclose = () => {
959
1146
  agentRuntimeStore.getState().setWs(null, agentId);
@@ -345,7 +345,7 @@ export function useAgentLifecycle(options) {
345
345
  const runtimesResponse = await listRuntimes(token, runtimesUrl);
346
346
  const runtimes = runtimesResponse.runtimes || [];
347
347
  const aiAgentRuntimes = runtimes.filter(rt => {
348
- if (rt.environment_name !== 'ai-agents-env') {
348
+ if (rt.environment.name !== 'ai-agents-env') {
349
349
  return false;
350
350
  }
351
351
  if (!agentSpecId) {
@@ -363,7 +363,7 @@ export function useAgentLifecycle(options) {
363
363
  if (latestRuntime?.pod_name && latestRuntime?.ingress) {
364
364
  connectToRuntime({
365
365
  podName: latestRuntime.pod_name,
366
- environmentName: latestRuntime.environment_name,
366
+ environmentName: latestRuntime.environment.name,
367
367
  jupyterBaseUrl: latestRuntime.ingress,
368
368
  });
369
369
  }
@@ -47,12 +47,17 @@ interface MutationResult {
47
47
  note?: string;
48
48
  }) => Promise<void>;
49
49
  }
50
+ type WSConnectionState = 'connecting' | 'connected' | 'closed';
51
+ interface DecisionMutationResult extends MutationResult {
52
+ /** Live socket state so callers can tailor offline messaging. */
53
+ connectionState: WSConnectionState;
54
+ }
50
55
  export declare function useToolApprovalsQuery(filters?: ToolApprovalFilters): import("@tanstack/react-query").UseQueryResult<NoInfer<ApprovalsQueryData>, Error>;
51
56
  export declare function usePendingApprovalCount(): import("@tanstack/react-query").UseQueryResult<NoInfer<{
52
57
  count: number;
53
58
  }>, Error>;
54
- export declare function useApproveToolRequest(): MutationResult;
55
- export declare function useRejectToolRequest(): MutationResult;
59
+ export declare function useApproveToolRequest(): DecisionMutationResult;
60
+ export declare function useRejectToolRequest(): DecisionMutationResult;
56
61
  export declare function useMarkToolApprovalRead(): MutationResult;
57
62
  export declare function useMarkToolApprovalUnread(): MutationResult;
58
63
  /**
@@ -68,10 +73,11 @@ export declare function useToolApprovals(filters?: ToolApprovalFilters): {
68
73
  pendingCountQuery: import("@tanstack/react-query").UseQueryResult<NoInfer<{
69
74
  count: number;
70
75
  }>, Error>;
71
- approve: MutationResult;
72
- reject: MutationResult;
76
+ approve: DecisionMutationResult;
77
+ reject: DecisionMutationResult;
73
78
  markRead: MutationResult;
74
79
  markUnread: MutationResult;
75
80
  remove: MutationResult;
81
+ connectionState: WSConnectionState;
76
82
  };
77
83
  export {};
@@ -30,7 +30,8 @@ function normalizeApproval(raw) {
30
30
  if (!raw || typeof raw !== 'object')
31
31
  return null;
32
32
  const rec = raw;
33
- const id = typeof rec.id === 'string' ? rec.id : undefined;
33
+ const idSource = rec.id ?? rec.approval_id ?? rec.approvalId;
34
+ const id = typeof idSource === 'string' ? idSource : undefined;
34
35
  if (!id)
35
36
  return null;
36
37
  const agent = str(rec.agent_id ?? rec.agentId);
@@ -262,7 +263,7 @@ export function usePendingApprovalCount() {
262
263
  }
263
264
  /** Build a mutation-style object that sends a WS decision. */
264
265
  function useDecisionMutation(approved) {
265
- const { send } = useApprovalsSocket();
266
+ const { send, connectionState } = useApprovalsSocket();
266
267
  const [isPending, setIsPending] = useState(false);
267
268
  const mutateAsync = useCallback(async ({ id, note }) => {
268
269
  setIsPending(true);
@@ -282,9 +283,11 @@ function useDecisionMutation(approved) {
282
283
  }
283
284
  }, [approved, send]);
284
285
  const mutate = useCallback((vars) => {
285
- void mutateAsync(vars);
286
+ void mutateAsync(vars).catch(err => {
287
+ console.warn('[tool-approval] decision was not sent', err);
288
+ });
286
289
  }, [mutateAsync]);
287
- return { isPending, mutate, mutateAsync };
290
+ return { isPending, mutate, mutateAsync, connectionState };
288
291
  }
289
292
  export function useApproveToolRequest() {
290
293
  return useDecisionMutation(true);
@@ -361,7 +364,9 @@ export function useDeleteToolApproval() {
361
364
  }
362
365
  }, [send]);
363
366
  const mutate = useCallback((vars) => {
364
- void mutateAsync(vars);
367
+ void mutateAsync(vars).catch(err => {
368
+ console.warn('[tool-approval] delete was not sent', err);
369
+ });
365
370
  }, [mutateAsync]);
366
371
  return { isPending, mutate, mutateAsync };
367
372
  }
@@ -382,6 +387,7 @@ export function useToolApprovals(filters) {
382
387
  markRead,
383
388
  markUnread,
384
389
  remove,
390
+ connectionState: approve.connectionState,
385
391
  }), [
386
392
  approvalsQuery,
387
393
  pendingQuery,