@datalayer/core 1.2.17 → 1.2.19

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 (122) hide show
  1. package/lib/cli-login.js +5 -51
  2. package/lib/components/anonymous/AnonymousKeyExpired.js +1 -15
  3. package/lib/components/anonymous/AnonymousKeyTimer.js +5 -15
  4. package/lib/components/auth/SignIn.js +2 -2
  5. package/lib/components/avatars/UserAvatar.js +3 -27
  6. package/lib/components/billing/BillingEntitySelect.js +6 -48
  7. package/lib/components/checkout/StripeCheckout.js +33 -217
  8. package/lib/components/contents/ContentSourceCard.js +2 -11
  9. package/lib/components/contents/HomeFolderObjectsBrowser.d.ts +1 -1
  10. package/lib/components/contents/HomeFolderObjectsBrowser.js +11 -35
  11. package/lib/components/display/CenteredSpinner.js +1 -6
  12. package/lib/components/display/DatalayerBox.js +2 -14
  13. package/lib/components/flashes/FlashClosable.js +2 -17
  14. package/lib/components/mcp/ClientBadge.js +1 -1
  15. package/lib/components/mcp/McpErrorBlankslate.js +1 -1
  16. package/lib/components/mcp/ScopeList.js +1 -1
  17. package/lib/components/notebooks/JupyterNotebook.js +1 -2
  18. package/lib/components/principal/Principal.js +2 -10
  19. package/lib/components/principal/PrincipalAppearance.js +19 -63
  20. package/lib/components/principal/PrincipalAvatar.js +5 -25
  21. package/lib/components/principal/PrincipalBadge.js +3 -13
  22. package/lib/components/principal/PrincipalBanner.js +4 -32
  23. package/lib/components/principal/PrincipalDetailsOverlay.js +7 -23
  24. package/lib/components/principal/PrincipalHoverCard.js +15 -18
  25. package/lib/components/principal/PrincipalSwitcherMenu.js +15 -83
  26. package/lib/components/progress/ConsumptionBar.js +1 -1
  27. package/lib/components/sharing/ShareAccessComponent.js +21 -229
  28. package/lib/components/sharing/SharingEditor.js +1 -1
  29. package/lib/components/spaces/SpaceDetailsOverlay.js +4 -35
  30. package/lib/components/spaces/SpaceDisplay.js +1 -8
  31. package/lib/components/storage/ContentsBrowser.js +3 -26
  32. package/lib/components/storage/ContentsTable.js +4 -23
  33. package/lib/components/students/StudentResultCircle.js +7 -29
  34. package/lib/components/timeline/Timeline.js +4 -36
  35. package/lib/hooks/layouts/LayoutBackdrop.js +2 -1
  36. package/lib/hooks/layouts/LayoutScreencapture.js +1 -1
  37. package/lib/hooks/libraryApp.d.ts +6 -1
  38. package/lib/hooks/libraryApp.js +10 -1
  39. package/lib/hooks/useAuthorization.js +6 -1
  40. package/lib/mocks/components/FlashMock.js +3 -8
  41. package/lib/mocks/views/DashboardMock.js +6 -72
  42. package/lib/mocks/views/FormMock.js +3 -2
  43. package/lib/mocks/views/TableMock.js +3 -2
  44. package/lib/mocks/views/WipMock.js +2 -1
  45. package/lib/models/Person.d.ts +9 -0
  46. package/lib/models/Person.js +23 -0
  47. package/lib/otel/index.d.ts +4 -0
  48. package/lib/otel/index.js +3 -0
  49. package/lib/otel/live/filter.d.ts +15 -0
  50. package/lib/otel/live/filter.js +16 -0
  51. package/lib/otel/live/index.d.ts +10 -0
  52. package/lib/otel/live/index.js +14 -0
  53. package/lib/otel/live/tracer.d.ts +94 -0
  54. package/lib/otel/live/tracer.js +223 -0
  55. package/lib/otel/live/useOtelLiveSpans.d.ts +6 -0
  56. package/lib/otel/live/useOtelLiveSpans.js +22 -0
  57. package/lib/otel/types.d.ts +17 -0
  58. package/lib/otel/views/OtelLive.js +4 -62
  59. package/lib/otel/views/OtelLiveSpans.d.ts +52 -0
  60. package/lib/otel/views/OtelLiveSpans.js +114 -0
  61. package/lib/otel/views/OtelLogsList.js +13 -64
  62. package/lib/otel/views/OtelMetricsChart.js +4 -19
  63. package/lib/otel/views/OtelMetricsList.js +8 -77
  64. package/lib/otel/views/OtelSearchBar.js +3 -12
  65. package/lib/otel/views/OtelSpanDetail.d.ts +6 -0
  66. package/lib/otel/views/OtelSpanDetail.js +31 -81
  67. package/lib/otel/views/OtelSpanTree.js +7 -57
  68. package/lib/otel/views/OtelSqlView.js +5 -99
  69. package/lib/otel/views/OtelSystemView.js +4 -34
  70. package/lib/otel/views/OtelTimeline.js +7 -37
  71. package/lib/otel/views/OtelTimelineRangeSlider.js +7 -140
  72. package/lib/otel/views/OtelTracesList.js +39 -87
  73. package/lib/otel/views/index.d.ts +3 -0
  74. package/lib/otel/views/index.js +2 -0
  75. package/lib/portable/index.d.ts +8 -0
  76. package/lib/portable/index.js +12 -0
  77. package/lib/state/substates/NavigationState.d.ts +6 -4
  78. package/lib/state/substates/NavigationState.js +6 -5
  79. package/lib/views/api-keys/APIKeyEdit.js +2 -11
  80. package/lib/views/api-keys/APIKeyNew.js +3 -19
  81. package/lib/views/api-keys/APIKeys.js +6 -11
  82. package/lib/views/api-keys/APIKeysStandalone.js +1 -1
  83. package/lib/views/datasources/DatasourceDetail.d.ts +1 -1
  84. package/lib/views/datasources/DatasourceDetail.js +24 -17
  85. package/lib/views/datasources/DatasourceNew.js +2 -6
  86. package/lib/views/datasources/Datasources.js +2 -14
  87. package/lib/views/iam/SignInSimple.js +8 -64
  88. package/lib/views/mcp/AdmittedClients.js +3 -16
  89. package/lib/views/mcp/AlertDestinations.js +2 -10
  90. package/lib/views/mcp/AlertRules.js +4 -9
  91. package/lib/views/mcp/ApprovalQueue.js +4 -21
  92. package/lib/views/mcp/AuditLog.js +7 -44
  93. package/lib/views/mcp/ConnectedAgents.js +1 -6
  94. package/lib/views/mcp/EnterpriseConsole.js +10 -23
  95. package/lib/views/mcp/IdentityProviders.js +7 -31
  96. package/lib/views/mcp/McpDashboard.js +14 -30
  97. package/lib/views/mcp/McpHome.js +7 -54
  98. package/lib/views/mcp/McpObservability.js +13 -85
  99. package/lib/views/mcp/NotebookRuns.js +5 -12
  100. package/lib/views/mcp/OrganizationPolicy.js +3 -9
  101. package/lib/views/mcp/OrganizationUsage.js +4 -11
  102. package/lib/views/mcp/PersonalPolicy.js +3 -3
  103. package/lib/views/mcp/Policies.js +5 -46
  104. package/lib/views/mcp/PolicyForm.js +1 -9
  105. package/lib/views/mcp/PolicyHistory.js +2 -8
  106. package/lib/views/mcp/RunDetail.js +3 -25
  107. package/lib/views/mcp/ScimProvisioning.js +3 -15
  108. package/lib/views/mcp/ServiceAgents.js +4 -17
  109. package/lib/views/mcp/TeamPolicies.js +7 -19
  110. package/lib/views/mcp/ToolAccess.js +15 -22
  111. package/lib/views/mcp/TraceTimeline.js +8 -23
  112. package/lib/views/otel/DashboardView.js +2 -2
  113. package/lib/views/otel/LogsView.js +2 -2
  114. package/lib/views/otel/MetricsView.js +2 -2
  115. package/lib/views/otel/OtelHeader.js +6 -40
  116. package/lib/views/otel/SqlView.js +2 -2
  117. package/lib/views/otel/SystemView.js +2 -2
  118. package/lib/views/otel/TracesView.js +2 -2
  119. package/lib/views/profile/UserBadge.js +6 -98
  120. package/lib/views/secrets/SecretEdit.js +2 -11
  121. package/lib/views/secrets/SecretNew.js +1 -1
  122. package/package.json +1 -1
