@datalayer/core 1.2.27 → 1.2.29

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 (80) hide show
  1. package/lib/api/evals/__tests__/external-definition.golden.json +110 -0
  2. package/lib/api/evals/client.d.ts +13 -1
  3. package/lib/api/evals/client.js +13 -0
  4. package/lib/api/evals/types.d.ts +168 -1
  5. package/lib/api/mcp/agentAccess.d.ts +10 -0
  6. package/lib/api/mcp/agentAccess.js +28 -0
  7. package/lib/api/mcp/generated.js +20 -0
  8. package/lib/api/mcp/index.d.ts +1 -0
  9. package/lib/api/mcp/index.js +1 -0
  10. package/lib/api/mcp/observability.d.ts +1 -1
  11. package/lib/api/mcp/observability.js +2 -0
  12. package/lib/api/mcp/operations.d.ts +28 -10
  13. package/lib/api/mcp/tasks.d.ts +9 -1
  14. package/lib/api/otel/firewall.d.ts +74 -0
  15. package/lib/api/otel/firewall.js +42 -0
  16. package/lib/api/otel/index.d.ts +1 -0
  17. package/lib/api/otel/index.js +1 -0
  18. package/lib/cli-login.js +2 -2
  19. package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
  20. package/lib/components/index.d.ts +1 -0
  21. package/lib/components/index.js +1 -0
  22. package/lib/components/mcp/ScopeList.js +10 -1
  23. package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
  24. package/lib/components/sharing/ShareAccessComponent.js +33 -8
  25. package/lib/components/toast/ToastPanel.d.ts +11 -0
  26. package/lib/components/toast/ToastPanel.js +42 -0
  27. package/lib/components/toast/index.d.ts +2 -0
  28. package/lib/components/toast/index.js +6 -0
  29. package/lib/components/toast/toastQueue.d.ts +35 -0
  30. package/lib/components/toast/toastQueue.js +47 -0
  31. package/lib/hooks/index.d.ts +1 -0
  32. package/lib/hooks/index.js +1 -0
  33. package/lib/hooks/useCache.d.ts +4 -2
  34. package/lib/hooks/useCache.js +13 -4
  35. package/lib/hooks/useDatalayer.js +12 -16
  36. package/lib/hooks/useKeptScroll.d.ts +6 -0
  37. package/lib/hooks/useKeptScroll.js +54 -0
  38. package/lib/hooks/useMcp.d.ts +2 -2
  39. package/lib/hooks/useMcp.js +5 -2
  40. package/lib/hooks/useNavigate.d.ts +16 -2
  41. package/lib/hooks/useNavigate.js +65 -5
  42. package/lib/hooks/useToast.d.ts +6 -0
  43. package/lib/hooks/useToast.js +40 -1
  44. package/lib/models/McpAgentAccess.d.ts +95 -0
  45. package/lib/models/McpAgentAccess.js +5 -0
  46. package/lib/models/Usage.d.ts +13 -0
  47. package/lib/models/Usage.js +5 -0
  48. package/lib/models/index.d.ts +1 -0
  49. package/lib/models/index.js +1 -0
  50. package/lib/otel/client/OtelClient.d.ts +56 -0
  51. package/lib/otel/client/OtelClient.js +25 -0
  52. package/lib/otel/client/index.d.ts +1 -1
  53. package/lib/otel/index.d.ts +1 -1
  54. package/lib/otel/views/OtelLive.js +3 -3
  55. package/lib/otel/views/OtelLiveSpans.js +2 -2
  56. package/lib/otel/views/OtelLogsList.js +3 -3
  57. package/lib/otel/views/OtelMetricsChart.d.ts +13 -0
  58. package/lib/otel/views/OtelMetricsChart.js +109 -26
  59. package/lib/otel/views/OtelSpanDetail.js +3 -3
  60. package/lib/otel/views/OtelSqlView.js +4 -4
  61. package/lib/otel/views/OtelSystemView.js +2 -2
  62. package/lib/otel/views/OtelTimelineRangeSlider.js +2 -2
  63. package/lib/otel/views/OtelTracesList.js +2 -2
  64. package/lib/state/substates/LayoutState.js +24 -3
  65. package/lib/state/substates/NavigationState.d.ts +11 -3
  66. package/lib/state/substates/NavigationState.js +48 -5
  67. package/lib/views/mcp/AuditLog.js +1 -1
  68. package/lib/views/mcp/ConnectedAgents.d.ts +1 -1
  69. package/lib/views/mcp/ConnectedAgents.js +13 -10
  70. package/lib/views/mcp/McpDashboard.d.ts +1 -1
  71. package/lib/views/mcp/McpDashboard.js +6 -6
  72. package/lib/views/mcp/McpHome.d.ts +3 -1
  73. package/lib/views/mcp/McpHome.js +19 -9
  74. package/lib/views/mcp/McpObservability.d.ts +22 -3
  75. package/lib/views/mcp/McpObservability.js +65 -17
  76. package/lib/views/mcp/Policies.js +1 -1
  77. package/lib/views/mcp/RunDetail.js +2 -2
  78. package/lib/views/mcp/types.d.ts +8 -4
  79. package/lib/views/otel/OtelHeader.js +2 -2
  80. package/package.json +2 -2
@@ -38,6 +38,9 @@ export const NAVIGATION_VIEW_HOMES = {
38
38
  // of its own to be sent to. It was `/power` until 2026-10-09, when the
39
39
  // shell took the name it is called by everywhere else.
40
40
  home: '/platform',
41
+ // The spaces and what they hold: the Workspace of the Sessions shell,
42
+ // a shell of its own since 9 November 2026.
43
+ spaces: '/spaces',
41
44
  agentify: '/agentify',
42
45
  // The research areas and their spaces.
43
46
  research: '/research',
@@ -60,6 +63,7 @@ const FORMER_TABS_KEY = 'datalayer_view_tabs';
60
63
  const COOKIE_MAX_AGE_DAYS = 365;
61
64
  const isView = (value) => value === 'studio' ||
62
65
  value === 'home' ||
66
+ value === 'spaces' ||
63
67
  value === 'agentify' ||
64
68
  value === 'research' ||
65
69
  value === 'admin' ||
@@ -243,17 +247,26 @@ export function isHostedAppRoute(route) {
243
247
  (segments.length === 4 && segments[2] === 'preview')));
244
248
  }
245
249
  /**
246
- * Whether a page belongs to no shell: the settings, the documentation and
247
- * the decks.
250
+ * Whether a page belongs to no shell: the settings, the documentation, the
251
+ * presentations and the agentspecs catalogue (since 8 November 2026: reached
252
+ * from an agent's picker on /agents/new, its env var's page swapped the
253
+ * Sessions sidebar for the Studio's).
248
254
  *
249
255
  * They are read from whichever shell the reader is in — its sidebar stays
250
256
  * beside them and the header's selector does not move — so they are never a
251
257
  * shell's own page: opening one does not change the shell, and no shell
252
258
  * returns to one.
259
+ *
260
+ * The presentations are `/decks/<collection>/<slug>` and their list,
261
+ * `/presentations`. Bare `/decks` is not one of them: it is the selected
262
+ * space's decks table, a page of the Spaces shell (the user, 12 November
263
+ * 2026). Neutral, it took the current shell whenever one was given, and the
264
+ * Sessions sidebar stayed beside it.
253
265
  */
