@datalayer/core 1.1.66 → 1.2.10
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 +41 -18
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/constants.js +1 -1
- package/lib/api/contents/attachments.js +3 -7
- package/lib/api/contents/datasets.d.ts +48 -0
- package/lib/api/contents/datasets.js +44 -7
- package/lib/api/contents/runtimeMounts.js +1 -1
- package/lib/api/contents/sandboxUid.d.ts +13 -45
- package/lib/api/contents/sandboxUid.js +14 -65
- package/lib/api/evals/client.d.ts +402 -0
- package/lib/api/evals/client.js +359 -0
- package/lib/api/evals/index.d.ts +10 -0
- package/lib/api/evals/index.js +18 -0
- package/lib/api/evals/request.d.ts +43 -0
- package/lib/api/evals/request.js +67 -0
- package/lib/api/evals/status.d.ts +27 -0
- package/lib/api/evals/status.js +122 -0
- package/lib/api/evals/types.d.ts +1335 -0
- package/lib/api/evals/types.js +5 -0
- package/lib/api/iam/connectedAgents.d.ts +29 -0
- package/lib/api/iam/connectedAgents.js +35 -0
- package/lib/api/iam/identityProviders.d.ts +76 -0
- package/lib/api/iam/identityProviders.js +187 -0
- package/lib/api/iam/index.d.ts +1 -0
- package/lib/api/iam/index.js +1 -0
- package/lib/api/iam/mcpPolicy.d.ts +102 -1
- package/lib/api/iam/mcpPolicy.js +38 -2
- package/lib/api/iam/profile.d.ts +17 -1
- package/lib/api/iam/profile.js +27 -0
- package/lib/api/iam/scimTokens.d.ts +79 -0
- package/lib/api/iam/scimTokens.js +141 -0
- package/lib/api/iam/secrets.js +9 -21
- package/lib/api/iam/trials.d.ts +38 -0
- package/lib/api/iam/trials.js +66 -0
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.js +4 -0
- package/lib/api/mcp/observability.d.ts +25 -9
- package/lib/api/mcp/observability.js +63 -50
- package/lib/api/orchestration/client.d.ts +53 -0
- package/lib/api/orchestration/client.js +105 -0
- package/lib/api/orchestration/events.d.ts +88 -0
- package/lib/api/orchestration/events.js +245 -0
- package/lib/api/orchestration/generated.d.ts +693 -0
- package/lib/api/orchestration/generated.js +687 -0
- package/lib/api/orchestration/index.d.ts +30 -0
- package/lib/api/orchestration/index.js +34 -0
- package/lib/api/orchestration/lifecycle.d.ts +39 -0
- package/lib/api/orchestration/lifecycle.js +57 -0
- package/lib/api/orchestration/measures.d.ts +55 -0
- package/lib/api/orchestration/measures.js +99 -0
- package/lib/api/otel/dashboards.d.ts +56 -0
- package/lib/api/otel/dashboards.js +50 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/api/otel/metrics.d.ts +16 -0
- package/lib/api/otel/metrics.js +15 -0
- package/lib/api/scheduler/client.d.ts +32 -0
- package/lib/api/scheduler/client.js +42 -0
- package/lib/api/scheduler/index.d.ts +8 -0
- package/lib/api/scheduler/index.js +16 -0
- package/lib/api/scheduler/request.d.ts +28 -0
- package/lib/api/scheduler/request.js +50 -0
- package/lib/api/scheduler/types.d.ts +88 -0
- package/lib/api/scheduler/types.js +5 -0
- package/lib/api/spacer/comments.d.ts +97 -0
- package/lib/api/spacer/comments.js +45 -0
- package/lib/api/spacer/index.d.ts +9 -0
- package/lib/api/spacer/index.js +17 -0
- package/lib/api/spacer/notebooks.d.ts +33 -0
- package/lib/api/spacer/notebooks.js +39 -0
- package/lib/api/spacer/request.d.ts +25 -0
- package/lib/api/spacer/request.js +46 -0
- package/lib/api/spacer/spaces.d.ts +68 -0
- package/lib/api/spacer/spaces.js +45 -0
- package/lib/api/utils/validation.d.ts +4 -4
- package/lib/api/utils/validation.js +10 -7
- package/lib/client/constants.d.ts +1 -0
- package/lib/client/constants.js +1 -0
- package/lib/components/animation/AnimatedText.d.ts +1 -44
- package/lib/components/animation/AnimatedText.js +6 -122
- package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
- package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
- package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
- package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
- package/lib/components/anonymous/index.d.ts +7 -0
- package/lib/components/anonymous/index.js +15 -0
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +15 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
- package/lib/components/billing/BillingEntitySelect.js +49 -10
- package/lib/components/billing/eligibility.d.ts +22 -0
- package/lib/components/billing/eligibility.js +27 -0
- package/lib/components/billing/index.d.ts +1 -0
- package/lib/components/billing/index.js +1 -0
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
- package/lib/components/display/DatalayerBox.d.ts +2 -3
- package/lib/components/display/DatalayerBox.js +1 -1
- package/lib/components/display/NavLink.js +3 -2
- package/lib/components/labels/StatusLabels.d.ts +2 -10
- package/lib/components/labels/StatusLabels.js +2 -26
- package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
- package/lib/components/principal/PrincipalAppearance.js +232 -174
- package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
- package/lib/components/principal/PrincipalAvatar.js +2 -2
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/screencapture/Screencapture.js +46 -3
- package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
- package/lib/components/sharing/ShareAccessComponent.js +110 -78
- package/lib/components/sharing/SharingEditor.d.ts +5 -1
- package/lib/components/sharing/SharingEditor.js +9 -4
- package/lib/components/sharing/index.d.ts +1 -0
- package/lib/components/sharing/index.js +1 -0
- package/lib/components/sharing/sandboxSharing.d.ts +26 -0
- package/lib/components/sharing/sandboxSharing.js +41 -0
- package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
- package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
- package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
- package/lib/components/spaces/SpaceDisplay.js +92 -0
- package/lib/components/spaces/index.d.ts +4 -0
- package/lib/components/spaces/index.js +7 -0
- package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
- package/lib/components/spaces/spaceDisplayModel.js +170 -0
- package/lib/hooks/cacheConverters.d.ts +0 -7
- package/lib/hooks/cacheConverters.js +0 -25
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useBillingEntityStore.js +3 -0
- package/lib/hooks/useCache.d.ts +168 -25
- package/lib/hooks/useCache.js +1424 -307
- package/lib/hooks/useContents.d.ts +22 -1
- package/lib/hooks/useContents.js +90 -21
- package/lib/hooks/useMcp.d.ts +87 -1
- package/lib/hooks/useMcp.js +247 -12
- package/lib/hooks/useNavigate.d.ts +41 -3
- package/lib/hooks/useNavigate.js +106 -52
- package/lib/hooks/usePrincipalCacheStore.js +3 -0
- package/lib/hooks/usePrincipalStore.js +3 -0
- package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
- package/lib/hooks/useSpaceCacheStore.js +85 -0
- package/lib/models/CreditsDTO.d.ts +6 -5
- package/lib/models/CreditsDTO.js +7 -5
- package/lib/models/McpBinding.d.ts +6 -0
- package/lib/models/Organization.d.ts +69 -0
- package/lib/models/Organization.js +96 -0
- package/lib/models/Secret.d.ts +1 -2
- package/lib/models/Secret.js +2 -18
- package/lib/models/StartedBy.d.ts +34 -0
- package/lib/models/StartedBy.js +36 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +3 -0
- package/lib/models/UserSettings.d.ts +13 -0
- package/lib/models/UserSettings.js +9 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
- package/lib/navigation/components.js +2 -3
- package/lib/routes/publicPaths.js +4 -0
- package/lib/state/index.d.ts +1 -0
- package/lib/state/index.js +1 -0
- package/lib/state/sessionEnd.d.ts +55 -0
- package/lib/state/sessionEnd.js +162 -0
- package/lib/state/substates/CoreState.js +3 -3
- package/lib/state/substates/IAMState.js +7 -0
- package/lib/state/substates/LayoutState.js +12 -0
- package/lib/state/substates/ProfileState.d.ts +1 -0
- package/lib/state/substates/ProfileState.js +1 -0
- package/lib/utils/Lazy.d.ts +20 -0
- package/lib/utils/Lazy.js +32 -6
- package/lib/utils/Screencapture.js +3 -0
- package/lib/utils/WithSuspense.d.ts +11 -1
- package/lib/utils/WithSuspense.js +11 -1
- package/lib/views/mcp/AdmittedClients.d.ts +44 -0
- package/lib/views/mcp/AdmittedClients.js +59 -0
- package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
- package/lib/views/mcp/ApprovalQueue.js +95 -0
- package/lib/views/mcp/ConnectedAgents.js +38 -3
- package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
- package/lib/views/mcp/EnterpriseConsole.js +23 -1
- package/lib/views/mcp/IdentityProviders.d.ts +33 -0
- package/lib/views/mcp/IdentityProviders.js +299 -0
- package/lib/views/mcp/McpDashboard.js +90 -7
- package/lib/views/mcp/McpObservability.d.ts +30 -1
- package/lib/views/mcp/McpObservability.js +105 -19
- package/lib/views/mcp/NotebookRuns.d.ts +39 -0
- package/lib/views/mcp/NotebookRuns.js +44 -0
- package/lib/views/mcp/OrganizationPolicy.js +2 -1
- package/lib/views/mcp/PersonalPolicy.js +2 -1
- package/lib/views/mcp/PolicyForm.d.ts +17 -1
- package/lib/views/mcp/PolicyForm.js +25 -3
- package/lib/views/mcp/PolicyHistory.d.ts +13 -0
- package/lib/views/mcp/PolicyHistory.js +14 -1
- package/lib/views/mcp/RunDetail.d.ts +87 -0
- package/lib/views/mcp/RunDetail.js +186 -0
- package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
- package/lib/views/mcp/ScimProvisioning.js +173 -0
- package/lib/views/mcp/TeamPolicies.js +66 -5
- package/lib/views/mcp/ToolAccess.d.ts +60 -0
- package/lib/views/mcp/ToolAccess.js +101 -0
- package/lib/views/mcp/TraceTimeline.d.ts +81 -0
- package/lib/views/mcp/TraceTimeline.js +132 -0
- package/lib/views/mcp/index.d.ts +5 -0
- package/lib/views/mcp/index.js +5 -0
- package/lib/views/otel/simpleAuthStore.js +3 -0
- package/lib/views/secrets/SecretEdit.js +3 -2
- package/lib/views/secrets/SecretNew.js +5 -1
- package/package.json +269 -264
- package/scripts/generate-mcp-types.py +1 -1
- package/scripts/generate-orchestration-types.py +920 -0
|
@@ -5,12 +5,21 @@ import { Blankslate } from '@primer/react/experimental';
|
|
|
5
5
|
import { Box } from '@datalayer/primer-addons';
|
|
6
6
|
import { SearchIcon, TelescopeIcon } from '@primer/octicons-react';
|
|
7
7
|
import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
8
|
-
import { useMcpMetrics, useMcpTrace, useRunTrace } from '../../hooks/useMcp.js';
|
|
8
|
+
import { useMcpMetrics, useMcpTrace, useMcpWorkflowsHealth, useRunTrace, useTasks, } from '../../hooks/useMcp.js';
|
|
9
|
+
import { TraceTimeline } from './TraceTimeline.js';
|
|
9
10
|
import { useNavigate } from '../../hooks/index.js';
|
|
10
11
|
import { MCP_METRIC_CATALOG } from '../../api/mcp/index.js';
|
|
11
|
-
import { durationLabel } from './format.js';
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
import { durationLabel, timeAgo } from './format.js';
|
|
13
|
+
/**
|
|
14
|
+
* What part of the platform a span belongs to, from its name.
|
|
15
|
+
*
|
|
16
|
+
* By prefix rather than by service, because one service emits spans for more
|
|
17
|
+
* than one stage — the gateway does its own policy check — and because a
|
|
18
|
+
* durable run's spans arrive from a process the gateway never talks to. A
|
|
19
|
+
* name nothing recognises gets no label rather than a wrong one: the span is
|
|
20
|
+
* still drawn, still timed, and still named.
|
|
21
|
+
*/
|
|
22
|
+
export const stageOf = (name) => {
|
|
14
23
|
if (name.startsWith('mcp.policy')) {
|
|
15
24
|
return 'Policy';
|
|
16
25
|
}
|
|
@@ -23,10 +32,16 @@ const STAGE = (name) => {
|
|
|
23
32
|
if (name.startsWith('durable.')) {
|
|
24
33
|
return 'Workflow';
|
|
25
34
|
}
|
|
35
|
+
// The Runtimes service is where a sandbox is actually started and a kernel
|
|
36
|
+
// actually executes; its spans are the ones that explain a run that spent
|
|
37
|
+
// its eleven minutes somewhere other than this gateway.
|
|
38
|
+
if (name.startsWith('runtimes.')) {
|
|
39
|
+
return 'Runtime';
|
|
40
|
+
}
|
|
26
41
|
return '';
|
|
27
42
|
};
|
|
28
43
|
const SpanRow = ({ node, depth, }) => {
|
|
29
|
-
const stage =
|
|
44
|
+
const stage = stageOf(node.span.span_name);
|
|
30
45
|
const failed = (node.span.status_code ?? '').toUpperCase() === 'ERROR';
|
|
31
46
|
return (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
32
47
|
display: 'flex',
|
|
@@ -61,6 +76,46 @@ const Sli = ({ label, value, note, }) => (_jsxs(Box, { sx: {
|
|
|
61
76
|
borderRadius: 2,
|
|
62
77
|
minWidth: 0,
|
|
63
78
|
}, children: [_jsx(Text, { sx: { display: 'block', fontSize: 3, fontWeight: 'bold' }, 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' }, children: note }))] }));
|
|
79
|
+
/**
|
|
80
|
+
* What the durable engine's own report amounts to, in one line.
|
|
81
|
+
*
|
|
82
|
+
* Three states, not two. "Unreachable" is deliberately not "unhealthy": the
|
|
83
|
+
* gateway answers `/operations/workflows` even when it cannot reach the
|
|
84
|
+
* engine, precisely so an operator can tell "the engine says it is unwell"
|
|
85
|
+
* from "nobody could ask it" — the first is a problem with the engine, the
|
|
86
|
+
* second is usually a problem with the network between them, and sending
|
|
87
|
+
* somebody to the wrong one costs an hour.
|
|
88
|
+
*
|
|
89
|
+
* A healthy engine with a backlog is still healthy, and says so with the
|
|
90
|
+
* backlog beside it: a queue is a queue, not a fault.
|
|
91
|
+
*/
|
|
92
|
+
export const engineLook = (health, failed) => {
|
|
93
|
+
if (failed) {
|
|
94
|
+
return {
|
|
95
|
+
label: 'Unreachable',
|
|
96
|
+
variant: 'secondary',
|
|
97
|
+
note: 'The gateway could not ask the durable engine how it is. That is a question about the network between them, not about the engine.',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!health) {
|
|
101
|
+
return { label: 'Asking…', variant: 'secondary', note: '' };
|
|
102
|
+
}
|
|
103
|
+
const backlog = (health.queues ?? []).reduce((total, queue) => total + (queue.backlog ?? 0), 0);
|
|
104
|
+
if (!health.healthy) {
|
|
105
|
+
return {
|
|
106
|
+
label: 'Unhealthy',
|
|
107
|
+
variant: 'danger',
|
|
108
|
+
note: health.detail || 'The engine reports itself unhealthy.',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
label: 'Healthy',
|
|
113
|
+
variant: backlog > 0 ? 'attention' : 'success',
|
|
114
|
+
note: backlog > 0
|
|
115
|
+
? `${backlog} ${backlog === 1 ? 'run is' : 'runs are'} waiting to be picked up.`
|
|
116
|
+
: 'Nothing is waiting to be picked up.',
|
|
117
|
+
};
|
|
118
|
+
};
|
|
64
119
|
const percentLabel = (value) => value === null || value === undefined
|
|
65
120
|
? 'Not measured'
|
|
66
121
|
: `${(value * 100).toFixed(1)}%`;
|
|
@@ -75,8 +130,12 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
|
|
|
75
130
|
});
|
|
76
131
|
const run = taskUid ? byTask : byTrace;
|
|
77
132
|
const metrics = useMcpMetrics({ agent, org });
|
|
133
|
+
// Only while the Live pane is open. A pane nobody is looking at should not
|
|
134
|
+
// be asking the durable engine how it is every thirty seconds.
|
|
135
|
+
const engine = useMcpWorkflowsHealth({ enabled: pane === 'live' });
|
|
136
|
+
const live = useTasks({ status: 'working' }, { enabled: pane === 'live' });
|
|
78
137
|
const telemetryNotice = (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["This is telemetry: spans and metrics are sampled, kept for a short time and meant for understanding how a call ran. It is not the record of what was allowed \u2014", ' ', _jsx(Text, { as: "span", sx: { color: 'accent.fg', cursor: 'pointer' }, onClick: () => navigate(routes.audit), children: "the audit log" }), ' ', "is, and it is kept per plan and never changed."] }));
|
|
79
|
-
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Observability" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "How the calls of your agents ran: where the time went, and how the service is behaving." })] })), _jsxs(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: [_jsx(SegmentedControl.Button, { selected: pane === 'runs', onClick: () => onPaneChange('runs'), children: "Runs" }), _jsx(SegmentedControl.Button, { selected: pane === 'metrics', onClick: () => onPaneChange('metrics'), children: "Metrics" })] }), telemetryNotice, pane === 'runs' && (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(Box, { sx: {
|
|
138
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Observability" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "How the calls of your agents ran: where the time went, and how the service is behaving." })] })), _jsxs(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: [_jsx(SegmentedControl.Button, { selected: pane === 'runs', onClick: () => onPaneChange('runs'), children: "Runs" }), _jsx(SegmentedControl.Button, { selected: pane === 'live', onClick: () => onPaneChange('live'), children: "Live" }), _jsx(SegmentedControl.Button, { selected: pane === 'metrics', onClick: () => onPaneChange('metrics'), children: "Metrics" })] }), telemetryNotice, pane === 'runs' && (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(Box, { sx: {
|
|
80
139
|
display: 'flex',
|
|
81
140
|
gap: 2,
|
|
82
141
|
alignItems: 'center',
|
|
@@ -97,12 +156,46 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
|
|
|
97
156
|
gap: 3,
|
|
98
157
|
alignItems: 'center',
|
|
99
158
|
flexWrap: 'wrap',
|
|
100
|
-
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: run.data?.traceId }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [run.data?.spans.length, " spans"] }), _jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?trace=${encodeURIComponent(run.data?.traceId ?? '')}`), children: "The audit row" })] }), _jsx(Box, { sx: {
|
|
159
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: run.data?.traceId }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [run.data?.spans.length, " spans"] }), _jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?trace=${encodeURIComponent(run.data?.traceId ?? '')}`), children: "The audit row" })] }), _jsx(TraceTimeline, { tree: run.data?.tree ?? [] }), _jsx(Box, { sx: {
|
|
101
160
|
border: '1px solid',
|
|
102
161
|
borderColor: 'border.default',
|
|
103
162
|
borderRadius: 2,
|
|
104
163
|
overflowX: 'auto',
|
|
105
|
-
}, children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })
|
|
164
|
+
}, children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })] }))] })), pane === 'live' && (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [(() => {
|
|
165
|
+
const look = engineLook(engine.data, engine.isError);
|
|
166
|
+
const queues = engine.data?.queues ?? [];
|
|
167
|
+
return (_jsxs(Box, { sx: {
|
|
168
|
+
border: '1px solid',
|
|
169
|
+
borderColor: 'border.default',
|
|
170
|
+
borderRadius: 2,
|
|
171
|
+
p: 3,
|
|
172
|
+
display: 'grid',
|
|
173
|
+
gap: 2,
|
|
174
|
+
}, children: [_jsxs(Box, { sx: {
|
|
175
|
+
display: 'flex',
|
|
176
|
+
gap: 2,
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
flexWrap: 'wrap',
|
|
179
|
+
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "The durable engine" }), _jsx(Label, { size: "small", variant: look.variant, children: look.label }), engine.data?.engine && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: engine.data.engine }))] }), look.note && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note })), queues.length > 0 && (_jsx(Box, { sx: { display: 'grid', gap: 1 }, children: queues.map(queue => (_jsxs(Box, { sx: {
|
|
180
|
+
display: 'flex',
|
|
181
|
+
gap: 2,
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
fontSize: 0,
|
|
184
|
+
}, children: [_jsx(Text, { sx: { fontFamily: 'mono' }, children: queue.name }), _jsxs(Text, { sx: { color: 'fg.muted' }, children: [queue.backlog, " waiting"] }), typeof queue.oldestWaitSeconds === 'number' && (_jsxs(Text, { sx: { color: 'fg.muted' }, children: ["oldest", ' ', durationLabel(queue.oldestWaitSeconds * 1000)] }))] }, queue.name))) }))] }));
|
|
185
|
+
})(), _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "Running now" }), live.isError ? (_jsx(McpErrorBlankslate, { state: errorState(live.error, 'The running calls'), onRetry: () => live.refetch() })) : live.isPending ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 4 }, children: _jsx(Spinner, {}) })) : (live.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing of yours is running. A call that finishes inside its own request never appears here \u2014 it is over before this pane could draw it \u2014 and is found by its trace in Runs." })) : ((live.data?.items ?? []).map(task => (_jsxs(Box, { sx: {
|
|
186
|
+
display: 'flex',
|
|
187
|
+
gap: 2,
|
|
188
|
+
alignItems: 'center',
|
|
189
|
+
flexWrap: 'wrap',
|
|
190
|
+
py: 1,
|
|
191
|
+
borderBottom: '1px solid',
|
|
192
|
+
borderColor: 'border.muted',
|
|
193
|
+
}, children: [_jsx(Label, { size: "small", variant: "success", children: "Working" }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' }), _jsx(Text, { sx: {
|
|
194
|
+
fontSize: 0,
|
|
195
|
+
fontFamily: 'mono',
|
|
196
|
+
color: 'accent.fg',
|
|
197
|
+
cursor: 'pointer',
|
|
198
|
+
}, onClick: () => onSelect({ task: task.uid }), children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["started ", timeAgo(task.createdAt)] })] }, task.uid))))] })] })), pane === 'metrics' && (_jsx(Box, { sx: { display: 'grid', gap: 3 }, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
106
199
|
display: 'grid',
|
|
107
200
|
gap: 3,
|
|
108
201
|
gridTemplateColumns: [
|
|
@@ -123,8 +216,7 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
|
|
|
123
216
|
borderRadius: 2,
|
|
124
217
|
overflowX: 'auto',
|
|
125
218
|
}, children: MCP_METRIC_CATALOG.map(name => {
|
|
126
|
-
const
|
|
127
|
-
const last = points[points.length - 1];
|
|
219
|
+
const reporting = metrics.data?.reporting.includes(name) ?? false;
|
|
128
220
|
return (_jsxs(Box, { sx: {
|
|
129
221
|
display: 'flex',
|
|
130
222
|
alignItems: 'center',
|
|
@@ -133,16 +225,10 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
|
|
|
133
225
|
px: 2,
|
|
134
226
|
borderBottom: '1px solid',
|
|
135
227
|
borderColor: 'border.muted',
|
|
136
|
-
}, children: [_jsx(Text, { sx: { fontSize: 0, fontFamily: 'mono', flex: 1 }, children: name }), _jsx(Text, { sx: {
|
|
137
|
-
? 'no point'
|
|
138
|
-
: `${points.length} points` }), _jsx(Text, { sx: {
|
|
228
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, fontFamily: 'mono', flex: 1 }, children: name }), _jsx(Text, { sx: {
|
|
139
229
|
fontSize: 0,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
textAlign: 'right',
|
|
143
|
-
}, children: last
|
|
144
|
-
? `${last.value}${last.unit ? ` ${last.unit}` : ''}`
|
|
145
|
-
: '—' })] }, name));
|
|
230
|
+
color: reporting ? 'success.fg' : 'fg.muted',
|
|
231
|
+
}, children: reporting ? 'reporting' : 'no point' })] }, name));
|
|
146
232
|
}) }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Metrics carry no user, agent or organization label by design, so a per-agent reading is computed from the request spans, which do.", metrics.data?.spans.length
|
|
147
233
|
? ` ${metrics.data.spans.length} spans read.`
|
|
148
234
|
: ''] })] })) }))] }));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runs of one notebook, beside the notebook.
|
|
3
|
+
*
|
|
4
|
+
* A run outlives the call that started it, so the agent that asked is
|
|
5
|
+
* usually gone — and the person who notices is the one looking at the
|
|
6
|
+
* notebook. `/mcp/runs` lists every run they own; this answers the narrower
|
|
7
|
+
* question they actually have in front of them: what is happening to *this*
|
|
8
|
+
* notebook, right now.
|
|
9
|
+
*
|
|
10
|
+
* It lists rather than acts. Cancelling and answering live on the run's own
|
|
11
|
+
* page, next to the output that gives the question its context; a second set
|
|
12
|
+
* of buttons here would be a second implementation of the same two calls,
|
|
13
|
+
* and a panel beside a notebook is a poor place to decide to stop something
|
|
14
|
+
* you cannot see the output of.
|
|
15
|
+
*
|
|
16
|
+
* @module views/mcp/NotebookRuns
|
|
17
|
+
*/
|
|
18
|
+
import type { JSX } from 'react';
|
|
19
|
+
import type { McpTask } from '../../models/McpTask';
|
|
20
|
+
import { type McpRoutes } from './types';
|
|
21
|
+
/**
|
|
22
|
+
* The runs worth a person's attention first.
|
|
23
|
+
*
|
|
24
|
+
* A notebook accumulates finished runs and keeps only one or two live ones,
|
|
25
|
+
* so ordering by time alone buries the live ones under yesterday's. Waiting
|
|
26
|
+
* first, then working, then whatever finished most recently — the same order
|
|
27
|
+
* the Runs panel uses, for the same reason.
|
|
28
|
+
*/
|
|
29
|
+
export declare const orderedRuns: (tasks: McpTask[]) => McpTask[];
|
|
30
|
+
export interface NotebookRunsProps {
|
|
31
|
+
/** The notebook whose runs these are. */
|
|
32
|
+
notebookUid: string;
|
|
33
|
+
/** Where this application puts the surfaces this view links to. */
|
|
34
|
+
routes: McpRoutes;
|
|
35
|
+
/** How many to draw; a rail, not a page. */
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare const NotebookRuns: ({ notebookUid, routes, limit, }: NotebookRunsProps) => JSX.Element;
|
|
39
|
+
export default NotebookRuns;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Heading, Label, Link, RelativeTime, Spinner, Text, } from '@primer/react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
import { useNotebookTasks } from '../../hooks/useMcp.js';
|
|
5
|
+
import { RUN_STATUS_LOOK, isRunOver } from './RunDetail.js';
|
|
6
|
+
/**
|
|
7
|
+
* The runs worth a person's attention first.
|
|
8
|
+
*
|
|
9
|
+
* A notebook accumulates finished runs and keeps only one or two live ones,
|
|
10
|
+
* so ordering by time alone buries the live ones under yesterday's. Waiting
|
|
11
|
+
* first, then working, then whatever finished most recently — the same order
|
|
12
|
+
* the Runs panel uses, for the same reason.
|
|
13
|
+
*/
|
|
14
|
+
export const orderedRuns = (tasks) => {
|
|
15
|
+
const rank = (task) => {
|
|
16
|
+
if (task.status === 'input_required') {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
return isRunOver(task.status) ? 2 : 1;
|
|
20
|
+
};
|
|
21
|
+
return [...tasks].sort((left, right) => rank(left) - rank(right) ||
|
|
22
|
+
right.lastUpdatedAt.localeCompare(left.lastUpdatedAt));
|
|
23
|
+
};
|
|
24
|
+
export const NotebookRuns = ({ notebookUid, routes, limit = 20, }) => {
|
|
25
|
+
const runs = useNotebookTasks(notebookUid);
|
|
26
|
+
const tasks = orderedRuns(runs.data?.items ?? []).slice(0, limit);
|
|
27
|
+
if (runs.isLoading) {
|
|
28
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 3 }, children: _jsx(Spinner, { size: "small" }) }));
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs(Box, { sx: { display: 'grid', gap: 2, minWidth: 0 }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "Runs" }), tasks.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing has run on this notebook yet. A tool call that outlives its request appears here while it runs." })) : (tasks.map(task => {
|
|
31
|
+
const look = RUN_STATUS_LOOK[task.status];
|
|
32
|
+
return (_jsxs(Box, { sx: {
|
|
33
|
+
display: 'grid',
|
|
34
|
+
gap: 1,
|
|
35
|
+
p: 2,
|
|
36
|
+
borderRadius: 2,
|
|
37
|
+
border: '1px solid',
|
|
38
|
+
borderColor: task.status === 'input_required'
|
|
39
|
+
? 'attention.emphasis'
|
|
40
|
+
: 'border.default',
|
|
41
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' })] }), _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsxs(Link, { href: `${routes.runs}/${encodeURIComponent(task.uid)}`, sx: { fontFamily: 'mono', fontSize: 0 }, children: [task.uid.slice(0, 16), "\u2026"] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: task.lastUpdatedAt }) })] })] }, task.uid));
|
|
42
|
+
}))] }));
|
|
43
|
+
};
|
|
44
|
+
export default NotebookRuns;
|
|
@@ -7,6 +7,7 @@ import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
|
7
7
|
import { useDeleteMcpPolicyLayer, useMcpPolicyLayer, useSetMcpPolicyLayer, } from '../../hooks/useMcp.js';
|
|
8
8
|
import { useToast } from '../../hooks/index.js';
|
|
9
9
|
import { McpPolicyConflict } from '../../api/iam/mcpPolicy.js';
|
|
10
|
+
import { AdmittedClients } from './AdmittedClients.js';
|
|
10
11
|
import { PolicyHistory } from './PolicyHistory.js';
|
|
11
12
|
import { EMPTY_POLICY_DRAFT, PolicyForm, draftOf, policyChanged, rulesFrom, } from './PolicyForm.js';
|
|
12
13
|
export const OrganizationPolicy = ({ errorState, orgUid, readOnly = false, showTitle = true, }) => {
|
|
@@ -84,7 +85,7 @@ export const OrganizationPolicy = ({ errorState, orgUid, readOnly = false, showT
|
|
|
84
85
|
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0, maxWidth: '52rem' }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Organization Policy" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What this organization narrows for every agent acting in it. Each layer only narrows \u2014 a team, or a person, can be stricter than this and never looser." })] })), conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { size: "small", onClick: () => {
|
|
85
86
|
setConflict('');
|
|
86
87
|
layer.refetch();
|
|
87
|
-
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, disabled: readOnly }), !readOnly && (_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save policy" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: "danger", onClick: () => setRemoving(true), children: "Remove policy" }) }))] })), _jsxs(Box, { sx: {
|
|
88
|
+
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, disabled: readOnly, belowAllowedClients: _jsx(AdmittedClients, { scope: "organization", subjectUid: orgUid, edited: draft.allowedClients !== stored.allowedClients }) }), !readOnly && (_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save policy" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: "danger", onClick: () => setRemoving(true), children: "Remove policy" }) }))] })), _jsxs(Box, { sx: {
|
|
88
89
|
borderTop: '1px solid',
|
|
89
90
|
borderColor: 'border.muted',
|
|
90
91
|
pt: 3,
|
|
@@ -6,6 +6,7 @@ import { McpErrorBlankslate } from '../../components/mcp/index.js';
|
|
|
6
6
|
import { useDeleteMcpPolicyLayer, useMcpPolicyLayer, useSetMcpPolicyLayer, } from '../../hooks/useMcp.js';
|
|
7
7
|
import { useToast } from '../../hooks/index.js';
|
|
8
8
|
import { McpPolicyConflict, } from '../../api/iam/mcpPolicy.js';
|
|
9
|
+
import { AdmittedClients } from './AdmittedClients.js';
|
|
9
10
|
import { EMPTY_POLICY_DRAFT, PolicyForm, draftOf, policyChanged, rulesFrom, } from './PolicyForm.js';
|
|
10
11
|
/** A list rule already denied elsewhere, as a sentence naming who. */
|
|
11
12
|
export const deniedElsewhere = (typed, organizations) => {
|
|
@@ -113,7 +114,7 @@ export const PersonalPolicy = ({ errorState, userUid, organizations = [], showTi
|
|
|
113
114
|
return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0, maxWidth: '52rem' }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Your MCP policy" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: ["Rules for your own agents. These can only make things", ' ', _jsx("strong", { children: "stricter" }), " \u2014 where you belong to an organization, its rules apply underneath and nothing here can loosen them."] })] })), conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { size: "small", onClick: () => {
|
|
114
115
|
setConflict('');
|
|
115
116
|
layer.refetch();
|
|
116
|
-
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, notes: notes }), _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
|
|
117
|
+
}, children: "Read it again" }) })] })), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsx(PolicyForm, { draft: draft, onChange: set, notes: notes, belowAllowedClients: _jsx(AdmittedClients, { scope: "personal", subjectUid: userUid, edited: draft.allowedClients !== stored.allowedClients }) }), _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
|
|
117
118
|
onSuccess: () => enqueueToast('Removed. You narrow nothing of your own.', {
|
|
118
119
|
variant: 'success',
|
|
119
120
|
}),
|
|
@@ -24,6 +24,11 @@ export type PolicyDraft = {
|
|
|
24
24
|
maxCallsPerMinute: string;
|
|
25
25
|
maxCreditsPerDay: string;
|
|
26
26
|
maxConcurrentSandboxes: string;
|
|
27
|
+
gpuHoursPerMonth: string;
|
|
28
|
+
storageBytes: string;
|
|
29
|
+
sessionMaxHours: string;
|
|
30
|
+
/** A checkbox, held as `'true'`/`''` so every field stays a string. */
|
|
31
|
+
ssoAdmitsWithoutConsent: string;
|
|
27
32
|
};
|
|
28
33
|
export declare const EMPTY_POLICY_DRAFT: PolicyDraft;
|
|
29
34
|
/** A stored list as the textarea shows it: one entry a line. */
|
|
@@ -62,6 +67,17 @@ export interface PolicyFormProps {
|
|
|
62
67
|
* needs to know while their hands are on it, not after they save.
|
|
63
68
|
*/
|
|
64
69
|
notes?: Partial<Record<keyof PolicyDraft, React.ReactNode>>;
|
|
70
|
+
/**
|
|
71
|
+
* Rendered under the admitted-clients field: what the stored entries
|
|
72
|
+
* actually name.
|
|
73
|
+
*
|
|
74
|
+
* Passed in rather than fetched here, because it needs the scope and
|
|
75
|
+
* subject this form is editing and the form is deliberately presentational
|
|
76
|
+
* — organization, team and personal all render it, and each knows its own
|
|
77
|
+
* layer. Not a `note`: those are one warning line in attention colour, and
|
|
78
|
+
* this is a list.
|
|
79
|
+
*/
|
|
80
|
+
belowAllowedClients?: React.ReactNode;
|
|
65
81
|
}
|
|
66
|
-
export declare const PolicyForm: ({ draft, onChange, disabled, notes, }: PolicyFormProps) => JSX.Element;
|
|
82
|
+
export declare const PolicyForm: ({ draft, onChange, disabled, notes, belowAllowedClients, }: PolicyFormProps) => JSX.Element;
|
|
67
83
|
export default PolicyForm;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { FormControl, TextInput, Textarea, Text } from '@primer/react';
|
|
2
|
+
import { FormControl, TextInput, Textarea, Text, ToggleSwitch, } from '@primer/react';
|
|
3
3
|
import { Box } from '@datalayer/primer-addons';
|
|
4
4
|
export const EMPTY_POLICY_DRAFT = {
|
|
5
5
|
toolDenylist: '',
|
|
@@ -8,6 +8,10 @@ export const EMPTY_POLICY_DRAFT = {
|
|
|
8
8
|
maxCallsPerMinute: '',
|
|
9
9
|
maxCreditsPerDay: '',
|
|
10
10
|
maxConcurrentSandboxes: '',
|
|
11
|
+
gpuHoursPerMonth: '',
|
|
12
|
+
storageBytes: '',
|
|
13
|
+
sessionMaxHours: '',
|
|
14
|
+
ssoAdmitsWithoutConsent: '',
|
|
11
15
|
};
|
|
12
16
|
/** A stored list as the textarea shows it: one entry a line. */
|
|
13
17
|
export const linesOf = (value) => (value ?? []).join('\n');
|
|
@@ -43,6 +47,10 @@ export const draftOf = (rules) => ({
|
|
|
43
47
|
maxCallsPerMinute: textOf(rules?.maxCallsPerMinute),
|
|
44
48
|
maxCreditsPerDay: textOf(rules?.maxCreditsPerDay),
|
|
45
49
|
maxConcurrentSandboxes: textOf(rules?.maxConcurrentSandboxes),
|
|
50
|
+
gpuHoursPerMonth: textOf(rules?.gpuHoursPerMonth),
|
|
51
|
+
storageBytes: textOf(rules?.storageBytes),
|
|
52
|
+
sessionMaxHours: textOf(rules?.sessionMaxHours),
|
|
53
|
+
ssoAdmitsWithoutConsent: rules?.ssoAdmitsWithoutConsent ? 'true' : '',
|
|
46
54
|
});
|
|
47
55
|
/**
|
|
48
56
|
* The draft as it will be stored, or the reason it will not be.
|
|
@@ -56,6 +64,9 @@ export const rulesFrom = (draft) => {
|
|
|
56
64
|
['maxCallsPerMinute', 'Calls per minute'],
|
|
57
65
|
['maxCreditsPerDay', 'Credits per day'],
|
|
58
66
|
['maxConcurrentSandboxes', 'Sandboxes at once'],
|
|
67
|
+
['gpuHoursPerMonth', 'GPU-hours per month'],
|
|
68
|
+
['storageBytes', 'Storage'],
|
|
69
|
+
['sessionMaxHours', 'Session at most'],
|
|
59
70
|
];
|
|
60
71
|
for (const [key, label] of numbers) {
|
|
61
72
|
const typed = draft[key].trim();
|
|
@@ -79,13 +90,24 @@ export const rulesFrom = (draft) => {
|
|
|
79
90
|
maxCallsPerMinute: numberFrom(draft.maxCallsPerMinute),
|
|
80
91
|
maxCreditsPerDay: numberFrom(draft.maxCreditsPerDay),
|
|
81
92
|
maxConcurrentSandboxes: numberFrom(draft.maxConcurrentSandboxes),
|
|
93
|
+
gpuHoursPerMonth: numberFrom(draft.gpuHoursPerMonth),
|
|
94
|
+
storageBytes: numberFrom(draft.storageBytes),
|
|
95
|
+
sessionMaxHours: numberFrom(draft.sessionMaxHours),
|
|
96
|
+
// `undefined` rather than `false` when it is off, so an organization that
|
|
97
|
+
// never touched it stores no rule at all — the same distinction the lists
|
|
98
|
+
// draw between "not set" and "set to nothing".
|
|
99
|
+
ssoAdmitsWithoutConsent: draft.ssoAdmitsWithoutConsent === 'true' ? true : undefined,
|
|
82
100
|
};
|
|
83
101
|
};
|
|
84
102
|
export const policyChanged = (a, b) => Object.keys(EMPTY_POLICY_DRAFT).some(key => a[key] !== b[key]);
|
|
85
103
|
const Note = ({ children, }) => children ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'attention.fg', mt: 1, mb: 0 }, children: children })) : null;
|
|
86
|
-
export const PolicyForm = ({ draft, onChange, disabled = false, notes = {}, }) => (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Denied tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolDenylist, onChange: event => onChange('toolDenylist', event.target.value), placeholder: 'execute_cell\ndelete_file' }), _jsx(FormControl.Caption, { children: "One a line. A denial is added to every other layer\u2019s and can never be lifted by one." }), _jsx(Note, { children: notes.toolDenylist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Permitted tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolAllowlist, onChange: event => onChange('toolAllowlist', event.target.value), placeholder: 'read_cell\nlist_notebooks' }), _jsxs(FormControl.Caption, { children: ["One a line, and everything else is refused.", ' ', _jsx("strong", { children: "Leave it empty to set no allowlist" }), " \u2014 empty means \u201CI have not set one\u201D, not \u201Cnothing is permitted\u201D, because the second would stop every agent at once."] }), _jsx(Note, { children: notes.toolAllowlist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Admitted clients" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.allowedClients, onChange: event => onChange('allowedClients', event.target.value), placeholder: 'https://claude.ai/.well-known/mcp-client.json\ncursor.com' }), _jsx(FormControl.Caption, { children: "A client\u2019s document URL, or just its hostname. Empty is not an allowlist, for the same reason as above." }), _jsx(Note, { children: notes.allowedClients })] }), _jsxs(Box, { sx: {
|
|
104
|
+
export const PolicyForm = ({ draft, onChange, disabled = false, notes = {}, belowAllowedClients, }) => (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Denied tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolDenylist, onChange: event => onChange('toolDenylist', event.target.value), placeholder: 'execute_cell\ndelete_file' }), _jsx(FormControl.Caption, { children: "One a line. A denial is added to every other layer\u2019s and can never be lifted by one." }), _jsx(Note, { children: notes.toolDenylist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Permitted tools" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.toolAllowlist, onChange: event => onChange('toolAllowlist', event.target.value), placeholder: 'read_cell\nlist_notebooks' }), _jsxs(FormControl.Caption, { children: ["One a line, and everything else is refused.", ' ', _jsx("strong", { children: "Leave it empty to set no allowlist" }), " \u2014 empty means \u201CI have not set one\u201D, not \u201Cnothing is permitted\u201D, because the second would stop every agent at once."] }), _jsx(Note, { children: notes.toolAllowlist })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Admitted clients" }), _jsx(Textarea, { block: true, rows: 3, disabled: disabled, value: draft.allowedClients, onChange: event => onChange('allowedClients', event.target.value), placeholder: 'https://claude.ai/.well-known/mcp-client.json\ncursor.com' }), _jsx(FormControl.Caption, { children: "A client\u2019s document URL, or just its hostname. Empty is not an allowlist, for the same reason as above." }), _jsx(Note, { children: notes.allowedClients }), belowAllowedClients && _jsx(Box, { sx: { mt: 2 }, children: belowAllowedClients })] }), _jsxs(Box, { sx: {
|
|
87
105
|
display: 'grid',
|
|
88
106
|
gap: 3,
|
|
89
107
|
gridTemplateColumns: ['1fr', '1fr 1fr 1fr'],
|
|
90
|
-
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Calls per minute" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCallsPerMinute, onChange: event => onChange('maxCallsPerMinute', event.target.value) }), _jsx(FormControl.Caption, { children: "Empty for no limit." }), _jsx(Note, { children: notes.maxCallsPerMinute })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Credits per day" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCreditsPerDay, onChange: event => onChange('maxCreditsPerDay', event.target.value) }), _jsx(FormControl.Caption, { children: "Checked before a launch, never mid-session." }), _jsx(Note, { children: notes.maxCreditsPerDay })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Sandboxes at once" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxConcurrentSandboxes, onChange: event => onChange('maxConcurrentSandboxes', event.target.value) }), _jsx(FormControl.Caption, { children: "Counted across the layer that set it." }), _jsx(Note, { children: notes.maxConcurrentSandboxes })] })] })] }))
|
|
108
|
+
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Calls per minute" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCallsPerMinute, onChange: event => onChange('maxCallsPerMinute', event.target.value) }), _jsx(FormControl.Caption, { children: "Empty for no limit." }), _jsx(Note, { children: notes.maxCallsPerMinute })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Credits per day" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxCreditsPerDay, onChange: event => onChange('maxCreditsPerDay', event.target.value) }), _jsx(FormControl.Caption, { children: "Checked before a launch, never mid-session." }), _jsx(Note, { children: notes.maxCreditsPerDay })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Sandboxes at once" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.maxConcurrentSandboxes, onChange: event => onChange('maxConcurrentSandboxes', event.target.value) }), _jsx(FormControl.Caption, { children: "Counted across the layer that set it." }), _jsx(Note, { children: notes.maxConcurrentSandboxes })] })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "GPU-hours per month" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: "any", disabled: disabled, value: draft.gpuHoursPerMonth, onChange: event => onChange('gpuHoursPerMonth', event.target.value) }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. Refuses a launch on an environment that", ' ', _jsx("strong", { children: "has a GPU" }), " \u2014 a CPU sandbox is never affected, whatever this reads. Fractions are allowed: half an hour on one card is 0.5.", _jsx("br", {}), "Over the ", _jsx("strong", { children: "last 30 days" }), ", not the calendar month, so a limit cannot be spent twice across a reset \u2014 which means it will not line up with an invoice period.", ' ', _jsx("strong", { children: "Every GPU-hour billed to this scope counts, not only your agents\u2019" }), ' ', "\u2014 a notebook somebody ran by hand is in this number too."] }), _jsx(Note, { children: notes.gpuHoursPerMonth })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Storage" }), _jsx(TextInput, { block: true, type: "number", min: 0, step: 1, disabled: disabled, value: draft.storageBytes, onChange: event => onChange('storageBytes', event.target.value), trailingVisual: "bytes" }), _jsxs(FormControl.Caption, { children: ["Empty for no limit. In ", _jsx("strong", { children: "bytes" }), " \u2014 1 GB is 1073741824, 1 TB is 1099511627776. A number whose unit is implied is one somebody eventually reads as the other, and this is compared against a figure reported in bytes.", _jsx("br", {}), "Checked on ", _jsx("strong", { children: "every" }), " launch, not only one that attaches content: every sandbox can write.", ' ', _jsx("strong", { children: "Every kept version counts" }), " \u2014 an object\u2019s size is its newest version\u2019s, so a hundred versions of a 1\u00A0GB file are 100\u00A0GB, and deleting the newest frees none of the rest."] }), _jsx(Note, { children: notes.storageBytes })] }), _jsxs(Box, { sx: {
|
|
109
|
+
display: 'grid',
|
|
110
|
+
gap: 3,
|
|
111
|
+
gridTemplateColumns: ['1fr', '1fr 1fr'],
|
|
112
|
+
}, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Session at most" }), _jsx(TextInput, { block: true, type: "number", min: 1, disabled: disabled, value: draft.sessionMaxHours, onChange: event => onChange('sessionMaxHours', event.target.value) }), _jsx(FormControl.Caption, { children: "Hours before a person signs in and consents again. It bites at the refresh: a token already minted lives out its own short life, and the connection ends when the client next tries to extend it. Empty for no limit." }), _jsx(Note, { children: notes.sessionMaxHours })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your directory may stand in for the consent screen" }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mt: 1 }, children: [_jsx(ToggleSwitch, { size: "small", checked: draft.ssoAdmitsWithoutConsent === 'true', disabled: disabled, "aria-labelledby": "sso-admits-without-consent-label", onClick: () => onChange('ssoAdmitsWithoutConsent', draft.ssoAdmitsWithoutConsent === 'true' ? '' : 'true') }), _jsx(Text, { id: "sso-admits-without-consent-label", sx: { fontSize: 1 }, children: "Skip it for clients you already admit" })] }), _jsxs(FormControl.Caption, { children: ["Only for somebody who signed in through one of", ' ', _jsx("strong", { children: "your own" }), " enabled identity providers, and only for a client named in ", _jsx("strong", { children: "Admitted clients" }), " above \u2014 which must not be empty. Every other session still sees the screen. This does not require anyone to sign in through your directory; it only says the screen adds nothing when they did."] }), _jsx(Note, { children: notes.ssoAdmitsWithoutConsent })] })] })] }));
|
|
91
113
|
export default PolicyForm;
|
|
@@ -39,6 +39,19 @@ export interface PolicyHistoryProps {
|
|
|
39
39
|
subjectUid?: string;
|
|
40
40
|
limit?: number;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* What a change was made *through*, or `''` for somebody's own browser.
|
|
44
|
+
*
|
|
45
|
+
* The row names the person either way — an agent acts with exactly the
|
|
46
|
+
* access of whoever authorized it, so the person is accountable for what it
|
|
47
|
+
* did. What the row must not do is stop there: this drawer exists for the
|
|
48
|
+
* administrator who finds a denylist they did not write, and a row naming
|
|
49
|
+
* only them tells them they wrote it.
|
|
50
|
+
*
|
|
51
|
+
* The agent's own uid wins over the client id. A service agent is a named
|
|
52
|
+
* thing somebody can go and look at; a client id is the software it ran in.
|
|
53
|
+
*/
|
|
54
|
+
export declare const actedThrough: (event: McpAuditEvent) => string;
|
|
42
55
|
/** What one row says about a change, in the words of the page it is on. */
|
|
43
56
|
export declare const describeChange: (event: McpAuditEvent) => string;
|
|
44
57
|
export declare const PolicyHistory: ({ orgUid, subjectUid, limit, }: PolicyHistoryProps) => JSX.Element;
|
|
@@ -8,6 +8,19 @@ export const POLICY_AUDIT_METHODS = [
|
|
|
8
8
|
'mcp.policy.set',
|
|
9
9
|
'mcp.policy.remove',
|
|
10
10
|
];
|
|
11
|
+
/**
|
|
12
|
+
* What a change was made *through*, or `''` for somebody's own browser.
|
|
13
|
+
*
|
|
14
|
+
* The row names the person either way — an agent acts with exactly the
|
|
15
|
+
* access of whoever authorized it, so the person is accountable for what it
|
|
16
|
+
* did. What the row must not do is stop there: this drawer exists for the
|
|
17
|
+
* administrator who finds a denylist they did not write, and a row naming
|
|
18
|
+
* only them tells them they wrote it.
|
|
19
|
+
*
|
|
20
|
+
* The agent's own uid wins over the client id. A service agent is a named
|
|
21
|
+
* thing somebody can go and look at; a client id is the software it ran in.
|
|
22
|
+
*/
|
|
23
|
+
export const actedThrough = (event) => String(event.agentUid || event.clientId || '');
|
|
11
24
|
/** What one row says about a change, in the words of the page it is on. */
|
|
12
25
|
export const describeChange = (event) => {
|
|
13
26
|
const info = (event.clientInfo ?? {});
|
|
@@ -57,6 +70,6 @@ export const PolicyHistory = ({ orgUid, subjectUid, limit = 20, }) => {
|
|
|
57
70
|
alignItems: 'baseline',
|
|
58
71
|
flexWrap: 'wrap',
|
|
59
72
|
fontSize: 0,
|
|
60
|
-
}, children: [_jsx(Text, { sx: { color: 'fg.muted', whiteSpace: 'nowrap' }, children: _jsx(RelativeTime, { datetime: event.at }) }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: event.userUid || 'unknown' }), _jsx(Text, { children: describeChange(event) }), String(event.clientInfo?.version ?? '') && (_jsxs(Label, { size: "small", variant: "secondary", children: ["v", String(event.clientInfo?.version ?? '')] }))] }, event.uid))) }));
|
|
73
|
+
}, children: [_jsx(Text, { sx: { color: 'fg.muted', whiteSpace: 'nowrap' }, children: _jsx(RelativeTime, { datetime: event.at }) }), _jsx(Text, { sx: { fontWeight: 'semibold' }, children: event.userUid || 'unknown' }), actedThrough(event) && (_jsxs(Label, { size: "small", variant: "attention", children: ["through ", actedThrough(event)] })), _jsx(Text, { children: describeChange(event) }), String(event.clientInfo?.version ?? '') && (_jsxs(Label, { size: "small", variant: "secondary", children: ["v", String(event.clientInfo?.version ?? '')] }))] }, event.uid))) }));
|
|
61
74
|
};
|
|
62
75
|
export default PolicyHistory;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One run: what it is doing, what it has printed, and the two things a
|
|
3
|
+
* person can do about it.
|
|
4
|
+
*
|
|
5
|
+
* A task is a tool call that outlived the request that asked for it, so the
|
|
6
|
+
* client that started it is usually gone. This is where somebody comes back
|
|
7
|
+
* to it — from another device, another session, or after closing the
|
|
8
|
+
* notebook — which is why it reads from the task record rather than from
|
|
9
|
+
* anything the caller held.
|
|
10
|
+
*
|
|
11
|
+
* **Cancelling stops the work, not just the record of it.** The gateway
|
|
12
|
+
* interrupts the kernel and then gives the tool a moment to hand back what
|
|
13
|
+
* the cell printed, so a run cancelled at minute nine of ten arrives here
|
|
14
|
+
* with its nine minutes of output and the `KeyboardInterrupt` that ended it.
|
|
15
|
+
* That is the reason the outputs are drawn for a cancelled run at all, and
|
|
16
|
+
* the reason the button says "Cancel" rather than "Stop watching".
|
|
17
|
+
*
|
|
18
|
+
* **`input_required` is a run asking, not a run idling.** It has stopped and
|
|
19
|
+
* will not move until somebody answers, so it is drawn as an open question
|
|
20
|
+
* with the answer box beside it rather than as another status label.
|
|
21
|
+
*
|
|
22
|
+
* Live while it runs: `useTaskEvents` patches the task's cache entry from
|
|
23
|
+
* the server's event stream, and `useTask` polls underneath as the fallback
|
|
24
|
+
* for a proxy that will not hold a stream open.
|
|
25
|
+
*
|
|
26
|
+
* @module views/mcp/RunDetail
|
|
27
|
+
*/
|
|
28
|
+
import type { JSX } from 'react';
|
|
29
|
+
import type { McpTask, McpTaskStatus } from '../../models/McpTask';
|
|
30
|
+
import type { McpSpanNode } from '../../api/mcp/observability';
|
|
31
|
+
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
32
|
+
/** How a run's status is drawn. Shared with the runs list. */
|
|
33
|
+
export declare const RUN_STATUS_LOOK: Record<McpTaskStatus, {
|
|
34
|
+
label: string;
|
|
35
|
+
variant: 'success' | 'attention' | 'secondary' | 'danger';
|
|
36
|
+
}>;
|
|
37
|
+
/** Whether the run has stopped for good. */
|
|
38
|
+
export declare const isRunOver: (status: McpTaskStatus) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Everything the run has printed, as text.
|
|
41
|
+
*
|
|
42
|
+
* Two shapes reach here and both are the run's output: the streamed
|
|
43
|
+
* `outputs`, and the `result` a finished tool returned. A cancelled run has
|
|
44
|
+
* the first and usually the second — the partial output is promoted to the
|
|
45
|
+
* result when it stops — so they are concatenated rather than chosen
|
|
46
|
+
* between, and de-duplicated by text so a promoted partial is not printed
|
|
47
|
+
* twice.
|
|
48
|
+
*/
|
|
49
|
+
export declare const outputTextOf: (task: McpTask) => string;
|
|
50
|
+
/**
|
|
51
|
+
* A run for the documentation to draw: cancelled mid-flight, holding the
|
|
52
|
+
* lines it printed before it stopped.
|
|
53
|
+
*
|
|
54
|
+
* Cancelled rather than completed on purpose. It is the state that shows
|
|
55
|
+
* both halves of what this view is for — the output a run keeps when it is
|
|
56
|
+
* stopped, and the reason it stopped — and it is the one a reader is most
|
|
57
|
+
* likely to be looking at when they open the documentation.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SAMPLE_RUN: McpTask;
|
|
60
|
+
/**
|
|
61
|
+
* The spans that run left behind, for the documentation to draw.
|
|
62
|
+
*
|
|
63
|
+
* The shape is the one a reader needs to recognise: the gateway's twenty-two
|
|
64
|
+
* seconds are almost entirely the sandbox's, and the durable step between
|
|
65
|
+
* them explains the gap. A timeline drawn from an empty trace would teach
|
|
66
|
+
* nobody what the timeline is for.
|
|
67
|
+
*/
|
|
68
|
+
export declare const SAMPLE_TRACE: McpSpanNode[];
|
|
69
|
+
export interface RunDetailProps {
|
|
70
|
+
/** The run to draw. */
|
|
71
|
+
taskUid: string;
|
|
72
|
+
/** The application's words for a failed request. */
|
|
73
|
+
errorState: McpErrorStateFn;
|
|
74
|
+
/** Where this application puts the surfaces this view links to. */
|
|
75
|
+
routes: McpRoutes;
|
|
76
|
+
/**
|
|
77
|
+
* Draw `SAMPLE_RUN` instead of asking the server, for documentation.
|
|
78
|
+
*
|
|
79
|
+
* The real component with fixed data, rather than a screenshot or a copy:
|
|
80
|
+
* a change to how a run is drawn reaches the documentation by itself, and
|
|
81
|
+
* a change that breaks it breaks the build rather than going unnoticed
|
|
82
|
+
* until somebody compares a picture.
|
|
83
|
+
*/
|
|
84
|
+
mock?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare const RunDetail: ({ taskUid, errorState, routes, mock, }: RunDetailProps) => JSX.Element;
|
|
87
|
+
export default RunDetail;
|