@@ -69,29 +69,22 @@ const QuotaCard = ({ label, hint, quota, }) => {
69
69
  const limit = quota?.limit;
70
70
  const used = quota?.used;
71
71
  const fraction = quota?.fraction;
72
- return (_jsxs(Box, { sx: {
73
- border: '1px solid',
74
- borderColor: 'border.default',
75
- borderRadius: 2,
76
- p: 3,
77
- flex: '1 1 220px',
78
- minWidth: 0,
79
- }, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: label }), _jsx(Box, { sx: { mt: 2, display: 'flex', alignItems: 'baseline', gap: 2 }, children: quota?.unknown ? (_jsx(Text, { sx: { fontSize: 2, color: 'fg.muted' }, children: "Not available" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 4, fontWeight: 'bold' }, children: typeof used === 'number' ? amount(used) : '—' }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: typeof limit === 'number'
72
+ return (_jsxs(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 3, flex: "1 1 220px", minWidth: 0, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: label }), _jsx(Box, { mt: 2, display: "flex", alignItems: "baseline", gap: 2, children: quota?.unknown ? (_jsx(Text, { sx: { fontSize: 2, color: 'fg.muted' }, children: "Not available" })) : (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { fontSize: 4, fontWeight: 'bold' }, children: typeof used === 'number' ? amount(used) : '—' }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: typeof limit === 'number'
80
73
  ? `of ${amount(limit)}`
81
- : 'no limit set' })] })) }), typeof fraction === 'number' && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(ProgressBar, { progress: Math.min(100, fraction * 100), barSize: "small", bg: `${quotaTone(fraction)}.emphasis`, "aria-label": `${label}: ${Math.round(fraction * 100)}% of the limit` }) })), _jsx(Text, { as: "p", sx: { mt: 2, mb: 0, fontSize: 0, color: 'fg.muted' }, children: quota?.unknown ? quota.unknown : hint })] }));
74
+ : 'no limit set' })] })) }), typeof fraction === 'number' && (_jsx(Box, { mt: 2, children: _jsx(ProgressBar, { progress: Math.min(100, fraction * 100), barSize: "small", bg: `${quotaTone(fraction)}.emphasis`, "aria-label": `${label}: ${Math.round(fraction * 100)}% of the limit` }) })), _jsx(Text, { as: "p", sx: { mt: 2, mb: 0, fontSize: 0, color: 'fg.muted' }, children: quota?.unknown ? quota.unknown : hint })] }));
82
75
  };
83
76
  export const OrganizationUsage = ({ errorState, orgUid, teamUid, showTitle = true, }) => {
84
77
  const usage = useOrgMcpUsage(orgUid, teamUid ? { team: teamUid } : {});
85
78
  const rows = useMemo(() => spendRowsOf(usage.data?.byAgent ?? []), [usage.data]);
86
79
  if (usage.isLoading) {
87
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 6 }, children: _jsx(Spinner, {}) }));
80
+ return (_jsx(Box, { display: "flex", justifyContent: "center", p: 6, children: _jsx(Spinner, {}) }));
88
81
  }
89
82
  if (usage.isError) {
90
83
  return (_jsx(McpErrorBlankslate, { state: errorState(usage.error, 'The usage'), onRetry: () => void usage.refetch() }));
91
84
  }
92
85
  const quotas = usage.data?.quotas ?? {};
93
86
  const breakdown = breakdownState(usage.data);