254
266
  export function isShellNeutralRoute(route) {
255
267
  const path = pathOf(route);
256
- return ['/settings', '/docs', '/decks'].some(prefix => path === prefix || path.startsWith(`${prefix}/`));
268
+ return (path.startsWith('/decks/') ||
269
+ ['/settings', '/docs', '/presentations', '/agentspecs'].some(prefix => path === prefix || path.startsWith(`${prefix}/`)));
257
270
  }
258
271
  /**
259
272
  * The shell a route belongs to, read off its first segment.
@@ -275,8 +288,8 @@ export function viewForRoute(route, current) {
275
288
  return current;
276
289
  }
277
290
  // The root is the Studio: what a signed-in reader opens on. The settings
278
- // are read from it too, with its sidebar beside them — and so is the
279
- // agentspecs catalogue, the specs an app is composed from.
291
+ // and the agentspecs catalogue are read from it too, with its sidebar
292
+ // beside them, when no shell is current.
280
293
  if (path === '/' ||
281
294
  path === '/studio' ||
282
295
  path.startsWith('/studio/') ||
@@ -286,6 +299,10 @@ export function viewForRoute(route, current) {
286
299
  path.startsWith('/agentspecs/')) {
287
300
  return 'studio';
288
301
  }
302
+ if (SPACES_SHELL_ROUTES.some(prefix => path === prefix || path.startsWith(`${prefix}/`)) ||
303
+ SPACES_SHELL_EXACT_ROUTES.includes(path)) {
304
+ return 'spaces';
305
+ }
289
306
  if (path === '/agentify' || path.startsWith('/agentify/')) {
290
307
  return 'agentify';
291
308
  }
@@ -315,6 +332,32 @@ export function viewForRoute(route, current) {
315
332
  }
316
333
  return 'home';
317
334
  }
335
+ /**
336
+ * The Spaces shell's pages: the spaces, then the work they hold — what was
337
+ * the Workspace of the Sessions sidebar until 9 November 2026, when it became
338
+ * a shell of its own.
339
+ */
340
+ const SPACES_SHELL_ROUTES = [
341
+ '/spaces',
342
+ '/items',
343
+ // The Library, public and private, beside the Spaces sidebar (the user,
344
+ // 9 November 2026).
345
+ '/library',
346
+ '/shared',
347
+ '/notebooks',
348
+ '/documents',
349
+ '/cells',
350
+ // The selected space's spreadsheets (the user, 12 November 2026).
351
+ '/spreadsheets',
352
+ '/datasets',
353
+ '/contents',
354
+ ];
355
+ /**
356
+ * The selected space's decks, `/decks` itself only (the user, 12 November
357
+ * 2026): `/decks/<collection>/<slug>` are presentations, drawn by their own
358
+ * page, not the Spaces shell's.
359
+ */
360
+ const SPACES_SHELL_EXACT_ROUTES = ['/decks'];
318
361
  /**
319
362
  * `/<account>/<space>/<kind>[/…]` for the kinds a space shows beside the
320
363
  * Agentify sidebar — decks, spreadsheets (Cells), courses and what a course
@@ -87,7 +87,7 @@ export const AuditLog = ({ errorState, routes, filters, onFiltersChange, retenti
87
87
  }), children: [_jsx(Select.Option, { value: "", children: "Every decision" }), _jsx(Select.Option, { value: "allowed", children: "Allowed" }), _jsx(Select.Option, { value: "refused", children: "Refused" })] }), _jsxs(Select, { size: "small", "aria-label": "Outcome", value: filters.outcome ?? '', onChange: event => set({
88
88
  outcome: (event.currentTarget.value ||
89
89
  undefined),
90
- }), children: [_jsx(Select.Option, { value: "", children: "Every outcome" }), _jsx(Select.Option, { value: "ok", children: "Succeeded" }), _jsx(Select.Option, { value: "error", children: "Failed" }), _jsx(Select.Option, { value: "is_error", children: "Tool error" })] }), _jsx(Box, { display: "flex", gap: 2, justifyContent: "flex-end", children: _jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", leadingVisual: DownloadIcon, disabled: exporting.isPending, children: "Export" }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Item, { onSelect: () => exportAs('jsonl'), children: ["JSONL", _jsx(ActionList.Description, { variant: "block", children: "One row per line, as a SIEM reads it." })] }), _jsxs(ActionList.Item, { onSelect: () => exportAs('csv'), children: ["CSV", _jsx(ActionList.Description, { variant: "block", children: "The same rows for a spreadsheet." })] })] }) })] }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Audit rows are written once and never changed or deleted by anyone, here or elsewhere. Arguments are redacted before they are written.", ' ', retention] }), page.isPending && !page.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 6, children: _jsx(Spinner, {}) })) : rows.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(HistoryIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Nothing to audit yet" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Every call an agent makes is written down here with its decision and its outcome. Connect a client and ask it to list your notebooks." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.access), children: "Connect an agent" })] })) : (
90
+ }), children: [_jsx(Select.Option, { value: "", children: "Every outcome" }), _jsx(Select.Option, { value: "ok", children: "Succeeded" }), _jsx(Select.Option, { value: "error", children: "Failed" }), _jsx(Select.Option, { value: "is_error", children: "Tool error" })] }), _jsx(Box, { display: "flex", gap: 2, justifyContent: "flex-end", children: _jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", leadingVisual: DownloadIcon, disabled: exporting.isPending, children: "Export" }), _jsx(ActionMenu.Overlay, { align: "end", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Item, { onSelect: () => exportAs('jsonl'), children: ["JSONL", _jsx(ActionList.Description, { variant: "block", children: "One row per line, as a SIEM reads it." })] }), _jsxs(ActionList.Item, { onSelect: () => exportAs('csv'), children: ["CSV", _jsx(ActionList.Description, { variant: "block", children: "The same rows for a spreadsheet." })] })] }) })] }) })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Audit rows are written once and never changed or deleted by anyone, here or elsewhere. Arguments are redacted before they are written.", ' ', retention] }), page.isPending && !page.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 6, children: _jsx(Spinner, {}) })) : rows.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(HistoryIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Nothing to audit yet" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Every call an agent makes is written down here with its decision and its outcome. Connect a client and ask it to list your notebooks." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.configuration), children: "Connect an agent" })] })) : (
91
91
  /* The rail of the activity view: each mark on a tinted disc, the discs
92
92
  linked by a line, so the rows carry no border of their own. */
