@datalayer/agent-runtimes 1.0.18 → 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.
- package/README.md +14 -6
- package/lib/AgentNode.js +567 -67
- package/lib/App.js +2 -2
- package/lib/agent-node/AgentNodeGallery.d.ts +80 -0
- package/lib/agent-node/AgentNodeGallery.js +627 -0
- package/lib/agent-notebook-main.js +0 -1
- package/lib/api/runtimes/runtimes.d.ts +57 -0
- package/lib/api/runtimes/runtimes.js +55 -1
- package/lib/chat/Chat.d.ts +14 -1
- package/lib/chat/Chat.js +22 -7
- package/lib/chat/base/ChatBase.js +86 -23
- package/lib/chat/document/EphemeralDocument.d.ts +49 -1
- package/lib/chat/document/EphemeralDocument.js +141 -15
- package/lib/chat/header/ChatHeaderBase.d.ts +3 -1
- package/lib/chat/header/ChatHeaderBase.js +5 -3
- package/lib/chat/messages/ChatMessageList.d.ts +19 -0
- package/lib/chat/messages/ChatMessageList.js +180 -9
- package/lib/chat/notebook/EphemeralNotebook.d.ts +40 -1
- package/lib/chat/notebook/EphemeralNotebook.js +49 -13
- package/lib/chat/prompt/InputPrompt.js +1 -1
- package/lib/chat/tools/SubagentActivity.d.ts +20 -0
- package/lib/chat/tools/SubagentActivity.js +154 -0
- package/lib/chat/tools/index.d.ts +1 -0
- package/lib/chat/tools/index.js +1 -0
- package/lib/chat/usage/TokenUsageBar.js +3 -3
- package/lib/client/AgentRuntimesClient.d.ts +5 -0
- package/lib/client/AgentsMixin.js +3 -1
- package/lib/client/mixins/RuntimesMixin.d.ts +12 -0
- package/lib/client/mixins/RuntimesMixin.js +24 -1
- package/lib/components/AgentSummary.d.ts +1 -0
- package/lib/components/AgentSummary.js +66 -10
- package/lib/components/runtimes/RuntimePickerBase.js +64 -29
- package/lib/components/runtimes/RuntimePickerCell.js +1 -1
- package/lib/components/runtimes/RuntimePickerNotebook.js +1 -1
- package/lib/components/runtimes/RuntimeSimplePicker.js +3 -2
- package/lib/components/runtimes/RuntimeUtils.js +2 -2
- package/lib/context/GraphFlowChart.js +1 -1
- package/lib/context/TokenUsageChart.js +1 -1
- package/lib/context/TurnGraphChart.js +2 -2
- package/lib/examples/A2UiAgentExample.js +4 -4
- package/lib/examples/A2UiComponentsGalleryExample.js +3 -3
- package/lib/examples/A2UiContactCardExample.js +3 -3
- package/lib/examples/A2UiRestaurantExample.js +2 -2
- package/lib/examples/A2UiViewerExample.js +3 -3
- package/lib/examples/AgUiAgenticExample.js +3 -3
- package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
- package/lib/examples/AgUiHaikuGenUiExample.js +3 -3
- package/lib/examples/AgUiHumanInTheLoopExample.js +5 -5
- package/lib/examples/AgUiSharedStateExample.js +3 -3
- package/lib/examples/AgUiToolsBasedGenUiExample.js +7 -5
- package/lib/examples/AgentCheckpointsExample.js +6 -5
- package/lib/examples/{AgentSandboxExample.d.ts → AgentCodeSandboxExample.d.ts} +4 -4
- package/lib/examples/{AgentSandboxExample.js → AgentCodeSandboxExample.js} +10 -9
- package/lib/examples/AgentCodeSandboxesExample.js +2 -2
- package/lib/examples/AgentCodemodeExample.js +3 -3
- package/lib/examples/AgentCompactionExample.d.ts +12 -0
- package/lib/examples/AgentCompactionExample.js +244 -0
- package/lib/examples/AgentEvalsExample.js +10 -20
- package/lib/examples/AgentGuardrailsExample.js +2 -2
- package/lib/examples/AgentHooksExample.js +1 -1
- package/lib/examples/AgentInferenceProviderExample.js +6 -8
- package/lib/examples/AgentLoopExample.js +3 -3
- package/lib/examples/AgentMCPExample.js +3 -3
- package/lib/examples/AgentMemoryExample.d.ts +4 -2
- package/lib/examples/AgentMemoryExample.js +93 -10
- package/lib/examples/AgentMonitoringExample.js +2 -2
- package/lib/examples/AgentNotificationsExample.js +1 -1
- package/lib/examples/AgentOtelExample.d.ts +2 -3
- package/lib/examples/AgentOtelExample.js +7 -12
- package/lib/examples/AgentOutputsExample.js +5 -5
- package/lib/examples/AgentParametersExample.js +3 -3
- package/lib/examples/AgentSkillsExample.js +4 -4
- package/lib/examples/AgentSubagentsExample.d.ts +2 -2
- package/lib/examples/AgentSubagentsExample.js +43 -34
- package/lib/examples/AgentToolApprovalsExample.js +1 -1
- package/lib/examples/AgentTriggersExample.js +9 -11
- package/lib/examples/AgentspecsExample.js +13 -5
- package/lib/examples/CellExample.d.ts +6 -0
- package/lib/examples/CellExample.js +84 -0
- package/lib/examples/ChatCustomExample.js +1 -1
- package/lib/examples/ChatExample.js +1 -1
- package/lib/examples/ChatStandaloneExample.js +2 -2
- package/lib/examples/HomeExample.js +29 -28
- package/lib/examples/Lexical2Example.js +2 -2
- package/lib/examples/LexicalAgentExample.js +1 -1
- package/lib/examples/LexicalAgentSidebarExample.js +2 -2
- package/lib/examples/NotebookAgentSidebarExample.d.ts +1 -1
- package/lib/examples/NotebookAgentSidebarExample.js +5 -4
- package/lib/examples/NotebookCollaborationExample.js +3 -3
- package/lib/examples/NotebookExample.js +2 -2
- package/lib/examples/components/ExampleWrapper.js +5 -0
- package/lib/examples/components/Header.js +2 -2
- package/lib/examples/components/HeaderControls.js +1 -1
- package/lib/examples/components/LexicalEditor.js +1 -1
- package/lib/examples/components/MainContent.js +1 -1
- package/lib/examples/components/MockFileBrowser.js +8 -8
- package/lib/examples/components/TimeTravel.js +1 -1
- package/lib/examples/components/haiku/HaikuDisplay.js +2 -2
- package/lib/examples/example-selector.js +4 -3
- package/lib/examples/hooks/useExampleAgentRuntime.js +11 -1
- package/lib/examples/hooks/useExampleAgentRuntimes.d.ts +7 -0
- package/lib/examples/hooks/useExampleAgentRuntimes.js +82 -0
- package/lib/examples/index.d.ts +2 -1
- package/lib/examples/index.js +2 -1
- package/lib/examples/main.js +359 -46
- package/lib/examples/utils/agentSummaryStore.d.ts +17 -0
- package/lib/examples/utils/themedProvider.js +32 -2
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +2 -1
- package/lib/examples2/NotebookMutationsKernel.js +6 -3
- package/lib/examples2/NotebookMutationsServiceManager.js +6 -3
- package/lib/examples2/ReactRouterExample.js +5 -5
- package/lib/examples2/main.js +38 -0
- package/lib/hooks/useAIAgentsWebSocket.js +35 -0
- package/lib/hooks/useAgentRuntimes.d.ts +22 -0
- package/lib/hooks/useAgentRuntimes.js +192 -5
- package/lib/hooks/useCheckpoints.js +2 -2
- package/lib/hooks/useToolApprovals.d.ts +10 -4
- package/lib/hooks/useToolApprovals.js +11 -5
- package/lib/identity/AgentIdentity.js +1 -2
- package/lib/models/Runtime.d.ts +10 -8
- package/lib/models/RuntimeDTO.d.ts +5 -3
- package/lib/models/RuntimeDTO.js +3 -3
- package/lib/runtimes/actions.js +3 -1
- package/lib/runtimes/apis.d.ts +7 -0
- package/lib/shims/primerAddonsBox.d.ts +1 -0
- package/lib/shims/primerAddonsBox.js +7 -0
- package/lib/specs/agents/agents.d.ts +51 -0
- package/lib/specs/agents/agents.js +3389 -94
- package/lib/specs/models.d.ts +2 -0
- package/lib/specs/models.js +37 -0
- package/lib/stores/agentRuntimeStore.d.ts +32 -1
- package/lib/stores/agentRuntimeStore.js +65 -1
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/types/agents.d.ts +9 -3
- package/lib/types/agentspecs.d.ts +2 -0
- package/lib/types/chat.d.ts +83 -0
- package/lib/types/models.d.ts +5 -0
- package/lib/types/stream.d.ts +58 -1
- package/package.json +6 -5
- package/scripts/codegen/generate_agents.py +7 -0
- package/scripts/codegen/generate_models.py +22 -9
- package/scripts/gen_workers.py +340 -0
- package/lib/examples/CellSimpleExample.d.ts +0 -6
- package/lib/examples/CellSimpleExample.js +0 -20
- package/scripts/codegen/__pycache__/generate_sandbox_agents.cpython-313.pyc +0 -0
|
@@ -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.
|
|
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,
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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 ? '#
|
|
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 ? '#
|
|
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 ? '#
|
|
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 ? '#
|
|
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 ? '#
|
|
95
|
+
color: isActive ? '#16A085' : 'inherit',
|
|
96
96
|
textDecoration: 'none',
|
|
97
97
|
}), children: "Settings" })] }) }), _jsx(NavigationLogger, {}), _jsx(NavigationControls, {}), _jsx(Box, { sx: {
|
|
98
98
|
p: 3,
|
package/lib/examples2/main.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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():
|
|
55
|
-
export declare function useRejectToolRequest():
|
|
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:
|
|
72
|
-
reject:
|
|
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
|
|
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,
|