94
- return (_jsxs(Box, { children: [showTitle && (_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Usage" })), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0, mb: 3 }, children: "What this organization's agents used over the last day, against what they may use. Limits are set in the organization's MCP policy, not here." }), _jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 3 }, children: QUOTAS.map(quota => (_jsx(QuotaCard, { label: quota.label, hint: quota.hint, quota: quotas[quota.key] }, quota.key))) }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, mt: 4, mb: 2 }, children: "Credits by agent" }), breakdown === 'unreadable' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The day's spend could not be read" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "A breakdown of a total nobody could read would make every agent look cheap. Try again in a moment." }) })] })) : breakdown === 'empty' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent spent credits today" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "Work a person ran themselves is in the total above, and is not an agent's." }) })] })) : (_jsx(Table.Container, { children: _jsx(DataTable, { "aria-labelledby": "usage-by-agent", data: rows, columns: [
87
+ return (_jsxs(Box, { children: [showTitle && (_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Usage" })), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0, mb: 3 }, children: "What this organization's agents used over the last day, against what they may use. Limits are set in the organization's MCP policy, not here." }), _jsx(Box, { display: "flex", flexWrap: "wrap", gap: 3, children: QUOTAS.map(quota => (_jsx(QuotaCard, { label: quota.label, hint: quota.hint, quota: quotas[quota.key] }, quota.key))) }), _jsx(Heading, { as: "h3", sx: { fontSize: 2, mt: 4, mb: 2 }, children: "Credits by agent" }), breakdown === 'unreadable' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "The day's spend could not be read" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "A breakdown of a total nobody could read would make every agent look cheap. Try again in a moment." }) })] })) : breakdown === 'empty' ? (_jsxs(Blankslate, { border: true, children: [_jsx(Blankslate.Visual, { children: _jsx(GraphIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No agent spent credits today" }), _jsx(Blankslate.Description, { children: _jsx(Text, { as: "p", sx: { textAlign: 'center' }, children: "Work a person ran themselves is in the total above, and is not an agent's." }) })] })) : (_jsx(Table.Container, { children: _jsx(DataTable, { "aria-labelledby": "usage-by-agent", data: rows, columns: [
95
88
  {
96
89
  header: 'Agent',
97
90
  field: 'clientId',
@@ -109,12 +109,12 @@ export const PersonalPolicy = ({ errorState, userUid, organizations = [], showTi
109
109
  return (_jsx(McpErrorBlankslate, { state: errorState(layer.error, 'Your MCP policy'), onRetry: () => layer.refetch() }));
110
110
  }
111
111
  if (layer.isPending && layer.data === undefined) {
112
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) }));
112
+ return (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) }));
113
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
+ return (_jsxs(Box, { 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, { mt: 2, children: _jsx(Button, { size: "small", onClick: () => {
115
115
  setConflict('');
116
116
  layer.refetch();
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
+ }, 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, { 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, { marginLeft: "auto", children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
118
118
  onSuccess: () => enqueueToast('Removed. You narrow nothing of your own.', {
119
119
  variant: 'success',
120
120
  }),
@@ -108,7 +108,7 @@ export const ruleValueLabel = (value) => {
108
108
  return String(value);
109
109
  };
110
110
  /** The tools of one access kind, and what happens before they run. */
111
- const ToolGroup = ({ title, description, tools, organizationName, }) => (_jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsxs(Box, { children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', display: 'block' }, children: title }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: description })] }), tools.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "No tool of this kind is available to your agents." })) : (_jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: tools.map(tool => (_jsx(Label, { size: "small", variant: !tool.allowed
111
+ const ToolGroup = ({ title, description, tools, organizationName, }) => (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', display: 'block' }, children: title }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: description })] }), tools.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "No tool of this kind is available to your agents." })) : (_jsx(Box, { display: "flex", gap: 1, flexWrap: "wrap", children: tools.map(tool => (_jsx(Label, { size: "small", variant: !tool.allowed
112
112
  ? 'danger'
113
113
  : tool.approval === 'explicit'
114
114
  ? 'attention'
@@ -155,56 +155,15 @@ export const Policies = ({ errorState, routes, organizationName, showTitle = tru
155
155
  if (policy.isError) {
156
156
  return (_jsx(McpErrorBlankslate, { state: errorState(policy.error, 'Policies'), onRetry: () => policy.refetch() }));
157
157
  }
158
- return (_jsxs(Box, { sx: { display: 'grid', gap: 4, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: previewAs
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, { sx: {
161
- display: 'flex',
162
- alignItems: 'center',
163
- gap: 2,
164
- flexWrap: 'wrap',
165
- }, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Preview as" }), _jsx(Box, { sx: { 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, { sx: { 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, { sx: { 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.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
166
161
  ? ` · evaluated ${policy.data.evaluatedAt}`
167
- : ''] })] }), _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", sx: { 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", sx: {
168
- textAlign: 'left',
169
- fontSize: 0,
170
- color: 'fg.muted',
171
- fontWeight: 'semibold',
172
- py: 2,
173
- px: 2,
174
- borderBottom: '1px solid',
175
- borderColor: 'border.default',
176
- }, children: header }, header))) }) }), _jsx(Box, { as: "tbody", children: rows.map(row => (_jsxs(Box, { as: "tr", children: [_jsxs(Box, { as: "td", sx: {
177
- py: 2,
178
- px: 2,
179
- borderBottom: '1px solid',
180
- borderColor: 'border.muted',
181
- verticalAlign: 'top',
182
- }, 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", sx: {
183
- py: 2,
184
- px: 2,
185
- borderBottom: '1px solid',
186
- borderColor: 'border.muted',
187
- verticalAlign: 'top',
188
- }, children: _jsx(Text, { sx: { fontSize: 1 }, children: ruleValueLabel(row.rule.value) }) }), _jsxs(Box, { as: "td", sx: {
189
- py: 2,
190
- px: 2,
191
- borderBottom: '1px solid',
192
- borderColor: 'border.muted',
193
- verticalAlign: 'top',
194
- whiteSpace: 'nowrap',
195
- }, children: [_jsx(Label, { size: "small", variant: LAYER_VARIANT[row.rule.decidedBy], children: layerLabel(row.rule.decidedBy, organizationName) }), row.rule.reason && (_jsx(Text, { sx: {
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: {
196
163
  fontSize: 0,
197
164
  color: 'fg.muted',
198
165
  display: 'block',
199
166
  mt: 1,
200
- }, children: row.rule.reason }))] })] }, row.name))) })] })] }), _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsx(ToolGroup, { title: "Read-only tools", description: "Tools that only look: listing, reading a cell, reading a source.", tools: readOnlyTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that change things", description: "Tools that write to a notebook, a document or a source.", tools: writeTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that launch or spend", description: "Tools that execute code, launch a sandbox or reserve compute.", tools: executeTools, organizationName: organizationName })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Your token carries ", policy.data?.scopes.length ?? 0, " scopes, which is the ceiling over every rule above:", ' ', (policy.data?.scopes ?? []).join(', ') || 'none', "."] })] })), _jsxs(Box, { sx: {
201
- p: 3,
202
- border: '1px solid',
203
- borderColor: 'border.default',
204
- borderRadius: 2,
205
- bg: 'canvas.subtle',
206
- display: 'grid',
207
- gap: 1,
208
- }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "Your own rules" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Narrowing the defaults for your own agents \u2014 approval by tool, a daily credits cap, a denylist, the environments they may use \u2014 is edited here once the policy layers ship. When your agents act inside an organization, that organization's policy applies instead of yours." }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', mt: 2 }, children: "Organizations you own" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The policy form for each organization you own, with its history and a preview of what one agent would get, is the same form the organization's MCP tab carries. It joins this page with the layers." })] })] }));
167
+ }, children: row.rule.reason }))] })] }, row.name))) })] })] }), _jsxs(Box, { display: "grid", gap: 3, children: [_jsx(ToolGroup, { title: "Read-only tools", description: "Tools that only look: listing, reading a cell, reading a source.", tools: readOnlyTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that change things", description: "Tools that write to a notebook, a document or a source.", tools: writeTools, organizationName: organizationName }), _jsx(ToolGroup, { title: "Tools that launch or spend", description: "Tools that execute code, launch a sandbox or reserve compute.", tools: executeTools, organizationName: organizationName })] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Your token carries ", policy.data?.scopes.length ?? 0, " scopes, which is the ceiling over every rule above:", ' ', (policy.data?.scopes ?? []).join(', ') || 'none', "."] })] })), _jsxs(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", display: "grid", gap: 1, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "Your own rules" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Narrowing the defaults for your own agents \u2014 approval by tool, a daily credits cap, a denylist, the environments they may use \u2014 is edited here once the policy layers ship. When your agents act inside an organization, that organization's policy applies instead of yours." }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold', mt: 2 }, children: "Organizations you own" }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The policy form for each organization you own, with its history and a preview of what one agent would get, is the same form the organization's MCP tab carries. It joins this page with the layers." })] })] }));
209
168
  };
210
169
  export default Policies;
@@ -101,13 +101,5 @@ export const rulesFrom = (draft) => {
101
101
  };
102
102
  export const policyChanged = (a, b) => Object.keys(EMPTY_POLICY_DRAFT).some(key => a[key] !== b[key]);
103
103
  const Note = ({ children, }) => children ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'attention.fg', mt: 1, mb: 0 }, children: children })) : null;
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: {
105
- display: 'grid',
106
- gap: 3,
107
- gridTemplateColumns: ['1fr', '1fr 1fr 1fr'],
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 })] })] })] }));
104
+ export const PolicyForm = ({ draft, onChange, disabled = false, notes = {}, belowAllowedClients, }) => (_jsxs(Box, { 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, { mt: 2, children: belowAllowedClients })] }), _jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', '1fr 1fr 1fr'], 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, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', '1fr 1fr'], 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, { 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 })] })] })] }));
113
105
  export default PolicyForm;