93
93
  _jsx(Box, { display: "grid", children: rows.map((event, index) => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The agents a person has connected, and the one action that matters:
2
+ * The MCP clients a person has authorized, and the one action that matters:
3
3
  * disconnecting one.
4
4
  *
5
5
  * A grant is not a setting — it is authority a person handed to a piece of
@@ -3,7 +3,7 @@ import { useMemo, useRef, useState } from 'react';
3
3
  import { ActionList, ActionMenu, Button, Heading, Label, RelativeTime, Spinner, Text, } from '@primer/react';
4
4
  import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
5
5
  import { Box } from '@datalayer/primer-addons';
6
- import { KebabHorizontalIcon, PlugIcon } from '@primer/octicons-react';
6
+ import { KebabHorizontalIcon, McpIcon } from '@primer/octicons-react';
7
7
  import { ClientBadge, McpErrorBlankslate, ScopeList, } from '../../components/mcp/index.js';
8
8
  import { useConnectedAgents, useDisconnectAgent, useDisconnectEveryAgent, } from '../../hooks/useMcp.js';
9
9
  import { useNavigate, useToast } from '../../hooks/index.js';
@@ -24,8 +24,8 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
24
24
  disconnectEvery.mutate(undefined, {
25
25
  onSuccess: answer => {
26
26
  enqueueToast(answer.revoked === 1
27
- ? '1 agent disconnected.'
28
- : `${answer.revoked} agents disconnected.`, { variant: 'success' });
27
+ ? '1 client disconnected.'
28
+ : `${answer.revoked} clients disconnected.`, { variant: 'success' });
29
29
  setDisconnectingEvery(false);
30
30
  },
31
31
  onError: error => {
@@ -75,19 +75,22 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
75
75
  {
76
76
  header: 'Allowed to',
77
77
  id: 'scopes',
78
- width: 'growCollapse',
78
+ // Room for its scopes, which wrap inside the cell rather than run
79
+ // over the dates beside it (30 October 2026).
80
+ width: 'grow',
81
+ minWidth: '240px',
79
82
  renderCell: row => _jsx(ScopeList, { scopes: row.scopes }),
80
83
  },
81
84
  {
82
85
  header: 'Connected',
83
86
  id: 'created',
84
- width: '130px',
87
+ width: '140px',
85
88
  renderCell: row => row.createdAt ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: row.createdAt }) })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "\u2014" })),
86
89
  },
87
90
  {
88
91
  header: 'Last used',
89
92
  id: 'last-used',
90
- width: '130px',
93
+ width: '140px',
91
94
  renderCell: row => row.lastUsedAt ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: _jsx(RelativeTime, { datetime: row.lastUsedAt }) })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Never" })),
92
95
  },
93
96
  {
@@ -99,9 +102,9 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
99
102
  },
100
103
  ];
101
104
  if (agents.isError) {
102
- return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Connected agents'), onRetry: () => agents.refetch() }));
105
+ return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Authorized clients'), onRetry: () => agents.refetch() }));
103
106
  }
104
- return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { display: "flex", alignItems: "start", justifyContent: "space-between", gap: 3, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Connected Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Every agent you have authorized, what it may do, and when it last did it." })] }), !readOnly && rows.length > 0 && (_jsx(Button, { variant: "danger", onClick: () => setDisconnectingEvery(true), disabled: disconnectEvery.isPending, children: "Disconnect all" }))] })), agents.isPending && !agents.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : rows.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "connected-agents", children: "Agents" }), _jsx(Table.Subtitle, { as: "p", id: "connected-agents-subtitle", children: "An agent acts with exactly the access of the person who authorized it, and no more." }), _jsx(DataTable, { "aria-labelledby": "connected-agents", "aria-describedby": "connected-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PlugIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent connected" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Connect an MCP client and approve the scopes it asks for; the grant appears here and can be revoked from here." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.access), children: "Connect an agent" })] })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Narrowing a grant to fewer scopes without disconnecting arrives with the organization policy." }), disconnecting && (_jsx(Dialog, { title: "Disconnect this agent?", onClose: () => setDisconnecting(null), returnFocusRef: returnFocusRef, footerButtons: [
107
+ return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { display: "flex", alignItems: "start", justifyContent: "space-between", gap: 3, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Authorized clients" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Every MCP client you have authorized, what it may do, and when it last did it." })] }), !readOnly && rows.length > 0 && (_jsx(Button, { variant: "danger", onClick: () => setDisconnectingEvery(true), disabled: disconnectEvery.isPending, children: "Disconnect all" }))] })), agents.isPending && !agents.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : rows.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "authorized-clients", children: "Clients" }), _jsx(Table.Subtitle, { as: "p", id: "authorized-clients-subtitle", children: "A client acts with exactly the access of the person who authorized it, and no more." }), _jsx(DataTable, { "aria-labelledby": "authorized-clients", "aria-describedby": "authorized-clients-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(McpIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No client authorized" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Connect an MCP client and approve the scopes it asks for; the grant appears here and can be revoked from here." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.configuration), children: "Connect a client" })] })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "Narrowing a grant to fewer scopes without disconnecting arrives with the organization policy." }), disconnecting && (_jsx(Dialog, { title: "Disconnect this client?", onClose: () => setDisconnecting(null), returnFocusRef: returnFocusRef, footerButtons: [
105
108
  {
106
109
  buttonType: 'default',
107
110
  content: 'Keep it',
@@ -113,7 +116,7 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
113
116
  onClick: confirmDisconnect,
114
117
  disabled: disconnect.isPending,
115
118
  },
116
- ], children: _jsxs(Text, { sx: { fontSize: 1 }, children: [disconnecting.clientName || disconnecting.clientId, " loses its grant at once and its next call is refused. An access token it still holds dies with its own short expiry. The other agents are untouched, and nothing this one already did is undone."] }) })), disconnectingEvery && (_jsxs(Dialog, { title: "Disconnect every agent?", onClose: () => setDisconnectingEvery(false), returnFocusRef: returnFocusRef, footerButtons: [
119
+ ], children: _jsxs(Text, { sx: { fontSize: 1 }, children: [disconnecting.clientName || disconnecting.clientId, " loses its grant at once and its next call is refused. An access token it still holds dies with its own short expiry. The other clients are untouched, and nothing this one already did is undone."] }) })), disconnectingEvery && (_jsxs(Dialog, { title: "Disconnect every client?", onClose: () => setDisconnectingEvery(false), returnFocusRef: returnFocusRef, footerButtons: [
117
120
  {
118
121
  buttonType: 'default',
119
122
  content: 'Keep them',
@@ -125,6 +128,6 @@ export const ConnectedAgents = ({ errorState, routes, showTitle = true, readOnly
125
128
  onClick: confirmDisconnectEvery,
126
129
  disabled: disconnectEvery.isPending,
127
130
  },
128
- ], children: [_jsx(Text, { sx: { fontSize: 1 }, children: "Every agent you have authorized loses its grant at once \u2014 signing out everywhere. Each will ask again the next time you connect it, and this time it goes through the approval screen rather than being answered from what you agreed before, so this is also how you force a fresh approval." }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2, mb: 0 }, children: "Yours only. Nobody else\u2019s sessions end, in this organization or any other, and nothing any agent already did is undone." })] }))] }));
131
+ ], children: [_jsx(Text, { sx: { fontSize: 1 }, children: "Every client you have authorized loses its grant at once \u2014 signing out everywhere. Each will ask again the next time you connect it, and this time it goes through the approval screen rather than being answered from what you agreed before, so this is also how you force a fresh approval." }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2, mb: 0 }, children: "Yours only. Nobody else\u2019s sessions end, in this organization or any other, and nothing any client already did is undone." })] }))] }));
129
132
  };
