@datalayer/core 1.1.59 → 1.1.66
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 +2 -2
- package/lib/api/DatalayerApi.d.ts +42 -1
- package/lib/api/DatalayerApi.js +90 -3
- package/lib/api/constants.d.ts +18 -0
- package/lib/api/constants.js +18 -0
- package/lib/api/contents/attachments.d.ts +9 -0
- package/lib/api/contents/attachments.js +43 -0
- package/lib/api/contents/bridges.d.ts +22 -0
- package/lib/api/contents/bridges.js +79 -0
- package/lib/api/contents/capabilities.d.ts +11 -0
- package/lib/api/contents/capabilities.js +21 -0
- package/lib/api/contents/cloudStorage.d.ts +31 -0
- package/lib/api/contents/cloudStorage.js +89 -0
- package/lib/api/contents/dataservers.d.ts +37 -0
- package/lib/api/contents/dataservers.js +121 -0
- package/lib/api/contents/datasets.d.ts +15 -0
- package/lib/api/contents/datasets.js +45 -0
- package/lib/api/contents/datasources.d.ts +70 -0
- package/lib/api/contents/datasources.js +149 -0
- package/lib/api/contents/generated.d.ts +1070 -0
- package/lib/api/contents/index.d.ts +16 -0
- package/lib/api/contents/index.js +20 -0
- package/lib/api/contents/mcp.d.ts +46 -0
- package/lib/api/contents/mcp.js +157 -0
- package/lib/api/contents/objects.d.ts +31 -0
- package/lib/api/contents/objects.js +88 -0
- package/lib/api/contents/operations.d.ts +3 -0
- package/lib/api/contents/operations.js +19 -0
- package/lib/api/contents/runtimeMounts.d.ts +40 -0
- package/lib/api/contents/runtimeMounts.js +59 -0
- package/lib/api/contents/sandboxUid.d.ts +53 -0
- package/lib/api/contents/sandboxUid.js +78 -0
- package/lib/api/contents/sources.d.ts +19 -0
- package/lib/api/contents/sources.js +119 -0
- package/lib/api/contents/sync.d.ts +18 -0
- package/lib/api/contents/sync.js +69 -0
- package/lib/api/contents/transfers.d.ts +38 -0
- package/lib/api/contents/transfers.js +123 -0
- package/lib/api/iam/connectedAgents.d.ts +39 -0
- package/lib/api/iam/connectedAgents.js +52 -0
- package/lib/api/iam/index.d.ts +2 -1
- package/lib/api/iam/index.js +2 -1
- package/lib/api/iam/mcpAlertRules.d.ts +51 -0
- package/lib/api/iam/mcpAlertRules.js +198 -0
- package/lib/api/iam/mcpAuditSettings.d.ts +46 -0
- package/lib/api/iam/mcpAuditSettings.js +120 -0
- package/lib/api/iam/mcpPolicy.d.ts +80 -0
- package/lib/api/iam/mcpPolicy.js +132 -0
- package/lib/api/iam/organizationTeams.d.ts +13 -0
- package/lib/api/iam/organizationTeams.js +43 -0
- package/lib/api/iam/serviceAgents.d.ts +68 -0
- package/lib/api/iam/serviceAgents.js +119 -0
- package/lib/api/index.d.ts +4 -1
- package/lib/api/index.js +3 -1
- package/lib/api/mcp/activity.d.ts +37 -0
- package/lib/api/mcp/activity.js +26 -0
- package/lib/api/mcp/alerts.d.ts +25 -0
- package/lib/api/mcp/alerts.js +51 -0
- package/lib/api/mcp/audit.d.ts +29 -0
- package/lib/api/mcp/audit.js +55 -0
- package/lib/api/mcp/bindings.d.ts +18 -0
- package/lib/api/mcp/bindings.js +42 -0
- package/lib/api/mcp/forwarding.d.ts +25 -0
- package/lib/api/mcp/forwarding.js +46 -0
- package/lib/api/mcp/gateway.d.ts +16 -0
- package/lib/api/mcp/gateway.js +70 -0
- package/lib/api/mcp/generated.d.ts +22 -0
- package/lib/api/mcp/generated.js +213 -0
- package/lib/api/mcp/index.d.ts +17 -0
- package/lib/api/mcp/index.js +25 -0
- package/lib/api/mcp/observability.d.ts +125 -0
- package/lib/api/mcp/observability.js +254 -0
- package/lib/api/mcp/operations.d.ts +83 -0
- package/lib/api/mcp/operations.js +50 -0
- package/lib/api/mcp/organizations.d.ts +102 -0
- package/lib/api/mcp/organizations.js +46 -0
- package/lib/api/mcp/policy.d.ts +9 -0
- package/lib/api/mcp/policy.js +23 -0
- package/lib/api/mcp/tasks.d.ts +49 -0
- package/lib/api/mcp/tasks.js +147 -0
- package/lib/api/utils/validation.d.ts +1 -1
- package/lib/api/utils/validation.js +1 -1
- package/lib/client/auth/index.js +0 -5
- package/lib/client/auth/storage.js +0 -5
- package/lib/client/auth/strategies.js +0 -5
- package/lib/client/base.d.ts +6 -0
- package/lib/client/base.js +8 -0
- package/lib/client/index.d.ts +0 -10
- package/lib/client/index.js +2 -2
- package/lib/client/mixins/ContentsMixin.d.ts +71 -0
- package/lib/client/mixins/ContentsMixin.js +157 -0
- package/lib/client/mixins/IAMMixin.d.ts +0 -31
- package/lib/client/mixins/IAMMixin.js +0 -58
- package/lib/components/animation/AnimatedText.d.ts +1 -0
- package/lib/components/animation/AnimatedText.js +0 -4
- package/lib/components/auth/SigInAPIKey.d.ts +1 -0
- package/lib/components/auth/SigInAPIKey.js +0 -6
- package/lib/components/auth/SignIn.d.ts +1 -0
- package/lib/components/auth/SignIn.js +0 -4
- package/lib/components/auth/SignIn.stories.d.ts +2 -2
- package/lib/components/avatars/BoringAvatar.d.ts +1 -1
- package/lib/components/avatars/BoringAvatar.stories.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.d.ts +1 -1
- package/lib/components/avatars/DLAvatar.js +0 -4
- package/lib/components/avatars/UserAvatar.d.ts +18 -1
- package/lib/components/avatars/UserAvatar.js +14 -14
- package/lib/components/avatars/UserProfileAvatar.d.ts +1 -1
- package/lib/components/banners/NoAutomationBanner.d.ts +1 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +10 -0
- package/lib/components/billing/BillingEntitySelect.js +0 -17
- package/lib/components/billing/index.js +0 -4
- package/lib/components/buttons/DownloadCSVButton.d.ts +1 -1
- package/lib/components/buttons/DownloadJsonButton.d.ts +1 -1
- package/lib/components/buttons/LongActionButton.d.ts +1 -0
- package/lib/components/buttons/LongActionButton.js +0 -4
- package/lib/components/buttons/UploadButton.d.ts +1 -0
- package/lib/components/buttons/UploadButton.js +0 -4
- package/lib/components/checkout/StripeCheckout.d.ts +1 -1
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +98 -53
- package/lib/components/collaboration/index.js +0 -4
- package/lib/components/confetti/ConfettiSuccess.d.ts +1 -1
- package/lib/components/contents/ContentSourceCard.d.ts +8 -0
- package/lib/components/contents/ContentSourceCard.js +23 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.d.ts +10 -0
- package/lib/components/contents/HomeFolderObjectsBrowser.js +148 -0
- package/lib/components/contents/index.d.ts +2 -0
- package/lib/components/contents/index.js +6 -0
- package/lib/components/context/OrganizationSelect.d.ts +1 -1
- package/lib/components/display/AvatarSkeleton.d.ts +1 -1
- package/lib/components/display/BusyDots.d.ts +1 -1
- package/lib/components/display/CenteredSpinner.d.ts +1 -1
- package/lib/components/display/CodePreview.d.ts +1 -1
- package/lib/components/display/DatalayerBox.d.ts +7 -2
- package/lib/components/display/DatalayerBox.js +6 -6
- package/lib/components/display/HorizontalCenter.d.ts +1 -1
- package/lib/components/display/JupyterDialog.d.ts +1 -0
- package/lib/components/display/JupyterDialog.js +1 -7
- package/lib/components/display/Markdown.d.ts +1 -0
- package/lib/components/display/NotebookSkeleton.d.ts +1 -1
- package/lib/components/display/Placeholder.d.ts +1 -1
- package/lib/components/display/ToTopBranded.d.ts +1 -1
- package/lib/components/echarts/EChartsReact.d.ts +1 -1
- package/lib/components/flashes/FlashClosable.d.ts +1 -0
- package/lib/components/flashes/FlashClosable.js +0 -4
- package/lib/components/flashes/FlashGuest.d.ts +1 -1
- package/lib/components/flashes/FlashSurveys.d.ts +1 -1
- package/lib/components/flashes/FlashUnauthorized.d.ts +1 -0
- package/lib/components/flashes/FlashUnauthorized.js +0 -6
- package/lib/components/flashes/surveys/Survey2025_1.d.ts +1 -1
- package/lib/components/iam/ExternalTokenSilentLogin.d.ts +1 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/labels/StatusLabels.d.ts +10 -0
- package/lib/components/labels/StatusLabels.js +30 -1
- package/lib/components/labels/VisibilityLabel.d.ts +1 -1
- package/lib/components/landings/StepBlock.d.ts +1 -1
- package/lib/components/mcp/ClientBadge.d.ts +41 -0
- package/lib/components/mcp/ClientBadge.js +33 -0
- package/lib/components/mcp/McpErrorBlankslate.d.ts +21 -0
- package/lib/components/mcp/McpErrorBlankslate.js +17 -0
- package/lib/components/mcp/ScopeList.d.ts +28 -0
- package/lib/components/mcp/ScopeList.js +14 -0
- package/lib/components/mcp/index.d.ts +10 -0
- package/lib/components/mcp/index.js +18 -0
- package/lib/components/navbar/NavigationVisbilityObserver.d.ts +2 -1
- package/lib/components/navbar/SubdomainNavBar.d.ts +10 -5
- package/lib/components/navbar/SubdomainNavBar.js +2 -1
- package/lib/components/notebooks/JupyterNotebook.d.ts +1 -1
- package/lib/components/notebooks/JupyterNotebookToolbar.d.ts +1 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +1 -0
- package/lib/components/principal/PrincipalAppearance.js +0 -8
- package/lib/components/principal/PrincipalAvatar.d.ts +9 -1
- package/lib/components/principal/PrincipalAvatar.js +25 -10
- package/lib/components/principal/PrincipalBadge.d.ts +1 -1
- package/lib/components/principal/PrincipalBanner.d.ts +1 -1
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +31 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +64 -33
- package/lib/components/principal/PrincipalHoverCard.d.ts +31 -0
- package/lib/components/principal/PrincipalHoverCard.js +52 -0
- package/lib/components/principal/PrincipalSwitcherMenu.d.ts +1 -0
- package/lib/components/principal/PrincipalSwitcherMenu.js +7 -15
- package/lib/components/principal/index.d.ts +2 -1
- package/lib/components/principal/index.js +2 -1
- package/lib/components/progress/ConsumptionBar.d.ts +1 -0
- package/lib/components/progress/ConsumptionBar.js +0 -4
- package/lib/components/progress/ProgressRing.d.ts +1 -0
- package/lib/components/progress/SpinnerBar.d.ts +1 -1
- package/lib/components/progress/Timer.d.ts +1 -0
- package/lib/components/progress/Timer.js +0 -4
- package/lib/components/screencapture/ScreencaptureButton.d.ts +1 -1
- package/lib/components/sharing/ShareAccessComponent.d.ts +15 -1
- package/lib/components/sharing/ShareAccessComponent.js +36 -23
- package/lib/components/sharing/SharingEditor.d.ts +17 -0
- package/lib/components/sharing/SharingEditor.js +0 -24
- package/lib/components/sparklines/Sparklines.js +3 -4
- package/lib/components/sparklines/index.js +0 -4
- package/lib/components/storage/ContentsBrowser.d.ts +25 -1
- package/lib/components/storage/ContentsBrowser.js +145 -52
- package/lib/components/storage/ContentsBrowserMock.d.ts +3 -0
- package/lib/components/storage/ContentsBrowserMock.js +75 -0
- package/lib/components/storage/ContentsItems.d.ts +2 -1
- package/lib/components/storage/ContentsItems.js +0 -4
- package/lib/components/storage/ContentsTable.d.ts +50 -0
- package/lib/components/storage/ContentsTable.js +134 -0
- package/lib/components/storage/HomeFolderDrive.d.ts +71 -0
- package/lib/components/storage/HomeFolderDrive.js +248 -0
- package/lib/components/storage/index.d.ts +2 -0
- package/lib/components/storage/index.js +2 -0
- package/lib/components/students/StudentItemStatus.d.ts +1 -1
- package/lib/components/students/StudentResultCircle.d.ts +1 -1
- package/lib/components/subnav/SubNav.d.ts +5 -5
- package/lib/components/subnav/SubNav.js +11 -9
- package/lib/components/tables/DataTable.d.ts +1 -1
- package/lib/components/text-reveal/TextRevealAnimation.d.ts +2 -2
- package/lib/components/text-reveal/useTextRevealAnimationLines.d.ts +1 -1
- package/lib/components/time/LiveRelativeTime.d.ts +1 -0
- package/lib/components/time/LiveRelativeTime.js +0 -8
- package/lib/components/time/index.js +0 -4
- package/lib/components/timeline/Timeline.d.ts +1 -1
- package/lib/components/timeline/index.js +0 -4
- package/lib/components/users/PeerIndicator.d.ts +1 -0
- package/lib/components/users/PeerIndicator.js +0 -4
- package/lib/config/Configuration.d.ts +15 -17
- package/lib/config/Configuration.js +7 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +2 -0
- package/lib/hooks/layouts/LayoutBackdrop.d.ts +1 -1
- package/lib/hooks/layouts/LayoutScreencapture.d.ts +1 -1
- package/lib/hooks/useBackdrop.d.ts +1 -1
- package/lib/hooks/useBackdropJupyterLab.d.ts +3 -2
- package/lib/hooks/useBackdropJupyterLab.js +0 -4
- package/lib/hooks/useCache.d.ts +310 -105
- package/lib/hooks/useCache.js +142 -355
- package/lib/hooks/useContainsFocus.d.ts +1 -1
- package/lib/hooks/useContents.d.ts +313 -0
- package/lib/hooks/useContents.js +964 -0
- package/lib/hooks/useFocusTrap.d.ts +4 -4
- package/lib/hooks/useFocusTrap.js +1 -1
- package/lib/hooks/useMcp.d.ts +229 -0
- package/lib/hooks/useMcp.js +656 -0
- package/lib/hooks/useMobile.js +0 -5
- package/lib/hooks/useProjectStore.d.ts +3 -3
- package/lib/hooks/useProjectStore.js +4 -4
- package/lib/hooks/useRef.d.ts +1 -1
- package/lib/hooks/useRef.js +3 -0
- package/lib/hooks/useScreencapture.d.ts +2 -1
- package/lib/hooks/useScreencapture.js +0 -4
- package/lib/hooks/useToast.js +0 -4
- package/lib/hooks/useUser.js +42 -0
- package/lib/hooks/useVisibilityObserver.d.ts +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +3 -1
- package/lib/mocks/components/FlashMock.d.ts +1 -1
- package/lib/mocks/views/ActionMenuMock.d.ts +1 -1
- package/lib/mocks/views/ChartMock.d.ts +1 -1
- package/lib/mocks/views/DashboardMock.d.ts +1 -1
- package/lib/mocks/views/FormMock.d.ts +1 -1
- package/lib/mocks/views/TableMock.d.ts +1 -1
- package/lib/mocks/views/WipMock.d.ts +1 -1
- package/lib/models/Credits.d.ts +16 -0
- package/lib/models/Credits.js +22 -0
- package/lib/models/McpAuditEvent.d.ts +52 -0
- package/lib/models/McpAuditEvent.js +5 -0
- package/lib/models/McpBinding.d.ts +51 -0
- package/lib/models/McpBinding.js +5 -0
- package/lib/models/McpPolicy.d.ts +77 -0
- package/lib/models/McpPolicy.js +5 -0
- package/lib/models/McpTask.d.ts +94 -0
- package/lib/models/McpTask.js +7 -0
- package/lib/models/StartedBy.d.ts +57 -0
- package/lib/models/StartedBy.js +81 -0
- package/lib/models/UserDTO.js +4 -2
- package/lib/models/contents/__fixtures__/solr-codec.json +57 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +66 -0
- package/lib/models/contents/fieldCase.d.ts +8 -0
- package/lib/models/contents/fieldCase.js +37 -0
- package/lib/models/contents/index.d.ts +2 -0
- package/lib/models/contents/index.js +6 -0
- package/lib/models/contents/permissions.d.ts +16 -0
- package/lib/models/contents/permissions.js +34 -0
- package/lib/models/index.d.ts +5 -1
- package/lib/models/index.js +5 -1
- package/lib/state/substates/CoreState.d.ts +15 -7
- package/lib/state/substates/CoreState.js +18 -14
- package/lib/state/substates/IAMState.d.ts +1 -1
- package/lib/state/substates/IAMState.js +5 -5
- package/lib/state/substates/ProfileState.js +0 -4
- package/lib/state/substates/index.d.ts +0 -1
- package/lib/state/substates/index.js +0 -1
- package/lib/utils/Jwt.d.ts +26 -0
- package/lib/utils/Jwt.js +37 -0
- package/lib/utils/WithSuspense.d.ts +1 -1
- package/lib/views/api-keys/APIKeyEdit.d.ts +1 -1
- package/lib/views/api-keys/APIKeyNew.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.d.ts +1 -1
- package/lib/views/api-keys/APIKeys.js +30 -4
- package/lib/views/api-keys/APIKeysStandalone.d.ts +5 -1
- package/lib/views/api-keys/APIKeysStandalone.js +2 -2
- package/lib/views/datasources/DatasourceDetail.d.ts +12 -5
- package/lib/views/datasources/DatasourceDetail.js +80 -110
- package/lib/views/datasources/DatasourceNew.d.ts +14 -8
- package/lib/views/datasources/DatasourceNew.js +129 -118
- package/lib/views/datasources/Datasources.d.ts +39 -7
- package/lib/views/datasources/Datasources.js +95 -40
- package/lib/views/datasources/DatasourcesMock.d.ts +3 -0
- package/lib/views/datasources/DatasourcesMock.js +57 -0
- package/lib/views/iam/SignInSimple.d.ts +12 -0
- package/lib/views/iam/SignInSimple.js +4 -4
- package/lib/views/index.d.ts +1 -0
- package/lib/views/index.js +1 -0
- package/lib/views/mcp/AlertDestinations.d.ts +34 -0
- package/lib/views/mcp/AlertDestinations.js +75 -0
- package/lib/views/mcp/AlertRules.d.ts +32 -0
- package/lib/views/mcp/AlertRules.js +245 -0
- package/lib/views/mcp/AuditLog.d.ts +42 -0
- package/lib/views/mcp/AuditLog.js +144 -0
- package/lib/views/mcp/ConnectedAgents.d.ts +34 -0
- package/lib/views/mcp/ConnectedAgents.js +100 -0
- package/lib/views/mcp/EnterpriseConsole.d.ts +74 -0
- package/lib/views/mcp/EnterpriseConsole.js +173 -0
- package/lib/views/mcp/McpDashboard.d.ts +50 -0
- package/lib/views/mcp/McpDashboard.js +347 -0
- package/lib/views/mcp/McpHome.d.ts +92 -0
- package/lib/views/mcp/McpHome.js +153 -0
- package/lib/views/mcp/McpObservability.d.ts +50 -0
- package/lib/views/mcp/McpObservability.js +150 -0
- package/lib/views/mcp/OrganizationPolicy.d.ts +45 -0
- package/lib/views/mcp/OrganizationPolicy.js +107 -0
- package/lib/views/mcp/OrganizationUsage.d.ts +69 -0
- package/lib/views/mcp/OrganizationUsage.js +113 -0
- package/lib/views/mcp/PersonalPolicy.d.ts +54 -0
- package/lib/views/mcp/PersonalPolicy.js +125 -0
- package/lib/views/mcp/Policies.d.ts +53 -0
- package/lib/views/mcp/Policies.js +210 -0
- package/lib/views/mcp/PolicyForm.d.ts +67 -0
- package/lib/views/mcp/PolicyForm.js +91 -0
- package/lib/views/mcp/PolicyHistory.d.ts +45 -0
- package/lib/views/mcp/PolicyHistory.js +62 -0
- package/lib/views/mcp/ServiceAgents.d.ts +46 -0
- package/lib/views/mcp/ServiceAgents.js +194 -0
- package/lib/views/mcp/TeamPolicies.d.ts +33 -0
- package/lib/views/mcp/TeamPolicies.js +98 -0
- package/lib/views/mcp/format.d.ts +25 -0
- package/lib/views/mcp/format.js +77 -0
- package/lib/views/mcp/index.d.ts +29 -0
- package/lib/views/mcp/index.js +37 -0
- package/lib/views/mcp/types.d.ts +66 -0
- package/lib/views/mcp/types.js +5 -0
- package/lib/views/secrets/SecretEdit.d.ts +5 -1
- package/lib/views/secrets/SecretEdit.js +2 -2
- package/lib/views/secrets/SecretNew.d.ts +11 -1
- package/lib/views/secrets/SecretNew.js +2 -2
- package/lib/views/secrets/Secrets.d.ts +12 -1
- package/lib/views/secrets/Secrets.js +2 -2
- package/package.json +28 -24
- package/scripts/generate-contents-types.py +169 -0
- package/scripts/generate-mcp-types.py +233 -0
- package/lib/api/iam/datasources.d.ts +0 -86
- package/lib/api/iam/datasources.js +0 -185
- package/lib/components/toolbars/index.d.ts +0 -1
- package/lib/models/Datasource.d.ts +0 -181
- package/lib/models/Datasource.js +0 -154
- package/lib/state/substates/DatasourceState.d.ts +0 -11
- package/lib/state/substates/DatasourceState.js +0 -16
- /package/lib/{components/toolbars/index.js → api/contents/generated.js} +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ActionList, ActionMenu, Button, Heading, Label, Link, Select, Spinner, Text, } from '@primer/react';
|
|
4
|
+
import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
|
|
5
|
+
import { Box } from '@datalayer/primer-addons';
|
|
6
|
+
import { ChecklistIcon, KebabHorizontalIcon, PlayIcon, PlugIcon, StackIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
7
|
+
import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
+
import { useDisconnectAgent, useMcpActivity, useTerminateBinding, } from '../../hooks/useMcp.js';
|
|
9
|
+
import { useNavigate, useToast } from '../../hooks/index.js';
|
|
10
|
+
import { clientStatusOf, durationLabel, plural, timeAgo } from './format.js';
|
|
11
|
+
/** How a client's status is drawn. */
|
|
12
|
+
const STATUS_LOOK = {
|
|
13
|
+
active: { label: 'Active', variant: 'success' },
|
|
14
|
+
idle: { label: 'Idle', variant: 'attention' },
|
|
15
|
+
disconnected: { label: 'Disconnected', variant: 'secondary' },
|
|
16
|
+
};
|
|
17
|
+
/** What a client is working on, in one phrase. */
|
|
18
|
+
const boundTo = (client) => {
|
|
19
|
+
const call = client.lastCall;
|
|
20
|
+
if (!call) {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
if (call.itemUid) {
|
|
24
|
+
return `Notebook ${call.itemUid}`;
|
|
25
|
+
}
|
|
26
|
+
if (call.sourceUid) {
|
|
27
|
+
return `Source ${call.sourceUid}`;
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
};
|
|
31
|
+
const countLook = (tone) => tone === 'danger' ? 'danger.fg' : 'fg.default';
|
|
32
|
+
const Count = ({ label, value, tone = 'neutral', note, }) => (_jsxs(Box, { sx: {
|
|
33
|
+
p: 3,
|
|
34
|
+
border: '1px solid',
|
|
35
|
+
borderColor: 'border.default',
|
|
36
|
+
borderRadius: 2,
|
|
37
|
+
bg: 'canvas.default',
|
|
38
|
+
minWidth: 0,
|
|
39
|
+
}, children: [_jsx(Text, { sx: {
|
|
40
|
+
display: 'block',
|
|
41
|
+
fontSize: 4,
|
|
42
|
+
fontWeight: 'bold',
|
|
43
|
+
color: countLook(tone),
|
|
44
|
+
}, children: value }), _jsx(Text, { sx: { display: 'block', fontSize: 0, color: 'fg.muted' }, children: label }), note && (_jsx(Text, { sx: { display: 'block', fontSize: 0, color: 'fg.subtle', mt: 1 }, children: note }))] }));
|
|
45
|
+
/** A panel that has nothing to show, and the step that would fill it. */
|
|
46
|
+
const PanelEmpty = ({ icon: Icon, heading, description, action, }) => (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(Icon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: heading }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center', m: 0 }, children: description }) }), action && (_jsx(Button, { size: "small", onClick: action.onClick, children: action.label }))] }));
|
|
47
|
+
export const McpDashboard = ({ errorState, routes, org, showTitle = true, section, }) => {
|
|
48
|
+
/** Whether this section is the one being drawn. */
|
|
49
|
+
const draws = (name) => section === name;
|
|
50
|
+
const navigate = useNavigate();
|
|
51
|
+
const { enqueueToast } = useToast();
|
|
52
|
+
const activity = useMcpActivity(org ? { org } : {});
|
|
53
|
+
const disconnect = useDisconnectAgent();
|
|
54
|
+
const terminate = useTerminateBinding();
|
|
55
|
+
const [disconnecting, setDisconnecting] = useState(null);
|
|
56
|
+
const [terminating, setTerminating] = useState(null);
|
|
57
|
+
// Primer 37 types Dialog's focus refs with React 18's non-nullable
|
|
58
|
+
// `RefObject<HTMLElement>`. React 19's `useRef(null)` is nullable, and this
|
|
59
|
+
// ref is only handed to Dialog, so narrow it once here.
|
|
60
|
+
const returnFocusRef = useRef(null);
|
|
61
|
+
// The calls panel filters what is already in hand: the answer carries the
|
|
62
|
+
// last fifty calls, so narrowing them is a matter of the browser, not of
|
|
63
|
+
// another request.
|
|
64
|
+
const [callClient, setCallClient] = useState('');
|
|
65
|
+
const [callTool, setCallTool] = useState('');
|
|
66
|
+
const [callOutcome, setCallOutcome] = useState('');
|
|
67
|
+
const data = activity.data;
|
|
68
|
+
const clients = useMemo(() => (data?.clients ?? []).map(client => ({
|
|
69
|
+
...client,
|
|
70
|
+
id: client.clientId,
|
|
71
|
+
status: clientStatusOf(client.lastCall?.at),
|
|
72
|
+
})), [data?.clients]);
|
|
73
|
+
const sandboxes = useMemo(() => (data?.sandboxes ?? []).map(binding => ({ ...binding, id: binding.uid })), [data?.sandboxes]);
|
|
74
|
+
const calls = useMemo(() => (data?.calls ?? []).map(call => ({ ...call, id: call.uid })), [data?.calls]);
|
|
75
|
+
const filteredCalls = useMemo(() => calls.filter(call => (!callClient || call.clientId === callClient) &&
|
|
76
|
+
(!callTool || call.tool === callTool) &&
|
|
77
|
+
(!callOutcome ||
|
|
78
|
+
(callOutcome === 'refused'
|
|
79
|
+
? call.decision === 'refused'
|
|
80
|
+
: call.outcome === callOutcome))), [calls, callClient, callTool, callOutcome]);
|
|
81
|
+
const callTools = useMemo(() => [
|
|
82
|
+
...new Set(calls.map(call => call.tool).filter(Boolean)),
|
|
83
|
+
].sort(), [calls]);
|
|
84
|
+
const auditFor = (call) => {
|
|
85
|
+
const parameters = new URLSearchParams();
|
|
86
|
+
if (call.taskId) {
|
|
87
|
+
parameters.set('task', call.taskId);
|
|
88
|
+
}
|
|
89
|
+
else if (call.traceId) {
|
|
90
|
+
parameters.set('trace', call.traceId);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (call.clientId) {
|
|
94
|
+
parameters.set('agent', call.clientId);
|
|
95
|
+
}
|
|
96
|
+
if (call.tool) {
|
|
97
|
+
parameters.set('tool', call.tool);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const search = parameters.toString();
|
|
101
|
+
return search ? `${routes.audit}?${search}` : routes.audit;
|
|
102
|
+
};
|
|
103
|
+
const traceFor = (call) => {
|
|
104
|
+
if (call.taskId) {
|
|
105
|
+
return `${routes.observability}?task=${encodeURIComponent(call.taskId)}`;
|
|
106
|
+
}
|
|
107
|
+
if (call.traceId) {
|
|
108
|
+
return `${routes.observability}?trace=${encodeURIComponent(call.traceId)}`;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
const confirmDisconnect = () => {
|
|
113
|
+
const client = disconnecting;
|
|
114
|
+
if (!client?.grantUid) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
disconnect.mutate(client.grantUid, {
|
|
118
|
+
onSuccess: () => {
|
|
119
|
+
enqueueToast(`${client.clientName || client.clientId} is disconnected.`, {
|
|
120
|
+
variant: 'success',
|
|
121
|
+
});
|
|
122
|
+
setDisconnecting(null);
|
|
123
|
+
},
|
|
124
|
+
onError: reason => {
|
|
125
|
+
enqueueToast(`Could not disconnect: ${reason.message}`, {
|
|
126
|
+
variant: 'error',
|
|
127
|
+
});
|
|
128
|
+
setDisconnecting(null);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
const confirmTerminate = () => {
|
|
133
|
+
const binding = terminating;
|
|
134
|
+
if (!binding) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
terminate.mutate(binding.uid, {
|
|
138
|
+
onSuccess: () => {
|
|
139
|
+
enqueueToast(`Sandbox ${binding.uid} is terminated.`, {
|
|
140
|
+
variant: 'success',
|
|
141
|
+
});
|
|
142
|
+
setTerminating(null);
|
|
143
|
+
},
|
|
144
|
+
onError: reason => {
|
|
145
|
+
enqueueToast(`Could not terminate: ${reason.message}`, {
|
|
146
|
+
variant: 'error',
|
|
147
|
+
});
|
|
148
|
+
setTerminating(null);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const clientColumns = [
|
|
153
|
+
{
|
|
154
|
+
header: 'Client',
|
|
155
|
+
field: 'clientId',
|
|
156
|
+
rowHeader: true,
|
|
157
|
+
renderCell: row => (_jsx(ClientBadge, { clientId: row.clientId, clientName: row.clientName })),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
header: 'Kind',
|
|
161
|
+
id: 'kind',
|
|
162
|
+
width: '110px',
|
|
163
|
+
// Service agents are principals of an organization and arrive with
|
|
164
|
+
// milestone 3; every grant IAM answers today is a delegation.
|
|
165
|
+
renderCell: () => (_jsx(Label, { size: "small", variant: "secondary", children: "Delegated" })),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
header: 'Bound to',
|
|
169
|
+
id: 'bound',
|
|
170
|
+
width: 'growCollapse',
|
|
171
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: boundTo(row) || '—' })),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
header: 'Last tool',
|
|
175
|
+
id: 'last-tool',
|
|
176
|
+
renderCell: row => row.lastCall ? (_jsxs(Box, { sx: { display: 'grid', gap: '2px', minWidth: 0 }, children: [_jsx(Text, { sx: { fontSize: 0 }, children: row.lastCall.tool || row.lastCall.method }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: timeAgo(row.lastCall.at) })] })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing yet" })),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
header: 'Status',
|
|
180
|
+
id: 'status',
|
|
181
|
+
width: '120px',
|
|
182
|
+
renderCell: row => (_jsx(Label, { size: "small", variant: STATUS_LOOK[row.status].variant, children: STATUS_LOOK[row.status].label })),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
header: '',
|
|
186
|
+
id: 'actions',
|
|
187
|
+
width: '48px',
|
|
188
|
+
align: 'end',
|
|
189
|
+
renderCell: row => (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Button, { variant: "invisible", size: "small", "aria-label": `Actions for ${row.clientName || row.clientId}`, icon: KebabHorizontalIcon }) }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsx(ActionList.Item, { onSelect: () => navigate(`${routes.audit}?agent=${encodeURIComponent(row.clientId)}`), children: "Audit" }), _jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { variant: "danger", disabled: !row.grantUid, onSelect: () => setDisconnecting(row), children: "Disconnect" })] }) })] })),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
const sandboxColumns = [
|
|
193
|
+
{
|
|
194
|
+
header: 'Handle',
|
|
195
|
+
field: 'uid',
|
|
196
|
+
rowHeader: true,
|
|
197
|
+
renderCell: row => (_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: row.uid })),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
header: 'Provider',
|
|
201
|
+
id: 'provider',
|
|
202
|
+
width: '120px',
|
|
203
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0 }, children: row.sandboxProvider || 'datalayer' })),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
header: 'Capabilities',
|
|
207
|
+
id: 'capabilities',
|
|
208
|
+
width: 'growCollapse',
|
|
209
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [(row.capabilities ?? []).map(capability => (_jsx(Label, { size: "small", variant: "secondary", children: capability }, capability))), (row.capabilities ?? []).length === 0 && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "\u2014" }))] })),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
header: 'Last used',
|
|
213
|
+
id: 'last-used',
|
|
214
|
+
width: '120px',
|
|
215
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: timeAgo(row.lastUsedAt ?? row.createdAt) || '—' })),
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
header: 'State',
|
|
219
|
+
id: 'state',
|
|
220
|
+
width: '150px',
|
|
221
|
+
renderCell: row => row.state === 'lost' ? (
|
|
222
|
+
// The loss is a fact of the session, not a hiccup: it is named
|
|
223
|
+
// with the error the next call was answered with.
|
|
224
|
+
_jsx(Label, { size: "small", variant: "danger", children: "SANDBOX_LOST" })) : (_jsx(Label, { size: "small", variant: row.state === 'active' ? 'success' : 'secondary', children: row.state ?? 'active' })),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
header: '',
|
|
228
|
+
id: 'actions',
|
|
229
|
+
width: '110px',
|
|
230
|
+
align: 'end',
|
|
231
|
+
renderCell: row => (_jsx(Button, { size: "small", variant: "danger", disabled: row.state === 'closed', onClick: () => setTerminating(row), children: "Terminate" })),
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
const callColumns = [
|
|
235
|
+
{
|
|
236
|
+
header: 'Tool',
|
|
237
|
+
field: 'tool',
|
|
238
|
+
rowHeader: true,
|
|
239
|
+
renderCell: row => (_jsxs(Box, { sx: { display: 'grid', gap: '2px', minWidth: 0 }, children: [_jsx(Text, { sx: { fontSize: 1 }, children: row.tool || row.method }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: timeAgo(row.at) })] })),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
header: 'Client',
|
|
243
|
+
id: 'client',
|
|
244
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.clientId || '—' })),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
header: 'On',
|
|
248
|
+
id: 'item',
|
|
249
|
+
width: 'growCollapse',
|
|
250
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.itemUid || row.sourceUid || '—' })),
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
header: 'Decision',
|
|
254
|
+
id: 'decision',
|
|
255
|
+
width: '110px',
|
|
256
|
+
renderCell: row => (_jsx(Label, { size: "small", variant: row.decision === 'refused' ? 'danger' : 'success', children: row.decision })),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
header: 'Outcome',
|
|
260
|
+
id: 'outcome',
|
|
261
|
+
width: '110px',
|
|
262
|
+
renderCell: row => (_jsx(Text, { sx: {
|
|
263
|
+
fontSize: 0,
|
|
264
|
+
color: row.outcome === 'ok' ? 'fg.muted' : 'danger.fg',
|
|
265
|
+
}, children: row.outcome ?? '—' })),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
header: 'Took',
|
|
269
|
+
id: 'duration',
|
|
270
|
+
width: '90px',
|
|
271
|
+
renderCell: row => (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: durationLabel(row.durationMs) || '—' })),
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
header: '',
|
|
275
|
+
id: 'links',
|
|
276
|
+
width: '150px',
|
|
277
|
+
align: 'end',
|
|
278
|
+
renderCell: row => {
|
|
279
|
+
const trace = traceFor(row);
|
|
280
|
+
return (_jsxs(Box, { sx: { display: 'flex', gap: 2, justifyContent: 'flex-end' }, children: [_jsx(Link, { as: "button", sx: { fontSize: 0, cursor: 'pointer' }, onClick: () => navigate(auditFor(row)), children: "Audit" }), trace && (_jsx(Link, { as: "button", sx: { fontSize: 0, cursor: 'pointer' }, onClick: () => navigate(trace), children: "Trace" }))] }));
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
if (activity.isError) {
|
|
285
|
+
return (_jsx(Box, { sx: { display: 'grid', gap: 3 }, children: _jsx(McpErrorBlankslate, { state: errorState(activity.error, 'MCP activity'), onRetry: () => activity.refetch() }) }));
|
|
286
|
+
}
|
|
287
|
+
const loading = activity.isPending && !data;
|
|
288
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 4, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "MCP" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What the agents connected to your workspace are doing right now." })] })), draws('overview') &&
|
|
289
|
+
(loading ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (_jsxs(Box, { sx: {
|
|
290
|
+
display: 'grid',
|
|
291
|
+
gap: 3,
|
|
292
|
+
gridTemplateColumns: [
|
|
293
|
+
'repeat(2, 1fr)',
|
|
294
|
+
'repeat(3, 1fr)',
|
|
295
|
+
'repeat(6, 1fr)',
|
|
296
|
+
],
|
|
297
|
+
}, children: [_jsx(Count, { label: "Clients connected", value: clients.length }), _jsx(Count, { label: "Sandboxes bound", value: sandboxes.filter(binding => (binding.state ?? 'active') === 'active').length }), _jsx(Count, { label: "Tasks running", value: data?.tasks.length ?? 0 }), _jsx(Count, { label: "Calls today", value: data?.today.calls ?? 0 }), _jsx(Count, { label: "Refusals today", value: data?.today.refusals ?? 0, tone: (data?.today.refusals ?? 0) > 0 ? 'danger' : 'neutral' }), _jsx(Count, { label: "Credits by agents", value: data?.today.credits ?? 0, note: "today" })] }))), draws('agents') &&
|
|
298
|
+
!loading &&
|
|
299
|
+
(clients.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-clients", children: "Connected clients" }), _jsxs(Table.Subtitle, { as: "p", id: "mcp-clients-subtitle", children: [plural(clients.length, 'client'), " with a live grant on your workspace."] }), _jsx(DataTable, { "aria-labelledby": "mcp-clients", "aria-describedby": "mcp-clients-subtitle", data: clients, columns: clientColumns })] })) : (_jsx(PanelEmpty, { icon: PlugIcon, heading: "No client connected", description: "Connect Claude Code, Codex, Cursor, VS Code or any MCP client to your workspace, and it appears here on its first call.", action: {
|
|
300
|
+
label: 'Set up a client',
|
|
301
|
+
onClick: () => navigate(routes.access),
|
|
302
|
+
} }))), draws('runs') && !loading && (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Running now" }), _jsx(PanelEmpty, { icon: PlayIcon, heading: "Nothing is running", description: (data?.tasks.length ?? 0) > 0
|
|
303
|
+
? 'Tasks are running, and the live pane that shows their progress, their cell and their approvals arrives with durable execution in milestone 2.'
|
|
304
|
+
: 'A tool call that outlives its request becomes a task you can watch, cancel and answer. Durable execution arrives in milestone 2; until then every call finishes inside its own request and is listed under Recent calls.' })] })), draws('sandboxes') &&
|
|
305
|
+
!loading &&
|
|
306
|
+
(sandboxes.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-sandboxes", children: "Sandboxes bound" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-sandboxes-subtitle", children: "The sessions your agents execute in. Terminating one ends its open work." }), _jsx(DataTable, { "aria-labelledby": "mcp-sandboxes", "aria-describedby": "mcp-sandboxes-subtitle", data: sandboxes, columns: sandboxColumns })] })) : (_jsx(PanelEmpty, { icon: StackIcon, heading: "No sandbox bound", description: "An agent that launches a sandbox keeps it for the session; it appears here with its provider, its capabilities and what it is doing." }))), draws('calls') &&
|
|
307
|
+
!loading &&
|
|
308
|
+
(calls.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-calls", children: "Recent calls" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-calls-subtitle", children: "The last calls as they landed. Every one of them has an audit row that outlives this page." }), _jsx(Table.Actions, { children: _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsxs(Select, { size: "small", "aria-label": "Filter by client", value: callClient, onChange: event => setCallClient(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every client" }), clients.map(client => (_jsx(Select.Option, { value: client.clientId, children: client.clientName || client.clientId }, client.clientId)))] }), _jsxs(Select, { size: "small", "aria-label": "Filter by tool", value: callTool, onChange: event => setCallTool(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every tool" }), callTools.map(tool => (_jsx(Select.Option, { value: tool, children: tool }, tool)))] }), _jsxs(Select, { size: "small", "aria-label": "Filter by outcome", value: callOutcome, onChange: event => setCallOutcome(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every outcome" }), _jsx(Select.Option, { value: "ok", children: "Succeeded" }), _jsx(Select.Option, { value: "error", children: "Failed" }), _jsx(Select.Option, { value: "refused", children: "Refused" })] })] }) }), filteredCalls.length > 0 ? (_jsx(DataTable, { "aria-labelledby": "mcp-calls", "aria-describedby": "mcp-calls-subtitle", data: filteredCalls, columns: callColumns })) : (_jsx(Box, { sx: {
|
|
309
|
+
p: 4,
|
|
310
|
+
textAlign: 'center',
|
|
311
|
+
color: 'fg.muted',
|
|
312
|
+
fontSize: 1,
|
|
313
|
+
}, children: "No call matches these filters." }))] })) : (_jsx(PanelEmpty, { icon: ChecklistIcon, heading: "No call yet", description: "Ask your agent to list your notebooks. The call, its decision and its outcome land here and in the audit log.", action: {
|
|
314
|
+
label: 'Open the audit log',
|
|
315
|
+
onClick: () => navigate(routes.audit),
|
|
316
|
+
} }))), !loading && (_jsxs(Box, { sx: {
|
|
317
|
+
display: 'flex',
|
|
318
|
+
gap: 3,
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
flexWrap: 'wrap',
|
|
321
|
+
}, children: [_jsx(Button, { size: "small", leadingVisual: TelescopeIcon, onClick: () => navigate(routes.observability), children: "Observability" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Refreshed ", timeAgo(data?.at) || 'just now', ", and again every ten seconds."] })] })), disconnecting && (_jsx(Dialog, { title: "Disconnect this client?", onClose: () => setDisconnecting(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
322
|
+
{
|
|
323
|
+
buttonType: 'default',
|
|
324
|
+
content: 'Keep it',
|
|
325
|
+
onClick: () => setDisconnecting(null),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
buttonType: 'danger',
|
|
329
|
+
content: 'Disconnect',
|
|
330
|
+
onClick: confirmDisconnect,
|
|
331
|
+
disabled: disconnect.isPending,
|
|
332
|
+
},
|
|
333
|
+
], children: _jsxs(Text, { sx: { fontSize: 1 }, children: [disconnecting.clientName || disconnecting.clientId, " loses its grant at once. Its next call is refused; an access token it still holds dies with its own short expiry. Nothing it already did is undone."] }) })), terminating && (_jsx(Dialog, { title: "Terminate this sandbox?", onClose: () => setTerminating(null), returnFocusRef: returnFocusRef, footerButtons: [
|
|
334
|
+
{
|
|
335
|
+
buttonType: 'default',
|
|
336
|
+
content: 'Keep it',
|
|
337
|
+
onClick: () => setTerminating(null),
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
buttonType: 'danger',
|
|
341
|
+
content: 'Terminate',
|
|
342
|
+
onClick: confirmTerminate,
|
|
343
|
+
disabled: terminate.isPending,
|
|
344
|
+
},
|
|
345
|
+
], children: _jsxs(Text, { sx: { fontSize: 1 }, children: ["The runtime behind ", terminating.uid, " is stopped and its session closed. Work the agent has open there ends as", ' ', _jsx("code", { children: "SANDBOX_LOST" }), "; anything already written to a notebook stays."] }) }))] }));
|
|
346
|
+
};
|
|
347
|
+
export default McpDashboard;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access › MCP: how to connect a client, and what your agents may reach.
|
|
3
|
+
*
|
|
4
|
+
* This is the *setup and access* page, not the dashboard. It answers "how
|
|
5
|
+
* do I point my client at Datalayer" first — the endpoint, and one card per
|
|
6
|
+
* client with the command that writes its configuration — and then, in five
|
|
7
|
+
* summaries that link onward, "and what have they got": the agents
|
|
8
|
+
* connected, the runs, the toolsets, the notebooks and sources shared with
|
|
9
|
+
* an agent, and how the calls are behaving.
|
|
10
|
+
*
|
|
11
|
+
* What is happening *right now* is the dashboard's question, and the line
|
|
12
|
+
* at the top of the page opens it rather than answering it twice.
|
|
13
|
+
*
|
|
14
|
+
* Each card degrades to an explanatory empty state: a card with nothing in
|
|
15
|
+
* it says what would put something there.
|
|
16
|
+
*
|
|
17
|
+
* @module views/mcp/McpHome
|
|
18
|
+
*/
|
|
19
|
+
import type { JSX } from 'react';
|
|
20
|
+
import { type McpRoutes } from './types';
|
|
21
|
+
export interface McpHomeProps {
|
|
22
|
+
/** Where this application puts the surfaces this view links to. */
|
|
23
|
+
routes: McpRoutes;
|
|
24
|
+
/** Where the per-client manuals live; each client appends its own name. */
|
|
25
|
+
clientDocsBase?: string;
|
|
26
|
+
showTitle?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to draw the summary cards beneath the setup.
|
|
29
|
+
*
|
|
30
|
+
* Off where they have a page of their own. This page answers "how do I
|
|
31
|
+
* connect one", and six cards about what connected agents are *doing* sat
|
|
32
|
+
* under that question answering a different one — a reader looking for a
|
|
33
|
+
* command scrolled past them, and a reader looking for the numbers had to
|
|
34
|
+
* know they were filed under Access.
|
|
35
|
+
*/
|
|
36
|
+
showSummaries?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to draw the setup half — the endpoint and the per-client cards.
|
|
39
|
+
*
|
|
40
|
+
* Off where only the summaries are wanted, which is how the MCP overview
|
|
41
|
+
* mounts this: the two halves answer different questions and now live on
|
|
42
|
+
* different tabs, and one component drawing either keeps the wording and
|
|
43
|
+
* the data of each in one place rather than two that drift.
|
|
44
|
+
*/
|
|
45
|
+
showSetup?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Open a documentation page beside this view rather than navigating to it.
|
|
48
|
+
*
|
|
49
|
+
* A client's "Configuration and authorization" opens the panel the
|
|
50
|
+
* application has rather than navigating: reading how to configure Cursor
|
|
51
|
+
* should not mean losing the endpoint, the other clients and the command
|
|
52
|
+
* that were on screen — the page you are reading about is the page you
|
|
53
|
+
* just left.
|
|
54
|
+
*
|
|
55
|
+
* Required, and a callback rather than the store itself: this view knows a
|
|
56
|
+
* documentation *path* and nothing about how an application shows one. An
|
|
57
|
+
* optional one would mean a second behaviour nobody chose, kept alive by
|
|
58
|
+
* whichever caller forgot to pass it.
|
|
59
|
+
*/
|
|
60
|
+
onOpenDocs: (path: string) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The clients the documentation carries a page for, in the order it lists
|
|
64
|
+
* them, with the name `datalayer mcp setup` takes.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* The command that writes a client's configuration.
|
|
68
|
+
*
|
|
69
|
+
* One function rather than the string written wherever it is shown. It is
|
|
70
|
+
* shown in two places that must not disagree — the Access view and the
|
|
71
|
+
* documentation page for each client — and a command a reader copies from
|
|
72
|
+
* the documentation that differs by a word from the one the interface shows
|
|
73
|
+
* is worse than either alone: it is two answers to "what do I run", and no
|
|
74
|
+
* way to tell which is current.
|
|
75
|
+
*/
|
|
76
|
+
export declare const mcpSetupCommand: (client: string) => string;
|
|
77
|
+
export declare const MCP_CLIENTS: {
|
|
78
|
+
name: string;
|
|
79
|
+
setup: string;
|
|
80
|
+
}[];
|
|
81
|
+
/**
|
|
82
|
+
* A line of text with the button that copies it.
|
|
83
|
+
*
|
|
84
|
+
* Exported so the documentation shows a command the same way this view
|
|
85
|
+
* does: a reader who has seen one and then the other should not have to
|
|
86
|
+
* work out whether they are the same affordance.
|
|
87
|
+
*/
|
|
88
|
+
export declare const Copyable: ({ text }: {
|
|
89
|
+
text: string;
|
|
90
|
+
}) => JSX.Element;
|
|
91
|
+
export declare const McpHome: ({ routes, clientDocsBase, showTitle, showSummaries, showSetup, onOpenDocs, }: McpHomeProps) => JSX.Element;
|
|
92
|
+
export default McpHome;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button, Heading, IconButton, Link, Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { CheckIcon, CopyIcon, KeyIcon, PlugIcon, PulseIcon, ShareIcon, TelescopeIcon, ToolsIcon, } from '@primer/octicons-react';
|
|
6
|
+
import { useConnectedAgents, useMcpActivity, useMcpMetrics, useTasks, } from '../../hooks/useMcp.js';
|
|
7
|
+
import { useNavigate } from '../../hooks/index.js';
|
|
8
|
+
import { useCoreStore, useIAMStore } from '../../state/index.js';
|
|
9
|
+
import { plural, timeAgo } from './format.js';
|
|
10
|
+
/**
|
|
11
|
+
* The clients the documentation carries a page for, in the order it lists
|
|
12
|
+
* them, with the name `datalayer mcp setup` takes.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The command that writes a client's configuration.
|
|
16
|
+
*
|
|
17
|
+
* One function rather than the string written wherever it is shown. It is
|
|
18
|
+
* shown in two places that must not disagree — the Access view and the
|
|
19
|
+
* documentation page for each client — and a command a reader copies from
|
|
20
|
+
* the documentation that differs by a word from the one the interface shows
|
|
21
|
+
* is worse than either alone: it is two answers to "what do I run", and no
|
|
22
|
+
* way to tell which is current.
|
|
23
|
+
*/
|
|
24
|
+
export const mcpSetupCommand = (client) => `datalayer mcp setup ${client}`;
|
|
25
|
+
export const MCP_CLIENTS = [
|
|
26
|
+
{ name: 'Claude Code', setup: 'claude-code' },
|
|
27
|
+
{ name: 'Claude Desktop', setup: 'claude-desktop' },
|
|
28
|
+
{ name: 'Codex', setup: 'codex' },
|
|
29
|
+
{ name: 'Cursor', setup: 'cursor' },
|
|
30
|
+
{ name: 'VS Code', setup: 'vscode' },
|
|
31
|
+
{ name: 'Windsurf', setup: 'windsurf' },
|
|
32
|
+
{ name: 'Cline', setup: 'cline' },
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* A line of text with the button that copies it.
|
|
36
|
+
*
|
|
37
|
+
* Exported so the documentation shows a command the same way this view
|
|
38
|
+
* does: a reader who has seen one and then the other should not have to
|
|
39
|
+
* work out whether they are the same affordance.
|
|
40
|
+
*/
|
|
41
|
+
export const Copyable = ({ text }) => {
|
|
42
|
+
const [copied, setCopied] = useState(false);
|
|
43
|
+
return (_jsxs(Box, { sx: {
|
|
44
|
+
display: 'flex',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
gap: 2,
|
|
47
|
+
p: 2,
|
|
48
|
+
bg: 'canvas.inset',
|
|
49
|
+
borderRadius: 2,
|
|
50
|
+
minWidth: 0,
|
|
51
|
+
}, children: [_jsx(Text, { sx: {
|
|
52
|
+
fontFamily: 'mono',
|
|
53
|
+
fontSize: 0,
|
|
54
|
+
flex: 1,
|
|
55
|
+
minWidth: 0,
|
|
56
|
+
overflow: 'hidden',
|
|
57
|
+
textOverflow: 'ellipsis',
|
|
58
|
+
whiteSpace: 'nowrap',
|
|
59
|
+
}, children: text }), _jsx(IconButton, { size: "small", variant: "invisible", "aria-label": copied ? 'Copied' : `Copy ${text}`, icon: copied ? CheckIcon : CopyIcon, onClick: () => {
|
|
60
|
+
navigator.clipboard?.writeText(text);
|
|
61
|
+
setCopied(true);
|
|
62
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
63
|
+
} })] }));
|
|
64
|
+
};
|
|
65
|
+
/** One of the five summaries: a heading, what it holds, and the way in. */
|
|
66
|
+
const Summary = ({ icon: Icon, title, children, action, }) => (_jsxs(Box, { sx: {
|
|
67
|
+
p: 3,
|
|
68
|
+
border: '1px solid',
|
|
69
|
+
borderColor: 'border.default',
|
|
70
|
+
borderRadius: 2,
|
|
71
|
+
display: 'grid',
|
|
72
|
+
gap: 2,
|
|
73
|
+
alignContent: 'start',
|
|
74
|
+
minWidth: 0,
|
|
75
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Icon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: title })] }), _jsx(Box, { sx: { fontSize: 0, color: 'fg.muted', display: 'grid', gap: 1 }, children: children }), action && (_jsx(Box, { children: _jsx(Link, { as: "button", sx: { fontSize: 0, cursor: 'pointer' }, onClick: action.onClick, children: action.label }) }))] }));
|
|
76
|
+
export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitle = true, showSummaries = true, showSetup = true, onOpenDocs, }) => {
|
|
77
|
+
const navigate = useNavigate();
|
|
78
|
+
const endpoint = useCoreStore(state => state.configuration.jupyterMcpServerUrl);
|
|
79
|
+
// Whether there is a token, never what it is: this page shows an address
|
|
80
|
+
// and a status, and a credential belongs on neither.
|
|
81
|
+
const signedIn = Boolean(useIAMStore(state => state.token));
|
|
82
|
+
const agents = useConnectedAgents();
|
|
83
|
+
const tasks = useTasks({ limit: 5 });
|
|
84
|
+
const activity = useMcpActivity();
|
|
85
|
+
const metrics = useMcpMetrics();
|
|
86
|
+
const connected = agents.data ?? [];
|
|
87
|
+
const runs = tasks.data?.items ?? [];
|
|
88
|
+
const live = activity.data?.clients.length ?? 0;
|
|
89
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 4, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "MCP" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Connect your agents to your notebooks, data and sandboxes through the Model Context Protocol." })] })), showSetup && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
90
|
+
display: 'flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
gap: 3,
|
|
93
|
+
flexWrap: 'wrap',
|
|
94
|
+
p: 3,
|
|
95
|
+
borderRadius: 2,
|
|
96
|
+
bg: 'canvas.subtle',
|
|
97
|
+
border: '1px solid',
|
|
98
|
+
borderColor: 'border.default',
|
|
99
|
+
}, children: [_jsx(Text, { sx: { fontSize: 1 }, children: live > 0
|
|
100
|
+
? `${plural(live, 'client')} connected right now.`
|
|
101
|
+
: 'No client is connected right now.' }), _jsx(Button, { size: "small", leadingVisual: PulseIcon, onClick: () => navigate(routes.dashboard), children: "What is going on" })] }), _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "The endpoint" }), _jsx(Copyable, { text: endpoint || 'https://mcp.datalayer.run/mcp' }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(KeyIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: signedIn
|
|
102
|
+
? 'You are signed in, so a client you set up from here authorizes through OAuth without a shared token.'
|
|
103
|
+
: 'Sign in to authorize a client through OAuth; without it a client can only use a personal access token.' })] })] }), _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Set up a client" }), _jsx(Box, { sx: {
|
|
104
|
+
display: 'grid',
|
|
105
|
+
gap: 3,
|
|
106
|
+
gridTemplateColumns: [
|
|
107
|
+
'1fr',
|
|
108
|
+
'repeat(2, 1fr)',
|
|
109
|
+
'repeat(3, 1fr)',
|
|
110
|
+
],
|
|
111
|
+
}, children: MCP_CLIENTS.map(client => (_jsxs(Box, { sx: {
|
|
112
|
+
p: 3,
|
|
113
|
+
border: '1px solid',
|
|
114
|
+
borderColor: 'border.default',
|
|
115
|
+
borderRadius: 2,
|
|
116
|
+
display: 'grid',
|
|
117
|
+
gap: 2,
|
|
118
|
+
minWidth: 0,
|
|
119
|
+
}, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: client.name }), _jsx(Copyable, { text: mcpSetupCommand(client.setup) }), _jsx(Link, { href: `${clientDocsBase}/${client.setup}`, sx: { fontSize: 0, cursor: 'pointer', textAlign: 'left' }, onClick: event => {
|
|
120
|
+
// A modified click is the reader asking for a new tab or
|
|
121
|
+
// window, and `href` is real so it still leads to the
|
|
122
|
+
// full page — for that, for the status bar and for the
|
|
123
|
+
// context menu.
|
|
124
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
onOpenDocs(`${clientDocsBase}/${client.setup}`);
|
|
129
|
+
}, children: "Configuration and authorization" })] }, client.setup))) })] })] })), showSummaries && (_jsxs(Box, { sx: {
|
|
130
|
+
display: 'grid',
|
|
131
|
+
gap: 3,
|
|
132
|
+
gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'],
|
|
133
|
+
}, children: [_jsx(Summary, { icon: PlugIcon, title: "Agents", action: {
|
|
134
|
+
label: 'Connected agents',
|
|
135
|
+
onClick: () => navigate(routes.agents),
|
|
136
|
+
}, children: connected.length === 0 ? (_jsx(Text, { children: "No agent has been authorized yet. Set up a client above and approve the scopes it asks for." })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [plural(connected.length, 'agent'), " authorized."] }), connected.slice(0, 3).map(agent => (_jsxs(Text, { children: [agent.clientName || agent.clientId, " \u2014", ' ', agent.lastUsedAt
|
|
137
|
+
? `used ${timeAgo(agent.lastUsedAt)}`
|
|
138
|
+
: 'never used'] }, agent.uid)))] })) }), _jsx(Summary, { icon: PulseIcon, title: "Runs", action: { label: 'All runs', onClick: () => navigate(routes.runs) }, children: runs.length === 0 ? (_jsx(Text, { children: "Nothing has run yet. A tool call that outlives its request becomes a run you can watch and cancel." })) : (runs.map(task => (_jsxs(Text, { children: [task.tool, " \u2014 ", task.status, " ", timeAgo(task.lastUpdatedAt)] }, task.uid)))) }), _jsxs(Summary, { icon: TelescopeIcon, title: "Observability", action: {
|
|
139
|
+
label: 'Runs and metrics',
|
|
140
|
+
onClick: () => navigate(routes.observability),
|
|
141
|
+
}, children: [_jsx(Text, { children: metrics.data?.slis.samples.calls
|
|
142
|
+
? `${plural(metrics.data.slis.samples.calls, 'call')} measured.`
|
|
143
|
+
: 'No call measured yet.' }), _jsx(Text, { children: metrics.data?.slis.p95CallDurationMs
|
|
144
|
+
? `p95 ${Math.round(metrics.data.slis.p95CallDurationMs)}ms`
|
|
145
|
+
: 'Latency not measured yet.' }), _jsx(Text, { children: metrics.data?.slis.availability === null ||
|
|
146
|
+
metrics.data?.slis.availability === undefined
|
|
147
|
+
? 'Error rate not measured yet.'
|
|
148
|
+
: `${((1 - metrics.data.slis.availability) * 100).toFixed(1)}% of calls failed.` })] }), _jsx(Summary, { icon: ToolsIcon, title: "Tools", children: _jsx(Text, { children: "The toolsets an agent may execute \u2014 the Contents sources you have enabled for it, their tools, their approval policy and their health \u2014 are managed here once toolset bindings ship." }) }), _jsx(Summary, { icon: ShareIcon, title: "Reach", children: _jsx(Text, { children: "Which notebooks and sources an agent can actually reach, as a list you can read at a glance, arrives with sharing to agents as principals." }) }), _jsx(Summary, { icon: KeyIcon, title: "Policies", action: {
|
|
149
|
+
label: 'What applies',
|
|
150
|
+
onClick: () => navigate(routes.policies),
|
|
151
|
+
}, children: _jsx(Text, { children: "What your agents are allowed to do, and which layer decided each rule." }) })] }))] }));
|
|
152
|
+
};
|
|
153
|
+
export default McpHome;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a call ran, as opposed to whether it was allowed.
|
|
3
|
+
*
|
|
4
|
+
* Two panes over the Datalayer OTEL service, read directly with the
|
|
5
|
+
* caller's token: **Runs**, the span tree of one call across the gateway,
|
|
6
|
+
* the policy check and the worker, with the durations that matter called
|
|
7
|
+
* out; and **Metrics**, the four service level indicators and the metric
|
|
8
|
+
* catalog for the caller's agents or, for an owner, the organization.
|
|
9
|
+
*
|
|
10
|
+
* There is no gateway route behind this and no second store: the gateway
|
|
11
|
+
* exports spans, metrics and logs through the collector the platform
|
|
12
|
+
* already runs, and this page queries that. Which is exactly why the page
|
|
13
|
+
* says on itself that it is telemetry — short retention, sampled, no
|
|
14
|
+
* security claim. The audit log is the trail that answers "was this
|
|
15
|
+
* allowed", and every pane links to it.
|
|
16
|
+
*
|
|
17
|
+
* Milestone 1 draws the two panes over synchronous calls. The workflow
|
|
18
|
+
* steps and sandbox spans of a durable task, and the live stream of calls
|
|
19
|
+
* as they land, arrive with milestone 2.
|
|
20
|
+
*
|
|
21
|
+
* @module views/mcp/McpObservability
|
|
22
|
+
*/
|
|
23
|
+
import type { JSX } from 'react';
|
|
24
|
+
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
25
|
+
export type McpObservabilityPane = 'runs' | 'metrics';
|
|
26
|
+
export interface McpObservabilityProps {
|
|
27
|
+
/** The application's words for a failed request. */
|
|
28
|
+
errorState: McpErrorStateFn;
|
|
29
|
+
/** Where this application puts the surfaces this view links to. */
|
|
30
|
+
routes: McpRoutes;
|
|
31
|
+
/** Which pane is open; the address owns it. */
|
|
32
|
+
pane: McpObservabilityPane;
|
|
33
|
+
onPaneChange: (pane: McpObservabilityPane) => void;
|
|
34
|
+
/** The run to open, when the address names one. */
|
|
35
|
+
taskUid?: string;
|
|
36
|
+
/** The trace to open — what a synchronous call leaves behind. */
|
|
37
|
+
traceId?: string;
|
|
38
|
+
/** Asked to write a new selection into the address. */
|
|
39
|
+
onSelect: (selection: {
|
|
40
|
+
task?: string;
|
|
41
|
+
trace?: string;
|
|
42
|
+
}) => void;
|
|
43
|
+
/** One agent's reading, rather than every agent of the caller. */
|
|
44
|
+
agent?: string;
|
|
45
|
+
/** An owner's reading of one organization. */
|
|
46
|
+
org?: string;
|
|
47
|
+
showTitle?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare const McpObservability: ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle, }: McpObservabilityProps) => JSX.Element;
|
|
50
|
+
export default McpObservability;
|