@@ -56,7 +56,7 @@ export const PolicyHistory = ({ orgUid, subjectUid, limit = 20, }) => {
56
56
  .slice(0, limit);
57
57
  }, [events.data, removals.data, subjectUid, limit]);
58
58
  if ((events.isPending || removals.isPending) && rows.length === 0) {
59
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 3 }, children: _jsx(Spinner, { size: "small" }) }));
59
+ return (_jsx(Box, { display: "flex", justifyContent: "center", py: 3, children: _jsx(Spinner, { size: "small" }) }));
60
60
  }
61
61
  if (events.isError || removals.isError) {
62
62
  return (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "The history could not be read. The policy above is unaffected." }));
@@ -64,12 +64,6 @@ export const PolicyHistory = ({ orgUid, subjectUid, limit = 20, }) => {
64
64
  if (rows.length === 0) {
65
65
  return (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: "No recorded change. Either this layer has not been edited, or this deployment routes identity records to a log pipeline rather than to the audit \u2014 an administrator can tell you which." }));
66
66
  }
67
- return (_jsx(Box, { sx: { display: 'grid', gap: 2 }, children: rows.map(event => (_jsxs(Box, { sx: {
68
- display: 'flex',
69
- gap: 2,
70
- alignItems: 'baseline',
71
- flexWrap: 'wrap',
72
- fontSize: 0,
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))) }));
67
+ return (_jsx(Box, { display: "grid", gap: 2, children: rows.map(event => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "baseline", flexWrap: "wrap", fontSize: 0, 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))) }));
74
68
  };
75
69
  export default PolicyHistory;
@@ -148,38 +148,16 @@ export const RunDetail = ({ taskUid, errorState, routes, mock = false, }) => {
148
148
  return (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'Run'), onRetry: () => run.refetch() }));
149
149
  }
150
150
  if (!mock && (run.isLoading || !run.data)) {
151
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 4 }, children: _jsx(Spinner, {}) }));
151
+ return (_jsx(Box, { display: "flex", justifyContent: "center", p: 4, children: _jsx(Spinner, {}) }));
152
152
  }
153
153
  const task = mock ? SAMPLE_RUN : run.data;
154
154
  const look = RUN_STATUS_LOOK[task.status];
155
155
  const over = isRunOver(task.status);
156
156
  const output = outputTextOf(task);
157
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [_jsxs(Box, { children: [_jsx(Link, { href: routes.runs, onClick: event => {
157
+ return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [_jsxs(Box, { children: [_jsx(Link, { href: routes.runs, onClick: event => {
158
158
  event.preventDefault();
159
159
  navigate(routes.runs);
160
- }, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { sx: {
161
- display: 'flex',
162
- gap: 2,
163
- alignItems: 'center',
164
- flexWrap: 'wrap',
165
- }, children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { sx: {
166
- borderWidth: 1,
167
- borderStyle: 'solid',
168
- borderColor: 'attention.emphasis',
169
- borderRadius: 2,
170
- p: 3,
171
- display: 'grid',
172
- gap: 2,
173
- }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsx(Box, { children: _jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }) })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", sx: {
174
- fontFamily: 'mono',
175
- fontSize: 0,
176
- whiteSpace: 'pre-wrap',
177
- overflowX: 'auto',
178
- bg: 'canvas.subtle',
179
- borderRadius: 2,
180
- p: 3,
181
- m: 0,
182
- }, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
160
+ }, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { borderWidth: 1, borderStyle: "solid", borderColor: "attention.emphasis", borderRadius: 2, p: 3, display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsx(Box, { children: _jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }) })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 3, m: 0, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
183
161
  ? 'This run printed nothing.'
184
162
  : 'Nothing printed yet. Output appears here as it arrives.' }))] }), task.error && (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Why it failed" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'danger.fg' }, children: task.error })] })), _jsx(TraceTimelineSection, { tree: mock ? SAMPLE_TRACE : (trace.data?.tree ?? []), traceId: mock ? SAMPLE_TRACE[0].span.trace_id : trace.data?.traceId, live: !over }), !over && (_jsxs(Box, { children: [_jsx(Button, { variant: "danger", disabled: cancel.isPending, onClick: () => cancel.mutate(task.uid), children: cancel.isPending ? 'Cancelling…' : 'Cancel this run' }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 2 }, children: "Cancelling interrupts the work itself, so the sandbox stops being billed for it. What it has already printed is kept." })] }))] }));
185
163
  };
@@ -112,7 +112,7 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
112
112
  header: 'Name',
113
113
  field: 'name',
114
114
  rowHeader: true,
115
- renderCell: row => (_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: row.name || 'SCIM' }), row.revoked && (_jsx(Label, { variant: "danger", size: "small", children: "Revoked" }))] })),
115
+ renderCell: row => (_jsxs(Box, { display: "grid", gap: 1, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: row.name || 'SCIM' }), row.revoked && (_jsx(Label, { variant: "danger", size: "small", children: "Revoked" }))] })),
116
116
  },
117
117
  {
118
118
  header: 'Last used',
@@ -142,19 +142,7 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
142
142
  return (_jsx(McpErrorBlankslate, { state: errorState(tokens.error, 'SCIM provisioning'), onRetry: () => tokens.refetch() }));
143
143
  }
144
144
  const shown = SCIM_STATUS_MESSAGES[status.state];