130
133
  export default ConnectedAgents;
@@ -45,6 +45,6 @@ export interface McpDashboardProps {
45
45
  section: McpDashboardSection;
46
46
  }
47
47
  /** The parts of the dashboard, each its own tab. */
48
- export type McpDashboardSection = 'overview' | 'agents' | 'runs' | 'sandboxes' | 'calls';
48
+ export type McpDashboardSection = 'overview' | 'clients' | 'runs' | 'sandboxes' | 'calls';
49
49
  export declare const McpDashboard: ({ errorState, routes, org, showTitle, section, }: McpDashboardProps) => JSX.Element;
50
50
  export default McpDashboard;
@@ -3,7 +3,7 @@ import { useMemo, useRef, useState } from 'react';
3
3
  import { ActionList, ActionMenu, Button, Heading, Label, Link, RelativeTime, Select, Spinner, Text, } from '@primer/react';
4
4
  import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
5
5
  import { Box, Card } from '@datalayer/primer-addons';
6
- import { ChecklistIcon, KebabHorizontalIcon, PlayIcon, PlugIcon, StackIcon, } from '@primer/octicons-react';
6
+ import { ChecklistIcon, KebabHorizontalIcon, PlayIcon, McpIcon, PackageIcon, } from '@primer/octicons-react';
7
7
  import { ShareAccessDialog, sandboxSharingUrl } from '../../components/sharing/index.js';
8
8
  import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
9
9
  import { useDisconnectAgent, useMcpActivity, useTerminateBinding, } from '../../hooks/useMcp.js';
@@ -375,17 +375,17 @@ export const McpDashboard = ({ errorState, routes, org, showTitle = true, sectio
375
375
  'repeat(2, 1fr)',
376
376
  'repeat(3, 1fr)',
377
377
  'repeat(6, 1fr)',
378
- ], children: [_jsx(Count, { label: "Clients connected", value: clients.length, to: routes.agents, onOpen: navigate }), _jsx(Count, { label: "Sandboxes bound", to: routes.sandboxes, onOpen: navigate, value: sandboxes.filter(binding => (binding.state ?? 'active') === 'active').length }), _jsx(Count, { label: "Tasks running", value: data?.tasks.length ?? 0, to: routes.runs, onOpen: navigate }), _jsx(Count, { label: "Calls today", value: data?.today.calls ?? 0, to: routes.calls, onOpen: navigate }), _jsx(Count, { label: "Refusals today", value: data?.today.refusals ?? 0, to: routes.audit, onOpen: navigate, tone: (data?.today.refusals ?? 0) > 0 ? 'danger' : 'neutral' }), _jsx(Count, { label: "Credits by agents", value: data?.today.credits ?? 0, note: "today",
378
+ ], children: [_jsx(Count, { label: "Clients connected", value: clients.length, to: routes.clients, onOpen: navigate }), _jsx(Count, { label: "Sandboxes bound", to: routes.sandboxes, onOpen: navigate, value: sandboxes.filter(binding => (binding.state ?? 'active') === 'active').length }), _jsx(Count, { label: "Tasks running", value: data?.tasks.length ?? 0, to: routes.runs, onOpen: navigate }), _jsx(Count, { label: "Calls today", value: data?.today.calls ?? 0, to: routes.calls, onOpen: navigate }), _jsx(Count, { label: "Refusals today", value: data?.today.refusals ?? 0, to: routes.audit, onOpen: navigate, tone: (data?.today.refusals ?? 0) > 0 ? 'danger' : 'neutral' }), _jsx(Count, { label: "Credits by agents", value: data?.today.credits ?? 0, note: "today",
379
379
  // Credits are summed from the day's calls, which the Calls tab
380
380
  // lists.
381
- to: routes.calls, onOpen: navigate })] }))), draws('agents') &&
381
+ to: routes.calls, onOpen: navigate })] }))), draws('clients') &&
382
382
  !loading &&
383
- (clients.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-clients", children: "Connected clients" }), _jsxs(Table.Subtitle, { as: "p", id: "mcp-clients-subtitle", children: [plural(clients.length, 'client'), " with a live grant on your workspace."] }), _jsx(DataTable, { "aria-labelledby": "mcp-clients", "aria-describedby": "mcp-clients-subtitle", data: clients, columns: clientColumns })] })) : (_jsx(PanelEmpty, { icon: PlugIcon, heading: "No client connected", description: "Connect Claude Code, Codex, Cursor, VS Code or any MCP client to your workspace, and it appears here on its first call.", action: {
383
+ (clients.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-clients", children: "Live connections" }), _jsxs(Table.Subtitle, { as: "p", id: "mcp-clients-subtitle", children: [plural(clients.length, 'client'), " connected to your workspace now, with its status and the last tool it called."] }), _jsx(DataTable, { "aria-labelledby": "mcp-clients", "aria-describedby": "mcp-clients-subtitle", data: clients, columns: clientColumns })] })) : (_jsx(PanelEmpty, { icon: McpIcon, heading: "No client connected", description: "Connect Claude Code, Codex, Cursor, VS Code or any MCP client to your workspace, and it appears here on its first call.", action: {
384
384
  label: 'Set up a client',
385
- onClick: () => navigate(routes.access),
385
+ onClick: () => navigate(routes.configuration),
386
386
  } }))), draws('runs') && !loading && (_jsx(Box, { children: tasks.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-runs", children: "Running now" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-runs-subtitle", children: "A tool call that outlives its request. Open one to watch its output, cancel it, or answer it when it is waiting on you." }), _jsx(DataTable, { "aria-labelledby": "mcp-runs", "aria-describedby": "mcp-runs-subtitle", data: tasks, columns: taskColumns })] })) : (_jsx(PanelEmpty, { icon: PlayIcon, heading: "Nothing is running", description: "A tool call that outlives its request becomes a task you can watch, cancel and answer. Ask a client for one \u2014 an agent that runs a long cell \u2014 and it appears here while it runs." })) })), draws('sandboxes') &&
387
387
  !loading &&
