@datalayer/core 1.1.66 → 1.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +43 -20
  2. package/lib/api/constants.d.ts +1 -1
  3. package/lib/api/constants.js +1 -1
  4. package/lib/api/contents/attachments.js +3 -7
  5. package/lib/api/contents/datasets.d.ts +48 -0
  6. package/lib/api/contents/datasets.js +44 -7
  7. package/lib/api/contents/runtimeMounts.js +1 -1
  8. package/lib/api/contents/sandboxUid.d.ts +13 -45
  9. package/lib/api/contents/sandboxUid.js +14 -65
  10. package/lib/api/evals/client.d.ts +402 -0
  11. package/lib/api/evals/client.js +359 -0
  12. package/lib/api/evals/index.d.ts +10 -0
  13. package/lib/api/evals/index.js +18 -0
  14. package/lib/api/evals/request.d.ts +43 -0
  15. package/lib/api/evals/request.js +67 -0
  16. package/lib/api/evals/status.d.ts +27 -0
  17. package/lib/api/evals/status.js +122 -0
  18. package/lib/api/evals/types.d.ts +1335 -0
  19. package/lib/api/evals/types.js +5 -0
  20. package/lib/api/iam/authentication.js +6 -4
  21. package/lib/api/iam/connectedAgents.d.ts +29 -0
  22. package/lib/api/iam/connectedAgents.js +35 -0
  23. package/lib/api/iam/healthz.js +2 -2
  24. package/lib/api/iam/identityProviders.d.ts +76 -0
  25. package/lib/api/iam/identityProviders.js +187 -0
  26. package/lib/api/iam/index.d.ts +1 -0
  27. package/lib/api/iam/index.js +1 -0
  28. package/lib/api/iam/mcpPolicy.d.ts +102 -1
  29. package/lib/api/iam/mcpPolicy.js +38 -2
  30. package/lib/api/iam/oauth2.js +18 -12
  31. package/lib/api/iam/profile.d.ts +17 -1
  32. package/lib/api/iam/profile.js +27 -0
  33. package/lib/api/iam/scimTokens.d.ts +79 -0
  34. package/lib/api/iam/scimTokens.js +141 -0
  35. package/lib/api/iam/secrets.js +10 -23
  36. package/lib/api/iam/trials.d.ts +38 -0
  37. package/lib/api/iam/trials.js +66 -0
  38. package/lib/api/index.d.ts +4 -0
  39. package/lib/api/index.js +4 -0
  40. package/lib/api/mcp/observability.d.ts +34 -11
  41. package/lib/api/mcp/observability.js +97 -59
  42. package/lib/api/orchestration/client.d.ts +53 -0
  43. package/lib/api/orchestration/client.js +105 -0
  44. package/lib/api/orchestration/events.d.ts +88 -0
  45. package/lib/api/orchestration/events.js +245 -0
  46. package/lib/api/orchestration/generated.d.ts +693 -0
  47. package/lib/api/orchestration/generated.js +687 -0
  48. package/lib/api/orchestration/index.d.ts +30 -0
  49. package/lib/api/orchestration/index.js +34 -0
  50. package/lib/api/orchestration/lifecycle.d.ts +39 -0
  51. package/lib/api/orchestration/lifecycle.js +57 -0
  52. package/lib/api/orchestration/measures.d.ts +55 -0
  53. package/lib/api/orchestration/measures.js +99 -0
  54. package/lib/api/otel/dashboards.d.ts +56 -0
  55. package/lib/api/otel/dashboards.js +50 -0
  56. package/lib/api/otel/index.d.ts +1 -0
  57. package/lib/api/otel/index.js +1 -0
  58. package/lib/api/otel/metrics.d.ts +16 -0
  59. package/lib/api/otel/metrics.js +15 -0
  60. package/lib/api/scheduler/client.d.ts +32 -0
  61. package/lib/api/scheduler/client.js +42 -0
  62. package/lib/api/scheduler/index.d.ts +8 -0
  63. package/lib/api/scheduler/index.js +16 -0
  64. package/lib/api/scheduler/request.d.ts +28 -0
  65. package/lib/api/scheduler/request.js +50 -0
  66. package/lib/api/scheduler/types.d.ts +88 -0
  67. package/lib/api/scheduler/types.js +5 -0
  68. package/lib/api/spacer/comments.d.ts +97 -0
  69. package/lib/api/spacer/comments.js +45 -0
  70. package/lib/api/spacer/index.d.ts +9 -0
  71. package/lib/api/spacer/index.js +17 -0
  72. package/lib/api/spacer/notebooks.d.ts +33 -0
  73. package/lib/api/spacer/notebooks.js +39 -0
  74. package/lib/api/spacer/request.d.ts +25 -0
  75. package/lib/api/spacer/request.js +46 -0
  76. package/lib/api/spacer/spaces.d.ts +68 -0
  77. package/lib/api/spacer/spaces.js +45 -0
  78. package/lib/api/utils/validation.d.ts +4 -4
  79. package/lib/api/utils/validation.js +10 -7
  80. package/lib/client/auth/strategies.js +1 -1
  81. package/lib/client/constants.d.ts +1 -0
  82. package/lib/client/constants.js +1 -0
  83. package/lib/client/mixins/IAMMixin.js +2 -2
  84. package/lib/components/animation/AnimatedText.d.ts +1 -44
  85. package/lib/components/animation/AnimatedText.js +6 -122
  86. package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
  87. package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
  88. package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
  89. package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
  90. package/lib/components/anonymous/index.d.ts +7 -0
  91. package/lib/components/anonymous/index.js +15 -0
  92. package/lib/components/avatars/UserAvatar.d.ts +7 -1
  93. package/lib/components/avatars/UserAvatar.js +15 -1
  94. package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
  95. package/lib/components/billing/BillingEntitySelect.js +49 -10
  96. package/lib/components/billing/eligibility.d.ts +22 -0
  97. package/lib/components/billing/eligibility.js +27 -0
  98. package/lib/components/billing/index.d.ts +1 -0
  99. package/lib/components/billing/index.js +1 -0
  100. package/lib/components/checkout/StripeCheckout.d.ts +1 -1
  101. package/lib/components/checkout/StripeCheckout.js +1 -1
  102. package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
  103. package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
  104. package/lib/components/display/DatalayerBox.d.ts +2 -3
  105. package/lib/components/display/DatalayerBox.js +1 -1
  106. package/lib/components/display/NavLink.js +3 -2
  107. package/lib/components/display/VisuallyHidden.d.ts +1 -2
  108. package/lib/components/labels/StatusLabels.d.ts +2 -10
  109. package/lib/components/labels/StatusLabels.js +2 -26
  110. package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
  111. package/lib/components/principal/PrincipalAppearance.js +232 -174
  112. package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
  113. package/lib/components/principal/PrincipalAvatar.js +2 -2
  114. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
  115. package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
  116. package/lib/components/screencapture/Screencapture.js +46 -3
  117. package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
  118. package/lib/components/sharing/ShareAccessComponent.js +110 -78
  119. package/lib/components/sharing/SharingEditor.d.ts +5 -1
  120. package/lib/components/sharing/SharingEditor.js +9 -4
  121. package/lib/components/sharing/index.d.ts +1 -0
  122. package/lib/components/sharing/index.js +1 -0
  123. package/lib/components/sharing/sandboxSharing.d.ts +26 -0
  124. package/lib/components/sharing/sandboxSharing.js +47 -0
  125. package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
  126. package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
  127. package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
  128. package/lib/components/spaces/SpaceDisplay.js +92 -0
  129. package/lib/components/spaces/index.d.ts +4 -0
  130. package/lib/components/spaces/index.js +7 -0
  131. package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
  132. package/lib/components/spaces/spaceDisplayModel.js +170 -0
  133. package/lib/config/Configuration.d.ts +4 -4
  134. package/lib/config/Configuration.js +7 -7
  135. package/lib/config/index.d.ts +1 -0
  136. package/lib/config/index.js +1 -0
  137. package/lib/config/planes.d.ts +45 -0
  138. package/lib/config/planes.js +53 -0
  139. package/lib/hooks/cacheConverters.d.ts +0 -7
  140. package/lib/hooks/cacheConverters.js +0 -25
  141. package/lib/hooks/index.d.ts +1 -0
  142. package/lib/hooks/index.js +1 -0
  143. package/lib/hooks/useBackdrop.d.ts +4 -5
  144. package/lib/hooks/useBillingEntityStore.js +3 -0
  145. package/lib/hooks/useCache.d.ts +197 -44
  146. package/lib/hooks/useCache.js +1586 -494
  147. package/lib/hooks/useContents.d.ts +22 -1
  148. package/lib/hooks/useContents.js +90 -21
  149. package/lib/hooks/useMcp.d.ts +87 -1
  150. package/lib/hooks/useMcp.js +248 -13
  151. package/lib/hooks/useNavigate.d.ts +41 -3
  152. package/lib/hooks/useNavigate.js +116 -52
  153. package/lib/hooks/usePrincipalCacheStore.js +3 -0
  154. package/lib/hooks/usePrincipalStore.js +3 -0
  155. package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
  156. package/lib/hooks/useSpaceCacheStore.js +85 -0
  157. package/lib/hooks/useWindowSize.js +1 -1
  158. package/lib/models/CreditsDTO.d.ts +6 -5
  159. package/lib/models/CreditsDTO.js +7 -5
  160. package/lib/models/IAMProvidersSpecs.d.ts +10 -5
  161. package/lib/models/IAMProvidersSpecs.js +0 -23
  162. package/lib/models/McpBinding.d.ts +6 -0
  163. package/lib/models/Organization.d.ts +69 -0
  164. package/lib/models/Organization.js +96 -0
  165. package/lib/models/Secret.d.ts +1 -2
  166. package/lib/models/Secret.js +2 -18
  167. package/lib/models/StartedBy.d.ts +34 -0
  168. package/lib/models/StartedBy.js +36 -0
  169. package/lib/models/Team.d.ts +2 -0
  170. package/lib/models/Team.js +2 -0
  171. package/lib/models/User.d.ts +4 -0
  172. package/lib/models/User.js +3 -0
  173. package/lib/models/UserSettings.d.ts +13 -0
  174. package/lib/models/UserSettings.js +9 -0
  175. package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
  176. package/lib/navigation/adapters/react-router.d.ts +1 -1
  177. package/lib/navigation/adapters/react-router.js +3 -0
  178. package/lib/navigation/components.js +2 -3
  179. package/lib/routes/publicPaths.js +4 -0
  180. package/lib/state/index.d.ts +1 -0
  181. package/lib/state/index.js +1 -0
  182. package/lib/state/sessionEnd.d.ts +55 -0
  183. package/lib/state/sessionEnd.js +162 -0
  184. package/lib/state/substates/CoreState.js +3 -16
  185. package/lib/state/substates/IAMState.d.ts +3 -3
  186. package/lib/state/substates/IAMState.js +7 -0
  187. package/lib/state/substates/LayoutState.js +12 -0
  188. package/lib/state/substates/NavigationState.d.ts +82 -0
  189. package/lib/state/substates/NavigationState.js +255 -0
  190. package/lib/state/substates/ProfileState.d.ts +1 -0
  191. package/lib/state/substates/ProfileState.js +1 -0
  192. package/lib/state/substates/index.d.ts +1 -0
  193. package/lib/state/substates/index.js +1 -0
  194. package/lib/utils/Lazy.d.ts +20 -0
  195. package/lib/utils/Lazy.js +32 -6
  196. package/lib/utils/Screencapture.d.ts +15 -0
  197. package/lib/utils/Screencapture.js +26 -20
  198. package/lib/utils/WithSuspense.d.ts +11 -1
  199. package/lib/utils/WithSuspense.js +11 -1
  200. package/lib/views/mcp/AdmittedClients.d.ts +44 -0
  201. package/lib/views/mcp/AdmittedClients.js +59 -0
  202. package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
  203. package/lib/views/mcp/ApprovalQueue.js +95 -0
  204. package/lib/views/mcp/ConnectedAgents.js +38 -3
  205. package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
  206. package/lib/views/mcp/EnterpriseConsole.js +23 -1
  207. package/lib/views/mcp/IdentityProviders.d.ts +33 -0
  208. package/lib/views/mcp/IdentityProviders.js +299 -0
  209. package/lib/views/mcp/McpDashboard.js +90 -7
  210. package/lib/views/mcp/McpHome.js +1 -1
  211. package/lib/views/mcp/McpObservability.d.ts +30 -1
  212. package/lib/views/mcp/McpObservability.js +105 -19
  213. package/lib/views/mcp/NotebookRuns.d.ts +39 -0
  214. package/lib/views/mcp/NotebookRuns.js +44 -0
  215. package/lib/views/mcp/OrganizationPolicy.js +2 -1
  216. package/lib/views/mcp/PersonalPolicy.js +2 -1
  217. package/lib/views/mcp/PolicyForm.d.ts +17 -1
  218. package/lib/views/mcp/PolicyForm.js +25 -3
  219. package/lib/views/mcp/PolicyHistory.d.ts +13 -0
  220. package/lib/views/mcp/PolicyHistory.js +14 -1
  221. package/lib/views/mcp/RunDetail.d.ts +87 -0
  222. package/lib/views/mcp/RunDetail.js +186 -0
  223. package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
  224. package/lib/views/mcp/ScimProvisioning.js +173 -0
  225. package/lib/views/mcp/TeamPolicies.js +66 -5
  226. package/lib/views/mcp/ToolAccess.d.ts +60 -0
  227. package/lib/views/mcp/ToolAccess.js +101 -0
  228. package/lib/views/mcp/TraceTimeline.d.ts +81 -0
  229. package/lib/views/mcp/TraceTimeline.js +132 -0
  230. package/lib/views/mcp/index.d.ts +5 -0
  231. package/lib/views/mcp/index.js +5 -0
  232. package/lib/views/otel/simpleAuthStore.js +3 -0
  233. package/lib/views/secrets/SecretEdit.js +3 -2
  234. package/lib/views/secrets/SecretNew.js +5 -1
  235. package/package.json +271 -264
  236. package/scripts/generate-mcp-types.py +16 -10
  237. 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
- /** The gateway's own spans, so the tree can call out what each stage cost. */
13
- const STAGE = (name) => {
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 = STAGE(node.span.span_name);
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))) }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "The workflow steps and the sandbox launch of a durable task join this tree with milestone 2, and so does the live stream of calls as they land." })] }))] })), 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: {
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 points = metrics.data?.metrics[name] ?? [];
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: { fontSize: 0, color: 'fg.muted' }, children: points.length === 0
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
- fontWeight: 'semibold',
141
- minWidth: 80,
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;