145
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { sx: {
146
- display: 'flex',
147
- alignItems: 'start',
148
- justifyContent: 'space-between',
149
- gap: 3,
150
- }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Directory provisioning" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Over SCIM. Sign-in can only act on somebody who turns up; this is what removes the person who does not." })] }), !readOnly && (_jsx(Button, { variant: "primary", ref: returnFocusRef, onClick: openCreate, children: "New SCIM token" }))] })), tokens.isPending && !tokens.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Flash, { variant: shown.variant, children: [_jsx(Text, { as: "p", sx: { fontWeight: 'semibold', m: 0 }, children: shown.heading }), _jsx(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0 }, children: shown.body }), status.lastUsedAt && (_jsxs(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0, color: 'fg.muted' }, children: ["Last provisioning call ", timeAgo(status.lastUsedAt), "."] }))] }), rows.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "scim-tokens", children: "SCIM tokens" }), _jsx(Table.Subtitle, { as: "p", id: "scim-tokens-subtitle", children: "A revoked token is kept rather than deleted, so the provisioning it did stays attributable." }), _jsx(DataTable, { "aria-labelledby": "scim-tokens", "aria-describedby": "scim-tokens-subtitle", data: rows, columns: columns })] }), _jsxs(Box, { sx: {
151
- border: '1px solid',
152
- borderColor: 'border.default',
153
- borderRadius: 2,
154
- p: 3,
155
- display: 'grid',
156
- gap: 2,
157
- }, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: "What to give the directory" }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "SCIM base URL" }), _jsx(TextInput, { block: true, readOnly: true, value: scimBaseUrl(iamUrl), "aria-describedby": "scim-base-url-note" }), _jsxs(FormControl.Caption, { id: "scim-base-url-note", children: ["Okta and Entra append ", _jsx("code", { children: "/Users" }), " themselves. Use the token as a bearer credential; the token is what says which organization is being provisioned, so nothing else names this one."] })] }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Groups are not provisioned over SCIM \u2014 this server answers them ", _jsx("code", { children: "501" }), " saying so. Roles come from the sign-in token\u2019s groups claim and the provider\u2019s role mappings, above; a second source of the same fact would eventually disagree with the first."] })] })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No SCIM token" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Issue one to let this organization\u2019s directory add people when they join and \u2014 the part sign-in cannot do \u2014 remove them when they leave." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: openCreate, children: "New SCIM token" }))] }))] })), creating && (_jsx(Dialog, { title: "New SCIM token", onClose: () => setCreating(false), returnFocusRef: returnFocusRef, footerButtons: [
145
+ 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: "Directory provisioning" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Over SCIM. Sign-in can only act on somebody who turns up; this is what removes the person who does not." })] }), !readOnly && (_jsx(Button, { variant: "primary", ref: returnFocusRef, onClick: openCreate, children: "New SCIM token" }))] })), tokens.isPending && !tokens.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Flash, { variant: shown.variant, children: [_jsx(Text, { as: "p", sx: { fontWeight: 'semibold', m: 0 }, children: shown.heading }), _jsx(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0 }, children: shown.body }), status.lastUsedAt && (_jsxs(Text, { as: "p", sx: { fontSize: 1, mt: 1, mb: 0, color: 'fg.muted' }, children: ["Last provisioning call ", timeAgo(status.lastUsedAt), "."] }))] }), rows.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "scim-tokens", children: "SCIM tokens" }), _jsx(Table.Subtitle, { as: "p", id: "scim-tokens-subtitle", children: "A revoked token is kept rather than deleted, so the provisioning it did stays attributable." }), _jsx(DataTable, { "aria-labelledby": "scim-tokens", "aria-describedby": "scim-tokens-subtitle", data: rows, columns: columns })] }), _jsxs(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 3, display: "grid", gap: 2, children: [_jsx(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: "What to give the directory" }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "SCIM base URL" }), _jsx(TextInput, { block: true, readOnly: true, value: scimBaseUrl(iamUrl), "aria-describedby": "scim-base-url-note" }), _jsxs(FormControl.Caption, { id: "scim-base-url-note", children: ["Okta and Entra append ", _jsx("code", { children: "/Users" }), " themselves. Use the token as a bearer credential; the token is what says which organization is being provisioned, so nothing else names this one."] })] }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Groups are not provisioned over SCIM \u2014 this server answers them ", _jsx("code", { children: "501" }), " saying so. Roles come from the sign-in token\u2019s groups claim and the provider\u2019s role mappings, above; a second source of the same fact would eventually disagree with the first."] })] })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No SCIM token" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Issue one to let this organization\u2019s directory add people when they join and \u2014 the part sign-in cannot do \u2014 remove them when they leave." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: openCreate, children: "New SCIM token" }))] }))] })), creating && (_jsx(Dialog, { title: "New SCIM token", onClose: () => setCreating(false), returnFocusRef: returnFocusRef, footerButtons: [
158
146
  {
159
147
  buttonType: 'default',
160
148
  content: 'Cancel',
@@ -168,6 +156,6 @@ export const ScimProvisioning = ({ errorState, orgUid, readOnly = false, showTit
168
156
  },
169
157
  ], children: _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, autoFocus: true, value: name, placeholder: "Okta", onChange: event => setName(event.target.value) }), _jsx(FormControl.Caption, { children: "Whatever tells this directory from another. Only for your own reading \u2014 it is not part of the credential." })] }) })), secret && (_jsx(Dialog, { title: "Copy this token now", onClose: closeSecret, footerButtons: [
170
158
  { buttonType: 'primary', content: 'Done', onClick: closeSecret },
171
- ], children: _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Flash, { variant: "warning", children: "This is the only time this token is shown. Only a hash of it is stored, so it cannot be shown again \u2014 if it is lost, rotate." }), _jsx(TextInput, { block: true, readOnly: true, value: secret, "aria-label": "SCIM token", sx: { fontFamily: 'mono' }, onFocus: event => event.target.select() }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Give it to the directory as a bearer token, with the base URL", ' ', _jsx("code", { children: scimBaseUrl(iamUrl) }), "."] })] }) }))] }));
159
+ ], children: _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Flash, { variant: "warning", children: "This is the only time this token is shown. Only a hash of it is stored, so it cannot be shown again \u2014 if it is lost, rotate." }), _jsx(TextInput, { block: true, readOnly: true, value: secret, "aria-label": "SCIM token", sx: { fontFamily: 'mono' }, onFocus: event => event.target.select() }), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: ["Give it to the directory as a bearer token, with the base URL", ' ', _jsx("code", { children: scimBaseUrl(iamUrl) }), "."] })] }) }))] }));
172
160
  };
173
161
  export default ScimProvisioning;
@@ -97,7 +97,7 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
97
97
  header: 'Agent',
98
98
  field: 'name',
99
99
  rowHeader: true,
100
- renderCell: row => (_jsxs(Box, { sx: { display: 'grid' }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: row.name || row.uid }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] })),
100
+ renderCell: row => (_jsxs(Box, { display: "grid", children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: row.name || row.uid }), row.description && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: row.description }))] })),
101
101
  },