388
- (sandboxes.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-sandboxes", children: "Sandboxes bound" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-sandboxes-subtitle", children: "The sessions your agents execute in. Terminating one ends its open work." }), _jsx(DataTable, { "aria-labelledby": "mcp-sandboxes", "aria-describedby": "mcp-sandboxes-subtitle", data: sandboxes, columns: sandboxColumns })] })) : (_jsx(PanelEmpty, { icon: StackIcon, heading: "No sandbox bound", description: "An agent that launches a sandbox keeps it for the session; it appears here with its provider, its capabilities and what it is doing." }))), draws('calls') &&
388
+ (sandboxes.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-sandboxes", children: "Sandboxes bound" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-sandboxes-subtitle", children: "The sessions your agents execute in. Terminating one ends its open work." }), _jsx(DataTable, { "aria-labelledby": "mcp-sandboxes", "aria-describedby": "mcp-sandboxes-subtitle", data: sandboxes, columns: sandboxColumns })] })) : (_jsx(PanelEmpty, { icon: PackageIcon, heading: "No Code Sandbox bound", description: "An agent that launches a sandbox keeps it for the session; it appears here with its provider, its capabilities and what it is doing." }))), draws('calls') &&
389
389
  !loading &&
390
390
  (calls.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "mcp-calls", children: "Recent calls" }), _jsx(Table.Subtitle, { as: "p", id: "mcp-calls-subtitle", children: "The last calls as they landed. Every one of them has an audit row that outlives this page." }), _jsx(Table.Actions, { children: _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsxs(Select, { size: "small", "aria-label": "Filter by client", value: callClient, onChange: event => setCallClient(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every client" }), clients.map(client => (_jsx(Select.Option, { value: client.clientId, children: client.clientName || client.clientId }, client.clientId)))] }), _jsxs(Select, { size: "small", "aria-label": "Filter by tool", value: callTool, onChange: event => setCallTool(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every tool" }), callTools.map(tool => (_jsx(Select.Option, { value: tool, children: tool }, tool)))] }), _jsxs(Select, { size: "small", "aria-label": "Filter by outcome", value: callOutcome, onChange: event => setCallOutcome(event.currentTarget.value), children: [_jsx(Select.Option, { value: "", children: "Every outcome" }), _jsx(Select.Option, { value: "ok", children: "Succeeded" }), _jsx(Select.Option, { value: "error", children: "Failed" }), _jsx(Select.Option, { value: "refused", children: "Refused" })] })] }) }), filteredCalls.length > 0 ? (_jsx(DataTable, { "aria-labelledby": "mcp-calls", "aria-describedby": "mcp-calls-subtitle", data: filteredCalls, columns: callColumns })) : (_jsx(Box, { p: 4, textAlign: "center", color: "fg.muted", fontSize: 1, children: "No call matches these filters." }))] })) : (_jsx(PanelEmpty, { icon: ChecklistIcon, heading: "No call yet", description: "Ask your agent to list your notebooks. The call, its decision and its outcome land here and in the audit log.", action: {
391
391
  label: 'Open the audit log',
@@ -85,8 +85,10 @@ export declare const MCP_CLIENTS: {
85
85
  * does: a reader who has seen one and then the other should not have to
86
86
  * work out whether they are the same affordance.
87
87
  */
88
- export declare const Copyable: ({ text }: {
88
+ export declare const Copyable: ({ text, compact, }: {
89
89
  text: string;
90
+ /** As wide as the text, with the button right after it. */
91
+ compact?: boolean;
90
92
  }) => JSX.Element;
91
93
  export declare const McpHome: ({ routes, clientDocsBase, showTitle, showSummaries, showSetup, onOpenDocs, }: McpHomeProps) => JSX.Element;
92
94
  export default McpHome;
@@ -39,12 +39,12 @@ export const MCP_CLIENTS = [
39
39
  * does: a reader who has seen one and then the other should not have to
40
40
  * work out whether they are the same affordance.
41
41
  */
42
- export const Copyable = ({ text }) => {
42
+ export const Copyable = ({ text, compact = false, }) => {
43
43
  const [copied, setCopied] = useState(false);
44
- return (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, p: 2, bg: "canvas.inset", borderRadius: 2, minWidth: 0, children: [_jsx(Text, { sx: {
44
+ return (_jsxs(Box, { display: compact ? 'inline-flex' : 'flex', justifySelf: compact ? 'start' : undefined, maxWidth: "100%", alignItems: "center", gap: 2, p: 2, bg: "canvas.inset", borderRadius: 2, minWidth: 0, children: [_jsx(Text, { sx: {
45
45
  fontFamily: 'mono',
46
46
  fontSize: 0,
47
- flex: 1,
47
+ flex: compact ? undefined : 1,
48
48
  minWidth: 0,
49
49
  overflow: 'hidden',
50
50
  textOverflow: 'ellipsis',
@@ -79,7 +79,7 @@ export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitl
79
79
  const live = activity.data?.clients.length ?? 0;
80
80
  return (_jsxs(Box, { display: "grid", gap: 4, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "MCP" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Connect your agents to your notebooks, data and sandboxes through the Model Context Protocol." })] })), showSetup && (_jsxs(_Fragment, { children: [_jsxs(Card, { border: true, shadow: "none", variant: "subtle", flexDirection: "row", display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap", p: 3, children: [_jsx(Text, { sx: { fontSize: 1 }, children: live > 0
81
81
  ? `${plural(live, 'client')} connected right now.`
82
- : 'No client is connected right now.' }), _jsx(Button, { size: "small", leadingVisual: PulseIcon, onClick: () => navigate(routes.dashboard), children: "What is going on" })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "The endpoint" }), _jsx(Copyable, { text: endpoint || 'https://mcp.datalayer.run/mcp' }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(KeyIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: signedIn
82
+ : 'No client is connected right now.' }), _jsx(Button, { size: "small", leadingVisual: PulseIcon, onClick: () => navigate(routes.dashboard), children: "What is going on" })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "The endpoint" }), _jsx(Copyable, { compact: true, text: endpoint || 'https://mcp.datalayer.run/mcp' }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(KeyIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: signedIn
83
83
  ? 'You are signed in, so a client you set up from here authorizes through OAuth without a shared token.'
84
84
  : 'Sign in to authorize a client through OAuth; without it a client can only use a personal access token.' })] })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Set up a client" }), _jsx(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'], children: MCP_CLIENTS.map(client => (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, minWidth: 0, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: client.name }), _jsx(Copyable, { text: mcpSetupCommand(client.setup) }), _jsx(Link, { href: `${clientDocsBase}/${client.setup}`, sx: { fontSize: 0, cursor: 'pointer', textAlign: 'left' }, onClick: event => {
85
85
  // A modified click is the reader asking for a new tab or
@@ -91,10 +91,10 @@ export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitl
91
91
  }
92
92
  event.preventDefault();
93
93
  onOpenDocs(`${clientDocsBase}/${client.setup}`);
94
- }, children: "Configuration and authorization" })] }, client.setup))) })] })] })), showSummaries && (_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'], children: [_jsx(Summary, { icon: AiAgentIcon, title: "Agents", to: routes.agents, onOpen: navigate, action: {
95
- label: 'Connected agents',
96
- onClick: () => navigate(routes.agents),
97
- }, children: connected.length === 0 ? (_jsx(Text, { children: "No agent has been authorized yet. Set up a client above and approve the scopes it asks for." })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [plural(connected.length, 'agent'), " authorized."] }), connected.slice(0, 3).map(agent => (_jsxs(Text, { children: [agent.clientName || agent.clientId, " \u2014", ' ', agent.lastUsedAt
94
+ }, children: "Configuration and authorization" })] }, client.setup))) })] })] })), showSummaries && (_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'], children: [_jsx(Summary, { icon: AiAgentIcon, title: "Clients", to: routes.clients, onOpen: navigate, action: {
95
+ label: 'Authorized clients',
96
+ onClick: () => navigate(routes.clients),
97
+ }, children: connected.length === 0 ? (_jsx(Text, { children: "No client has been authorized yet. Set up a client above and approve the scopes it asks for." })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [plural(connected.length, 'client'), " authorized."] }), connected.slice(0, 3).map(agent => (_jsxs(Text, { children: [agent.clientName || agent.clientId, " \u2014", ' ', agent.lastUsedAt
98
98
  ? `used ${timeAgo(agent.lastUsedAt)}`
99
99
  : 'never used'] }, agent.uid)))] })) }), _jsx(Summary, { icon: PulseIcon, title: "Runs", to: routes.runs, onOpen: navigate, action: { label: 'All runs', onClick: () => navigate(routes.runs) }, children: runs.length === 0 ? (_jsx(Text, { children: "Nothing has run yet. A tool call that outlives its request becomes a run you can watch and cancel." })) : (runs.map(task => (_jsxs(Text, { children: [task.tool, " \u2014 ", task.status, " ", timeAgo(task.lastUpdatedAt)] }, task.uid)))) }), _jsxs(Summary, { icon: TelescopeIcon, title: "Observability", to: routes.observability, onOpen: navigate, action: {
100
100
  label: 'Runs and metrics',
@@ -106,7 +106,17 @@ export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitl
106
106
  : 'Latency not measured yet.' }), _jsx(Text, { children: metrics.data?.slis.availability === null ||
107
107
  metrics.data?.slis.availability === undefined
108
108
  ? 'Error rate not measured yet.'
109
- : `${((1 - metrics.data.slis.availability) * 100).toFixed(1)}% of calls failed.` })] }), _jsx(Summary, { icon: ToolsIcon, title: "Tools", children: _jsx(Text, { children: "The toolsets an agent may execute \u2014 the Contents sources you have enabled for it, their tools, their approval policy and their health \u2014 are managed here once toolset bindings ship." }) }), _jsx(Summary, { icon: RssIcon, title: "Reach", children: _jsx(Text, { children: "Which notebooks and sources an agent can actually reach, as a list you can read at a glance, arrives with sharing to agents as principals." }) }), _jsx(Summary, { icon: ShieldCheckIcon, title: "Policies", to: routes.policies, onOpen: navigate, action: {
109
+ : `${((1 - metrics.data.slis.availability) * 100).toFixed(1)}% of calls failed.` })] }), _jsx(Summary, { icon: ToolsIcon, title: "Tools", to: routes.tools, onOpen: navigate, action: routes.tools
110
+ ? {
111
+ label: 'What each agent may execute',
112
+ onClick: () => navigate(routes.tools),
113
+ }
114
+ : undefined, children: _jsx(Text, { children: "The toolsets each of your agents may execute, with their tools, their approval policy and their health." }) }), _jsx(Summary, { icon: RssIcon, title: "Reach", to: routes.reach, onOpen: navigate, action: routes.reach
115
+ ? {
116
+ label: 'What each agent reaches',
117
+ onClick: () => navigate(routes.reach),
118
+ }
119
+ : undefined, children: _jsx(Text, { children: "The notebooks and sources each of your agents can actually reach, and whether it may read, edit or run them." }) }), _jsx(Summary, { icon: ShieldCheckIcon, title: "Policies", to: routes.policies, onOpen: navigate, action: {
110
120
  label: 'What applies',
111
121
  onClick: () => navigate(routes.policies),
112
122
  }, children: _jsx(Text, { children: "What your agents are allowed to do, and which layer decided each rule." }) })] }))] }));