102
102
  {
103
103
  header: 'Kind',
@@ -134,12 +134,7 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
134
134
  if (agents.isError) {
135
135
  return (_jsx(McpErrorBlankslate, { state: errorState(agents.error, 'Service agents'), onRetry: () => agents.refetch() }));
136
136
  }
137
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [showTitle && (_jsxs(Box, { sx: {
138
- display: 'flex',
139
- alignItems: 'start',
140
- justifyContent: 'space-between',
141
- gap: 3,
142
- }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Service Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Agents that belong to this organization rather than to a person: a pipeline, a CI job, a bot. Each holds its own key and spends under its own name." })] }), !readOnly && (_jsx(Button, { variant: "primary", onClick: () => setCreating(true), children: "New service agent" }))] })), agents.isPending && !agents.data ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : rows.length > 0 ? (_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h3", id: "service-agents", children: "Service agents" }), _jsx(Table.Subtitle, { as: "p", id: "service-agents-subtitle", children: "A revoked agent stays listed: its audit rows name it, and a uid that resolves to nothing makes them unreadable a year from now." }), _jsx(DataTable, { "aria-labelledby": "service-agents", "aria-describedby": "service-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(KeyIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No service agent" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "An agent that runs on somebody\u2019s grant stops when that person leaves, and its spend shows against them. A service agent is the organization\u2019s own." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: () => setCreating(true), children: "New service agent" }))] })), creating && (_jsx(Dialog, { title: "New service agent", onClose: closeCreate, returnFocusRef: returnFocusRef, footerButtons: [
137
+ 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: "Service Agents" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Agents that belong to this organization rather than to a person: a pipeline, a CI job, a bot. Each holds its own key and spends under its own name." })] }), !readOnly && (_jsx(Button, { variant: "primary", onClick: () => setCreating(true), children: "New service agent" }))] })), 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: "service-agents", children: "Service agents" }), _jsx(Table.Subtitle, { as: "p", id: "service-agents-subtitle", children: "A revoked agent stays listed: its audit rows name it, and a uid that resolves to nothing makes them unreadable a year from now." }), _jsx(DataTable, { "aria-labelledby": "service-agents", "aria-describedby": "service-agents-subtitle", data: rows, columns: columns })] })) : (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(KeyIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No service agent" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "An agent that runs on somebody\u2019s grant stops when that person leaves, and its spend shows against them. A service agent is the organization\u2019s own." }) }), !readOnly && (_jsx(Button, { size: "small", onClick: () => setCreating(true), children: "New service agent" }))] })), creating && (_jsx(Dialog, { title: "New service agent", onClose: closeCreate, returnFocusRef: returnFocusRef, footerButtons: [
143
138
  { buttonType: 'default', content: 'Cancel', onClick: closeCreate },
144
139
  {
145
140
  buttonType: 'primary',
@@ -151,21 +146,13 @@ export const ServiceAgents = ({ errorState, orgUid, readOnly = false, showTitle
151
146
  // into an obvious gap in the form.
152
147
  disabled: create.isPending || !name.trim() || scopes.length === 0,
153
148
  },
154
- ], children: _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: name, onChange: event => setName(event.target.value), placeholder: "nightly ingest" }), _jsx(FormControl.Caption, { children: "What somebody reading an audit row or a spend line a year from now has to go on." })] }), _jsxs(Box, { children: [_jsx(Text, { as: "p", sx: { fontSize: 1, fontWeight: 'semibold', mb: 1 }, children: "Allowed to" }), _jsx(Box, { sx: { display: 'grid', gap: 1 }, children: SERVICE_AGENT_SCOPES.map(scope => (_jsxs(FormControl, { children: [_jsx(Checkbox, { checked: scopes.includes(scope), onChange: event => toggleScope(scope, event.target.checked) }), _jsx(FormControl.Label, { children: scope })] }, scope))) }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1 }, children: "The scopes about a person \u2014 their profile, acting as them \u2014 are not here: a pipeline is not a person, and granting one to it would do nothing while looking like it did something." })] })] }) })), keyShown && (_jsx(Dialog, { title: keyShown.rotated ? 'The new key' : 'The key', onClose: () => setKeyShown(null), returnFocusRef: returnFocusRef, footerButtons: [
149
+ ], children: _jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: name, onChange: event => setName(event.target.value), placeholder: "nightly ingest" }), _jsx(FormControl.Caption, { children: "What somebody reading an audit row or a spend line a year from now has to go on." })] }), _jsxs(Box, { children: [_jsx(Text, { as: "p", sx: { fontSize: 1, fontWeight: 'semibold', mb: 1 }, children: "Allowed to" }), _jsx(Box, { display: "grid", gap: 1, children: SERVICE_AGENT_SCOPES.map(scope => (_jsxs(FormControl, { children: [_jsx(Checkbox, { checked: scopes.includes(scope), onChange: event => toggleScope(scope, event.target.checked) }), _jsx(FormControl.Label, { children: scope })] }, scope))) }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1 }, children: "The scopes about a person \u2014 their profile, acting as them \u2014 are not here: a pipeline is not a person, and granting one to it would do nothing while looking like it did something." })] })] }) })), keyShown && (_jsx(Dialog, { title: keyShown.rotated ? 'The new key' : 'The key', onClose: () => setKeyShown(null), returnFocusRef: returnFocusRef, footerButtons: [
155
150
  {
156
151
  buttonType: 'primary',
157
152
  content: 'I have stored it',
158
153
  onClick: () => setKeyShown(null),
159
154
  },
160
- ], children: _jsxs(Box, { sx: { display: 'grid', gap: 2 }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "This is shown once and cannot be shown again." }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "We store a hash of it, so there is no page and no support request that can show it to you a second time. If you lose it, rotate \u2014 that issues a new key and stops this one." }), _jsx(Box, { as: "pre", sx: {
161
- fontFamily: 'mono',
162
- fontSize: 1,
163
- p: 2,
164
- m: 0,
165
- bg: 'canvas.subtle',
166
- borderRadius: 2,
167
- overflowX: 'auto',
168
- }, children: keyShown.key }), keyShown.rotated && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [keyShown.agent, "\u2019s previous key stopped working when this one was issued."] }))] }) })), rotating && (_jsx(Dialog, { title: "Rotate this key?", onClose: () => setRotating(null), returnFocusRef: returnFocusRef, footerButtons: [
155
+ ], children: _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: "This is shown once and cannot be shown again." }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "We store a hash of it, so there is no page and no support request that can show it to you a second time. If you lose it, rotate \u2014 that issues a new key and stops this one." }), _jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 1, p: 2, m: 0, bg: "canvas.subtle", borderRadius: 2, overflowX: "auto", children: keyShown.key }), keyShown.rotated && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [keyShown.agent, "\u2019s previous key stopped working when this one was issued."] }))] }) })), rotating && (_jsx(Dialog, { title: "Rotate this key?", onClose: () => setRotating(null), returnFocusRef: returnFocusRef, footerButtons: [
169
156
  {
170
157
  buttonType: 'default',
171
158
  content: 'Keep it',
@@ -53,29 +53,23 @@ const TeamPolicyForm = ({ orgUid, teamUid, teamName, readOnly, }) => {
53
53
  });
54
54
  };
55
55
  if (layer.isPending && layer.data === undefined) {
56
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 4 }, children: _jsx(Spinner, {}) }));
56
+ return (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) }));
57
57
  }
58
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { size: "small", onClick: () => {
58
+ return (_jsxs(Box, { display: "grid", gap: 3, children: [conflict && (_jsxs(Flash, { variant: "warning", children: [_jsx(Text, { sx: { fontSize: 1 }, children: conflict }), _jsx(Box, { mt: 2, children: _jsx(Button, { size: "small", onClick: () => {
59
59
  setConflict('');
60
60
  layer.refetch();
61
61
  }, 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, notes: {
62
62
  maxConcurrentSandboxes: "Counted over this team's own sandboxes, not the organization's " +
63
63
  '— so another team being busy never uses up this limit. The ' +
64
64
  "organization's cap still applies on top.",
65
- }, belowAllowedClients: _jsx(AdmittedClients, { scope: "team", subjectUid: teamUid, edited: draft.allowedClients !== stored.allowedClients }) }), !readOnly && (_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsxs(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: ["Save ", teamName, "\u2019s policy"] }), 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, {
65
+ }, belowAllowedClients: _jsx(AdmittedClients, { scope: "team", subjectUid: teamUid, edited: draft.allowedClients !== stored.allowedClients }) }), !readOnly && (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsxs(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: ["Save ", teamName, "\u2019s policy"] }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" })), layer.data && (_jsx(Box, { marginLeft: "auto", children: _jsx(Button, { variant: "danger", disabled: remove.isPending, onClick: () => remove.mutate(undefined, {
66
66
  onSuccess: () => enqueueToast(`${teamName} narrows nothing of its own.`, {
67
67
  variant: 'success',
68
68
  }),
69
69
  onError: error => enqueueToast(`Could not remove: ${error.message}`, {
70
70
  variant: 'error',
71
71
  }),
72
- }), children: "Remove" }) }))] })), !readOnly && _jsx(TeamSessions, { teamUid: teamUid, teamName: teamName }), _jsxs(Box, { sx: {
73
- borderTop: '1px solid',
74
- borderColor: 'border.muted',
75
- pt: 3,
76
- display: 'grid',
77
- gap: 2,
78
- }, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "History" }), _jsx(PolicyHistory, { orgUid: orgUid, subjectUid: teamUid })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "A team can be stricter than its organization and never looser. Permitting a tool the organization denies does nothing, and a cap above the organization\u2019s leaves the organization\u2019s in force." })] }));
72
+ }), children: "Remove" }) }))] })), !readOnly && _jsx(TeamSessions, { teamUid: teamUid, teamName: teamName }), _jsxs(Box, { borderTop: "1px solid", borderColor: "border.muted", pt: 3, display: "grid", gap: 2, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "History" }), _jsx(PolicyHistory, { orgUid: orgUid, subjectUid: teamUid })] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: "A team can be stricter than its organization and never looser. Permitting a tool the organization denies does nothing, and a cap above the organization\u2019s leaves the organization\u2019s in force." })] }));
79
73
  };
80
74
  /**
81
75
  * Ending every session consented in one team.
@@ -117,13 +111,7 @@ const TeamSessions = ({ teamUid, teamName, }) => {
117
111
  },
118
112
  });
119
113
  };
120
- return (_jsxs(Box, { sx: {
121
- borderTop: '1px solid',
122
- borderColor: 'border.muted',
123
- pt: 3,
124
- display: 'grid',
125
- gap: 2,
126
- }, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "Sessions" }), _jsxs(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: ["Every agent connected while acting for ", teamName, " loses its grant at once. Each asks again the next time somebody connects it, and goes through the approval screen rather than being answered from what was agreed before \u2014 so this is also how a fresh approval is forced."] }), _jsx(Box, { children: _jsxs(Button, { variant: "danger", ref: returnFocusRef, onClick: () => setAsking(true), disabled: disconnect.isPending, children: ["End ", teamName, "\u2019s sessions"] }) }), asking && (_jsxs(Dialog, { title: `End every session in ${teamName}?`, onClose: () => setAsking(false), returnFocusRef: returnFocusRef, footerButtons: [
114
+ return (_jsxs(Box, { borderTop: "1px solid", borderColor: "border.muted", pt: 3, display: "grid", gap: 2, children: [_jsx(Text, { as: "h3", sx: { fontSize: 1, fontWeight: 'semibold', m: 0 }, children: "Sessions" }), _jsxs(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: ["Every agent connected while acting for ", teamName, " loses its grant at once. Each asks again the next time somebody connects it, and goes through the approval screen rather than being answered from what was agreed before \u2014 so this is also how a fresh approval is forced."] }), _jsx(Box, { children: _jsxs(Button, { variant: "danger", ref: returnFocusRef, onClick: () => setAsking(true), disabled: disconnect.isPending, children: ["End ", teamName, "\u2019s sessions"] }) }), asking && (_jsxs(Dialog, { title: `End every session in ${teamName}?`, onClose: () => setAsking(false), returnFocusRef: returnFocusRef, footerButtons: [
127
115
  {
128
116
  buttonType: 'default',
129
117
  content: 'Keep them',
@@ -146,12 +134,12 @@ export const TeamPolicies = ({ errorState, orgUid, readOnly = false, showTitle =
146
134
  return (_jsx(McpErrorBlankslate, { state: errorState(teams.error, "The organization's teams"), onRetry: () => teams.refetch() }));
147
135
  }
148
136
  if (teams.isPending && !teams.data) {
149
- return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) }));
137
+ return (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) }));
150
138
  }
151
139
  if ((teams.data ?? []).length === 0) {
152
140
  return (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(PeopleIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No teams" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "A team is a scope, not a label: it gets its own rules, its own sandbox limit and its own alerts. Create one in the organization\u2019s settings and it appears here." }) })] }));
153
141
  }
154
- 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: "Team policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What each team narrows, on top of this organization\u2019s own rules. A team owner may write their own team\u2019s." })] })), _jsx(Box, { sx: { maxWidth: '20rem' }, children: _jsx(Select, { value: current?.uid ?? '', onChange: event => setChosen(event.target.value), "aria-label": "Team", children: (teams.data ?? []).map(team => (_jsx(Select.Option, { value: team.uid, children: team.name }, team.uid))) }) }), current && (
142
+ return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, maxWidth: "52rem", children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Team policies" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "What each team narrows, on top of this organization\u2019s own rules. A team owner may write their own team\u2019s." })] })), _jsx(Box, { maxWidth: "20rem", children: _jsx(Select, { value: current?.uid ?? '', onChange: event => setChosen(event.target.value), "aria-label": "Team", children: (teams.data ?? []).map(team => (_jsx(Select.Option, { value: team.uid, children: team.name }, team.uid))) }) }), current && (
155
143
  // Keyed by the team, so choosing another unmounts the form rather
156
144
  // than carrying one team's unsaved draft onto the next.
157
145
  _jsx(TeamPolicyForm, { orgUid: orgUid, teamUid: current.uid, teamName: current.name, readOnly: readOnly }, current.uid))] }));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { Button, Heading, Label, Spinner, Text, Truncate, } from '@primer/react';
3
+ import { Button, Heading, Label, Spinner, Text, Truncate } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
5
  import { McpErrorBlankslate } from '../../components/mcp/index.js';
6
6
  import { useContentSources, useMcpSessions, useMcpTools, useRevokeMcpSession, } from '../../hooks/useContents.js';
@@ -14,7 +14,9 @@ import { useContentSources, useMcpSessions, useMcpTools, useRevokeMcpSession, }
14
14
  */
15
15
  export const effectiveTools = (sessionAllowed, sourceAllowed) => {
16
16
  const source = new Set(sourceAllowed ?? []);
17
- return [...new Set(sessionAllowed ?? [])].filter(tool => source.has(tool)).sort();
17
+ return [...new Set(sessionAllowed ?? [])]
18
+ .filter(tool => source.has(tool))
19
+ .sort();
18
20
  };
19
21
  /**
20
22
  * The tools a session still lists that its source has stopped allowing.
@@ -25,7 +27,9 @@ export const effectiveTools = (sessionAllowed, sourceAllowed) => {
25
27
  */
26
28
  export const withdrawnTools = (sessionAllowed, sourceAllowed) => {
27
29
  const source = new Set(sourceAllowed ?? []);
28
- return [...new Set(sessionAllowed ?? [])].filter(tool => !source.has(tool)).sort();
30
+ return [...new Set(sessionAllowed ?? [])]
31
+ .filter(tool => !source.has(tool))
32
+ .sort();
29
33
  };
30
34
  /** How a source's approval policy reads to somebody deciding whether to trust it. */