@@ -24,6 +24,8 @@ import type { JSX } from 'react';
24
24
  import type { McpWorkflowsHealth } from '../../api/mcp/operations';
25
25
  import { type McpErrorStateFn, type McpRoutes } from './types';
26
26
  export type McpObservabilityPane = 'runs' | 'live' | 'metrics';
27
+ /** Every pane, in the order the switcher shows them. */
28
+ export declare const ALL_PANES: readonly McpObservabilityPane[];
27
29
  export interface McpObservabilityProps {
28
30
  /** The application's words for a failed request. */
29
31
  errorState: McpErrorStateFn;
@@ -32,6 +34,13 @@ export interface McpObservabilityProps {
32
34
  /** Which pane is open; the address owns it. */
33
35
  pane: McpObservabilityPane;
34
36
  onPaneChange: (pane: McpObservabilityPane) => void;
37
+ /**
38
+ * The panes offered, in this order; all three when unsaid. The Live pane
39
+ * reads the platform's own engine and is a platform administrator's
40
+ * (the user, 6 November 2026): an application shows an account its runs
41
+ * and metrics, and its admin page the live engine.
42
+ */
43
+ panes?: readonly McpObservabilityPane[];
35
44
  /** The run to open, when the address names one. */
36
45
  taskUid?: string;
37
46
  /** The trace to open — what a synchronous call leaves behind. */
@@ -67,13 +76,23 @@ export declare const stageOf: (name: string) => string;
67
76
  * second is usually a problem with the network between them, and sending
68
77
  * somebody to the wrong one costs an hour.
69
78
  *
70
- * A healthy engine with a backlog is still healthy, and says so with the
71
- * backlog beside it: a queue is a queue, not a fault.
79
+ * An engine taking work is healthy; a queue it was told to run that nobody
80
+ * serves is drawn as a warning beside it — work sent there is accepted and
81
+ * never done. The engine reports no backlog of its own (DBOS's pending and
82
+ * enqueued are both `running`), so what is running is counted among the
83
+ * newest runs it lists, and said as such.
72
84
  */
73
85
  export declare const engineLook: (health: McpWorkflowsHealth | undefined, failed: boolean) => {
74
86
  label: string;
75
87
  variant: "success" | "attention" | "danger" | "secondary";
76
88
  note: string;
77
89
  };
78
- export declare const McpObservability: ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle, }: McpObservabilityProps) => JSX.Element;
90
+ /** Each queue the engine polls, with how many of its newest runs are running there and the oldest of them. */
91
+ export declare const engineQueues: (health: McpWorkflowsHealth | undefined, now?: number) => {
92
+ name: string;
93
+ running: number;
94
+ oldestSeconds?: number;
95
+ served: boolean;
96
+ }[];
97
+ export declare const McpObservability: ({ errorState, routes, pane, onPaneChange, panes, taskUid, traceId, onSelect, agent, org, showTitle, }: McpObservabilityProps) => JSX.Element;
79
98
  export default McpObservability;
@@ -10,6 +10,17 @@ import { TraceTimeline } from './TraceTimeline.js';
10
10
  import { useNavigate } from '../../hooks/index.js';
11
11
  import { MCP_METRIC_CATALOG } from '../../api/mcp/index.js';
12
12
  import { durationLabel, timeAgo } from './format.js';
13
+ /** Every pane, in the order the switcher shows them. */
14
+ export const ALL_PANES = [
15
+ 'runs',
16
+ 'live',
17
+ 'metrics',
18
+ ];
19
+ const PANE_LABELS = {
20
+ runs: 'Runs',
21
+ live: 'Live',
22
+ metrics: 'Metrics',
23
+ };
13
24
  /**
14
25
  * What part of the platform a span belongs to, from its name.
15
26
  *
@@ -71,8 +82,11 @@ const Sli = ({ label, value, note, }) => (_jsxs(Card, { border: true, shadow: "n
71
82
  * second is usually a problem with the network between them, and sending
72
83
  * somebody to the wrong one costs an hour.
73
84
  *
74
- * A healthy engine with a backlog is still healthy, and says so with the
75
- * backlog beside it: a queue is a queue, not a fault.
85
+ * An engine taking work is healthy; a queue it was told to run that nobody
86
+ * serves is drawn as a warning beside it — work sent there is accepted and
87
+ * never done. The engine reports no backlog of its own (DBOS's pending and
88
+ * enqueued are both `running`), so what is running is counted among the
89
+ * newest runs it lists, and said as such.
76
90
  */