31
35
  export const APPROVAL_POLICY_LOOK = {
@@ -52,13 +56,7 @@ export const mcpConfigOf = (source) => source.kind === 'mcp'
52
56
  const SessionRow = ({ session, sourceTools, mayRevoke, onRevoke, revoking, }) => {
53
57
  const effective = effectiveTools(session.allowedTools, sourceTools);
54
58
  const withdrawn = withdrawnTools(session.allowedTools, sourceTools);
55
- return (_jsxs(Box, { sx: {
56
- display: 'grid',
57
- gap: 1,
58
- py: 2,
59
- borderTop: '1px solid',
60
- borderColor: 'border.muted',
61
- }, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(Label, { size: "small", variant: session.status === 'active' ? 'success' : 'secondary', children: session.status }), _jsx(Truncate, { title: session.actorUid, sx: { fontSize: 0, fontFamily: 'mono', maxWidth: '16rem' }, children: session.actorUid }), session.status === 'active' && mayRevoke && (_jsx(Button, { size: "small", variant: "danger", disabled: revoking, onClick: onRevoke, children: revoking ? 'Revoking…' : 'Revoke' }))] }), effective.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg' }, children: "This session can call nothing. An empty allowlist allows no tool \u2014 it does not mean unrestricted." })) : (_jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: effective.map(tool => (_jsx(Label, { size: "small", variant: "secondary", children: tool }, tool))) })), withdrawn.length > 0 && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Refused at every call, because the source no longer allows", ' ', withdrawn.join(', '), " \u2014 the session still lists it."] }))] }));
59
+ return (_jsxs(Box, { display: "grid", gap: 1, py: 2, borderTop: "1px solid", borderColor: "border.muted", children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: session.status === 'active' ? 'success' : 'secondary', children: session.status }), _jsx(Truncate, { title: session.actorUid, sx: { fontSize: 0, fontFamily: 'mono', maxWidth: '16rem' }, children: session.actorUid }), session.status === 'active' && mayRevoke && (_jsx(Button, { size: "small", variant: "danger", disabled: revoking, onClick: onRevoke, children: revoking ? 'Revoking…' : 'Revoke' }))] }), effective.length === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg' }, children: "This session can call nothing. An empty allowlist allows no tool \u2014 it does not mean unrestricted." })) : (_jsx(Box, { display: "flex", gap: 1, flexWrap: "wrap", children: effective.map(tool => (_jsx(Label, { size: "small", variant: "secondary", children: tool }, tool))) })), withdrawn.length > 0 && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Refused at every call, because the source no longer allows", ' ', withdrawn.join(', '), " \u2014 the session still lists it."] }))] }));
62
60
  };
63
61
  const SourceCard = ({ source, permissions, errorState, }) => {
64
62
  const config = mcpConfigOf(source);
@@ -69,17 +67,9 @@ const SourceCard = ({ source, permissions, errorState, }) => {
69
67
  const [revoking, setRevoking] = useState('');
70
68
  const look = config ? APPROVAL_POLICY_LOOK[config.approvalPolicy] : undefined;
71
69
  const offered = manifest.data?.tools?.length ?? 0;
72
- return (_jsxs(Box, { sx: {
73
- border: '1px solid',
74
- borderColor: 'border.default',
75
- borderRadius: 2,
76
- p: 3,
77
- display: 'grid',
78
- gap: 2,
79
- minWidth: 0,
80
- }, children: [_jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: source.name }), look && (_jsx(Label, { size: "small", variant: look.variant, children: look.label })), config?.transport && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: config.transport }))] }), look && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note })), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: sourceTools.length === 0
70
+ return (_jsxs(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, p: 3, display: "grid", gap: 2, minWidth: 0, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: source.name }), look && (_jsx(Label, { size: "small", variant: look.variant, children: look.label })), config?.transport && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: config.transport }))] }), look && _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: sourceTools.length === 0
81
71
  ? 'No tool is allowed on this source, so an agent can call none of it.'
82
- : `${sourceTools.length} of ${offered || sourceTools.length} tools allowed to agents.` }), sourceTools.length > 0 && (_jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [...sourceTools].sort().map(tool => (_jsx(Label, { size: "small", variant: "accent", children: tool }, tool))) })), _jsxs(Box, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 0, color: 'fg.muted', mb: 1 }, children: "Open sessions" }), sessions.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sessions.error, 'The sessions'), onRetry: () => sessions.refetch() })) : sessions.isPending ? (_jsx(Spinner, { size: "small" })) : (sessions.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "No agent has opened this source." })) : ((sessions.data?.items ?? []).map(session => (_jsx(SessionRow, { session: session, sourceTools: sourceTools,
72
+ : `${sourceTools.length} of ${offered || sourceTools.length} tools allowed to agents.` }), sourceTools.length > 0 && (_jsx(Box, { display: "flex", gap: 1, flexWrap: "wrap", children: [...sourceTools].sort().map(tool => (_jsx(Label, { size: "small", variant: "accent", children: tool }, tool))) })), _jsxs(Box, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 0, color: 'fg.muted', mb: 1 }, children: "Open sessions" }), sessions.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sessions.error, 'The sessions'), onRetry: () => sessions.refetch() })) : sessions.isPending ? (_jsx(Spinner, { size: "small" })) : (sessions.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "No agent has opened this source." })) : ((sessions.data?.items ?? []).map(session => (_jsx(SessionRow, { session: session, sourceTools: sourceTools,
83
73
  // Revoking a session withdraws an agent's access to this
84
74
  // source, so it follows the source's own write permission
85
75
  // rather than being offered to everyone who can read the page.
@@ -91,11 +81,14 @@ const SourceCard = ({ source, permissions, errorState, }) => {
91
81
  } }, session.uid))))] })] }));
92
82
  };
93
83
  export const ToolAccess = ({ errorState, spaceUid, }) => {
94
- const sources = useContentSources({ kind: 'mcp', ...(spaceUid ? { spaceUid } : {}) });
84
+ const sources = useContentSources({
85
+ kind: 'mcp',
86
+ ...(spaceUid ? { spaceUid } : {}),
87
+ });
95
88
  // The listing wraps each source with what this caller may do with it, and
96
89
  // both halves are used: the source to read the allowlist, the permissions
97
90
  // to decide whether revoking is offered at all.
98
91
  const items = (sources.data?.items ?? []);
99
- return (_jsxs(Box, { sx: { display: 'grid', gap: 3, minWidth: 0 }, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Tools" }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: "The MCP sources your agents may reach, the tools allowed on each, and the sessions open against them. A session can only narrow what the source allows, never widen it." })] }), sources.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sources.error, 'The sources'), onRetry: () => sources.refetch() })) : sources.isPending ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 5 }, children: _jsx(Spinner, {}) })) : items.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No MCP source is connected here. A source has to be created before its tools can be offered to an agent." })) : (items.map(entry => (_jsx(SourceCard, { source: entry.source, permissions: entry.permissions, errorState: errorState }, entry.source.uid))))] }));
92
+ return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Tools" }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: "The MCP sources your agents may reach, the tools allowed on each, and the sessions open against them. A session can only narrow what the source allows, never widen it." })] }), sources.isError ? (_jsx(McpErrorBlankslate, { state: errorState(sources.error, 'The sources'), onRetry: () => sources.refetch() })) : sources.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : items.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No MCP source is connected here. A source has to be created before its tools can be offered to an agent." })) : (items.map(entry => (_jsx(SourceCard, { source: entry.source, permissions: entry.permissions, errorState: errorState }, entry.source.uid))))] }));
100
93
  };
101
94
  export default ToolAccess;