77
91
  export const engineLook = (health, failed) => {
78
92
  if (failed) {
@@ -85,26 +99,58 @@ export const engineLook = (health, failed) => {
85
99
  if (!health) {
86
100
  return { label: 'Asking…', variant: 'secondary', note: '' };
87
101
  }
88
- const backlog = (health.queues ?? []).reduce((total, queue) => total + (queue.backlog ?? 0), 0);
89
- if (!health.healthy) {
102
+ if (!health.durable) {
90
103
  return {
91
104
  label: 'Unhealthy',
92
105
  variant: 'danger',
93
- note: health.detail || 'The engine reports itself unhealthy.',
106
+ note: health.detail || 'The engine reports it is not taking work.',
94
107
  };
95
108
  }
109
+ const unserved = health.queuesNobodyServes ?? [];
110
+ if (unserved.length > 0) {
111
+ return {
112
+ label: 'Healthy',
113
+ variant: 'attention',
114
+ note: `Nobody serves ${unserved.join(', ')}: work sent there is accepted and never done.`,
115
+ };
116
+ }
117
+ const running = (health.runs ?? []).filter(run => run.status === 'running').length;
96
118
  return {
97
119
  label: 'Healthy',
98
- variant: backlog > 0 ? 'attention' : 'success',
99
- note: backlog > 0
100
- ? `${backlog} ${backlog === 1 ? 'run is' : 'runs are'} waiting to be picked up.`
101
- : 'Nothing is waiting to be picked up.',
120
+ variant: 'success',
121
+ note: running > 0
122
+ ? `${running} of its newest runs ${running === 1 ? 'is' : 'are'} running.`
123
+ : 'None of its newest runs is running.',
102
124
  };
103
125
  };
126
+ /** Each queue the engine polls, with how many of its newest runs are running there and the oldest of them. */
127
+ export const engineQueues = (health, now = Date.now()) => {
128
+ if (!health)
129
+ return [];
130
+ const unserved = health.queuesNobodyServes ?? [];
131
+ const names = [
132
+ ...(health.queues ?? []),
133
+ ...unserved.filter(name => !(health.queues ?? []).includes(name)),
134
+ ];
135
+ return names.map(name => {
136
+ const running = (health.runs ?? []).filter(run => run.queue === name && run.status === 'running');
137
+ const started = running
138
+ .map(run => run.startedAt)
139
+ .filter((at) => typeof at === 'number');
140
+ return {
141
+ name,
142
+ running: running.length,
143
+ oldestSeconds: started.length
144
+ ? Math.max(0, Math.round(now / 1000 - Math.min(...started)))
145
+ : undefined,
146
+ served: !unserved.includes(name),
147
+ };
148
+ });
149
+ };
104
150
  const percentLabel = (value) => value === null || value === undefined
105
151
  ? 'Not measured'
106
152
  : `${(value * 100).toFixed(1)}%`;
107
- export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle = true, }) => {
153
+ export const McpObservability = ({ errorState, routes, pane, onPaneChange, panes = ALL_PANES, taskUid, traceId, onSelect, agent, org, showTitle = true, }) => {
108
154
  const navigate = useNavigate();
109
155
  const [find, setFind] = useState(taskUid ?? traceId ?? '');
110
156
  // One of the two, never both: a task names its own trace, and a trace
@@ -120,7 +166,7 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
120
166
  const engine = useMcpWorkflowsHealth({ enabled: pane === 'live' });
121
167
  const live = useTasks({ status: 'working' }, { enabled: pane === 'live' });
122
168
  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."] }));
123
- return (_jsxs(Box, { 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, { display: "grid", gap: 3, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(TextInput, { size: "small", leadingVisual: SearchIcon, "aria-label": "Task or trace id", placeholder: "Task uid or trace id", value: find, sx: { minWidth: 320 }, onChange: event => setFind(event.currentTarget.value) }), _jsx(Button, { size: "small", onClick: () => {
169
+ return (_jsxs(Box, { 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." })] })), panes.length > 1 && (_jsx(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: panes.map(each => (_jsx(SegmentedControl.Button, { selected: pane === each, onClick: () => onPaneChange(each), children: PANE_LABELS[each] }, each))) })), telemetryNotice, pane === 'runs' && (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(TextInput, { size: "small", leadingVisual: SearchIcon, "aria-label": "Task or trace id", placeholder: "Task uid or trace id", value: find, sx: { minWidth: 320 }, onChange: event => setFind(event.currentTarget.value) }), _jsx(Button, { size: "small", onClick: () => {
124
170
  const value = find.trim();
125
171
  if (!value) {
126
172
  onSelect({});
@@ -131,16 +177,18 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
131
177
  onSelect(/^[0-9a-f]{32}$/i.test(value)
132
178
  ? { trace: value }
133
179
  : { task: value });
134
- }, children: "Show the run" })] }), !taskUid && !traceId ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Pick a run" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Open a call from the audit log or the dashboard, or paste its task uid or trace id here, and its spans across the gateway, the policy check and the worker appear with the time each took." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.audit), children: "Open the audit log" })] })) : run.isError ? (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'This run'), onRetry: () => run.refetch() })) : run.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (run.data?.spans.length ?? 0) === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No span for this call" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Spans reach the collector a few seconds after the call and are kept for a short time. A call older than the retention window has an audit row but no trace." }) })] })) : (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", 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(Card, { border: true, shadow: "none", display: "block", overflowX: "auto", children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })] }))] })), pane === 'live' && (_jsxs(Box, { display: "grid", gap: 3, children: [(() => {
135
- const look = engineLook(engine.data, engine.isError);
136
- const queues = engine.data?.queues ?? [];
137
- return (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", 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, { display: "grid", gap: 1, children: queues.map(queue => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", fontSize: 0, 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))) }))] }));
138
- })(), _jsxs(Box, { 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, { 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, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Label, { size: "small", variant: "success", children: "Working" }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' }), _jsx(Text, { sx: {
180
+ }, children: "Show the run" })] }), !taskUid && !traceId ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Pick a run" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Open a call from the audit log or the dashboard, or paste its task uid or trace id here, and its spans across the gateway, the policy check and the worker appear with the time each took." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.audit), children: "Open the audit log" })] })) : run.isError ? (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'This run'), onRetry: () => run.refetch() })) : run.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (run.data?.spans.length ?? 0) === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No span for this call" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Spans reach the collector a few seconds after the call and are kept for a short time. A call older than the retention window has an audit row but no trace." }) })] })) : (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", 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(Card, { border: true, shadow: "none", display: "block", overflowX: "auto", children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })] }))] })), pane === 'live' && (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Card, { border: true, shadow: "none", p: 3, 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, { 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, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Label, { size: "small", variant: "success", children: "Working" }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' }), _jsx(Text, { sx: {
139
181
  fontSize: 0,
140
182
  fontFamily: 'mono',
141
183
  color: 'accent.fg',
142
184
  cursor: 'pointer',
143
- }, 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, { display: "grid", gap: 3, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: [
185
+ }, onClick: () => onSelect({ task: task.uid }), children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["started ", timeAgo(task.createdAt)] })] }, task.uid))))] }), (() => {
186
+ const look = engineLook(engine.data, engine.isError);
187
+ const queues = engineQueues(engine.data);
188
+ return (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", 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, { display: "grid", gap: 1, children: queues.map(queue => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", fontSize: 0, children: [_jsx(Text, { sx: { fontFamily: 'mono' }, children: queue.name }), queue.served ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: queue.running > 0
189
+ ? `${queue.running} running`
190
+ : 'idle' })) : (_jsx(Label, { size: "small", variant: "attention", children: "nobody serves it" })), typeof queue.oldestSeconds === 'number' && (_jsxs(Text, { sx: { color: 'fg.muted' }, children: ["oldest ", durationLabel(queue.oldestSeconds * 1000)] }))] }, queue.name))) }))] }));
191
+ })()] })), pane === 'metrics' && (_jsx(Box, { display: "grid", gap: 3, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: [
144
192
  '1fr',
145
193
  'repeat(2, 1fr)',
146
194
  'repeat(4, 1fr)',
@@ -157,7 +157,7 @@ export const Policies = ({ errorState, routes, organizationName, showTitle = tru
157
157
  }
158
158
  return (_jsxs(Box, { display: "grid", gap: 4, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: previewAs
159
159
  ? 'What applies to this agent, and which layer decided it.'
160
- : 'What applies to your agents, and which layer decided it.' })] })), (agents.data?.length ?? 0) > 1 && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Preview as" }), _jsx(Box, { minWidth: "18rem", children: _jsxs(Select, { value: previewAs, onChange: event => setPreviewAs(event.target.value), "aria-label": "Preview the policy as", children: [_jsx(Select.Option, { value: "", children: "You \u2014 your own token" }), (agents.data ?? []).map(agent => (_jsx(Select.Option, { value: agent.clientId, children: agent.clientName || agent.clientId }, agent.uid)))] }) }), previewAs && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "A grant carries its own scopes and its own admitted clients, so this can differ from what applies to you." }))] })), policy.isPending && !policy.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 6, children: _jsx(Spinner, {}) })) : rows.length === 0 && tools.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(ShieldCheckIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The defaults apply" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No agent is connected yet, so nothing narrows the Datalayer defaults. Connect a client and this page fills with what its calls are allowed to do." }) }), _jsx(Link, { as: "button", sx: { cursor: 'pointer' }, onClick: () => navigate(routes.access), children: "Set up a client" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "What applies to your agents" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Scope of this reading: ", policy.data?.scope ?? 'personal', policy.data?.evaluatedAt
160
+ : 'What applies to your agents, and which layer decided it.' })] })), (agents.data?.length ?? 0) > 1 && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap", children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Preview as" }), _jsx(Box, { minWidth: "18rem", children: _jsxs(Select, { value: previewAs, onChange: event => setPreviewAs(event.target.value), "aria-label": "Preview the policy as", children: [_jsx(Select.Option, { value: "", children: "You \u2014 your own token" }), (agents.data ?? []).map(agent => (_jsx(Select.Option, { value: agent.clientId, children: agent.clientName || agent.clientId }, agent.uid)))] }) }), previewAs && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "A grant carries its own scopes and its own admitted clients, so this can differ from what applies to you." }))] })), policy.isPending && !policy.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 6, children: _jsx(Spinner, {}) })) : rows.length === 0 && tools.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(ShieldCheckIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The defaults apply" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No agent is connected yet, so nothing narrows the Datalayer defaults. Connect a client and this page fills with what its calls are allowed to do." }) }), _jsx(Link, { as: "button", sx: { cursor: 'pointer' }, onClick: () => navigate(routes.configuration), children: "Set up a client" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "What applies to your agents" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Scope of this reading: ", policy.data?.scope ?? 'personal', policy.data?.evaluatedAt
161
161
  ? ` · evaluated ${policy.data.evaluatedAt}`
162
162
  : ''] })] }), _jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h4", id: "effective-policy", children: "Effective policy" }), _jsx(Table.Subtitle, { as: "p", id: "effective-policy-subtitle", children: "Each layer only narrows the one above it, so the strictest rule is what applies." }), _jsxs(Box, { as: "table", "aria-labelledby": "effective-policy", "aria-describedby": "effective-policy-subtitle", width: "100%", borderCollapse: "collapse", children: [_jsx(Box, { as: "thead", children: _jsx(Box, { as: "tr", children: ['Rule', 'What applies', 'Decided by'].map(header => (_jsx(Box, { as: "th", textAlign: "left", fontSize: 0, color: "fg.muted", fontWeight: "semibold", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.default", children: header }, header))) }) }), _jsx(Box, { as: "tbody", children: rows.map(row => (_jsxs(Box, { as: "tr", children: [_jsxs(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", children: [_jsx(Text, { sx: { fontSize: 1, display: 'block' }, children: row.label }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] }), _jsx(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", children: _jsx(Text, { sx: { fontSize: 1 }, children: ruleValueLabel(row.rule.value) }) }), _jsxs(Box, { as: "td", py: 2, px: 2, borderBottom: "1px solid", borderColor: "border.muted", verticalAlign: "top", whiteSpace: "nowrap", children: [_jsx(Label, { size: "small", variant: LAYER_VARIANT[row.rule.decidedBy], children: layerLabel(row.rule.decidedBy, organizationName) }), row.rule.reason && (_jsx(Text, { sx: {
163
163
  fontSize: 0,