@datalayer/core 1.2.19 → 1.2.21

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 (171) hide show
  1. package/lib/api/evals/client.d.ts +6 -6
  2. package/lib/api/evals/client.js +5 -5
  3. package/lib/api/evals/status.d.ts +3 -3
  4. package/lib/api/evals/status.js +3 -3
  5. package/lib/api/evals/types.d.ts +9 -9
  6. package/lib/api/iam/secrets.js +2 -2
  7. package/lib/api/iam/servicePrincipals.js +1 -1
  8. package/lib/api/iam/trials.js +1 -1
  9. package/lib/api/orchestration/generated.d.ts +12 -12
  10. package/lib/api/orchestration/generated.js +1 -1
  11. package/lib/api/orchestration/index.d.ts +2 -2
  12. package/lib/api/orchestration/index.js +2 -2
  13. package/lib/api/orchestration/lifecycle.d.ts +1 -1
  14. package/lib/api/orchestration/lifecycle.js +1 -1
  15. package/lib/api/scheduler/client.d.ts +1 -1
  16. package/lib/api/scheduler/client.js +1 -1
  17. package/lib/api/scheduler/types.d.ts +1 -1
  18. package/lib/api/spacer/comments.d.ts +1 -1
  19. package/lib/api/spacer/comments.js +1 -1
  20. package/lib/api/spacer/spaces.d.ts +1 -1
  21. package/lib/api/spacer/spaces.js +1 -1
  22. package/lib/components/avatars/UserAvatar.d.ts +10 -11
  23. package/lib/components/avatars/UserAvatar.js +23 -28
  24. package/lib/components/avatars/UserProfileAvatar.js +1 -1
  25. package/lib/components/avatars/avatarChoice.d.ts +42 -0
  26. package/lib/components/avatars/avatarChoice.js +65 -0
  27. package/lib/components/avatars/index.d.ts +1 -0
  28. package/lib/components/avatars/index.js +1 -0
  29. package/lib/components/collaboration/LiveEditorCollaborators.d.ts +3 -4
  30. package/lib/components/collaboration/LiveEditorCollaborators.js +7 -5
  31. package/lib/components/emoji/faceEmojis.js +48 -0
  32. package/lib/components/emoji/fluent/loaders.js +46 -0
  33. package/lib/components/emoji/fluent/svg/1f30a.d.ts +2 -0
  34. package/lib/components/emoji/fluent/svg/1f30a.js +5 -0
  35. package/lib/components/emoji/fluent/svg/1f310.d.ts +2 -0
  36. package/lib/components/emoji/fluent/svg/1f310.js +5 -0
  37. package/lib/components/emoji/fluent/svg/1f324.d.ts +2 -0
  38. package/lib/components/emoji/fluent/svg/1f324.js +5 -0
  39. package/lib/components/emoji/fluent/svg/1f326.d.ts +2 -0
  40. package/lib/components/emoji/fluent/svg/1f326.js +5 -0
  41. package/lib/components/emoji/fluent/svg/1f327.d.ts +2 -0
  42. package/lib/components/emoji/fluent/svg/1f327.js +5 -0
  43. package/lib/components/emoji/fluent/svg/1f33e.d.ts +2 -0
  44. package/lib/components/emoji/fluent/svg/1f33e.js +5 -0
  45. package/lib/components/emoji/fluent/svg/1f372.d.ts +2 -0
  46. package/lib/components/emoji/fluent/svg/1f372.js +5 -0
  47. package/lib/components/emoji/fluent/svg/1f373.d.ts +2 -0
  48. package/lib/components/emoji/fluent/svg/1f373.js +5 -0
  49. package/lib/components/emoji/fluent/svg/1f39b.d.ts +2 -0
  50. package/lib/components/emoji/fluent/svg/1f39b.js +5 -0
  51. package/lib/components/emoji/fluent/svg/1f3ab.d.ts +2 -0
  52. package/lib/components/emoji/fluent/svg/1f3ab.js +5 -0
  53. package/lib/components/emoji/fluent/svg/1f41b.d.ts +2 -0
  54. package/lib/components/emoji/fluent/svg/1f41b.js +5 -0
  55. package/lib/components/emoji/fluent/svg/1f441.d.ts +2 -0
  56. package/lib/components/emoji/fluent/svg/1f441.js +5 -0
  57. package/lib/components/emoji/fluent/svg/1f465.d.ts +2 -0
  58. package/lib/components/emoji/fluent/svg/1f465.js +5 -0
  59. package/lib/components/emoji/fluent/svg/1f4b9.d.ts +2 -0
  60. package/lib/components/emoji/fluent/svg/1f4b9.js +5 -0
  61. package/lib/components/emoji/fluent/svg/1f4be.d.ts +2 -0
  62. package/lib/components/emoji/fluent/svg/1f4be.js +5 -0
  63. package/lib/components/emoji/fluent/svg/1f4c1.d.ts +2 -0
  64. package/lib/components/emoji/fluent/svg/1f4c1.js +5 -0
  65. package/lib/components/emoji/fluent/svg/1f4c2.d.ts +2 -0
  66. package/lib/components/emoji/fluent/svg/1f4c2.js +5 -0
  67. package/lib/components/emoji/fluent/svg/1f4c4.d.ts +2 -0
  68. package/lib/components/emoji/fluent/svg/1f4c4.js +5 -0
  69. package/lib/components/emoji/fluent/svg/1f4d3.d.ts +2 -0
  70. package/lib/components/emoji/fluent/svg/1f4d3.js +5 -0
  71. package/lib/components/emoji/fluent/svg/1f4d6.d.ts +2 -0
  72. package/lib/components/emoji/fluent/svg/1f4d6.js +5 -0
  73. package/lib/components/emoji/fluent/svg/1f4dc.d.ts +2 -0
  74. package/lib/components/emoji/fluent/svg/1f4dc.js +5 -0
  75. package/lib/components/emoji/fluent/svg/1f4e1.d.ts +2 -0
  76. package/lib/components/emoji/fluent/svg/1f4e1.js +5 -0
  77. package/lib/components/emoji/fluent/svg/1f4e2.d.ts +2 -0
  78. package/lib/components/emoji/fluent/svg/1f4e2.js +5 -0
  79. package/lib/components/emoji/fluent/svg/1f4e7.d.ts +2 -0
  80. package/lib/components/emoji/fluent/svg/1f4e7.js +5 -0
  81. package/lib/components/emoji/fluent/svg/1f500.d.ts +2 -0
  82. package/lib/components/emoji/fluent/svg/1f500.js +5 -0
  83. package/lib/components/emoji/fluent/svg/1f501.d.ts +2 -0
  84. package/lib/components/emoji/fluent/svg/1f501.js +5 -0
  85. package/lib/components/emoji/fluent/svg/1f504.d.ts +2 -0
  86. package/lib/components/emoji/fluent/svg/1f504.js +5 -0
  87. package/lib/components/emoji/fluent/svg/1f52d.d.ts +2 -0
  88. package/lib/components/emoji/fluent/svg/1f52d.js +5 -0
  89. package/lib/components/emoji/fluent/svg/1f552.d.ts +2 -0
  90. package/lib/components/emoji/fluent/svg/1f552.js +5 -0
  91. package/lib/components/emoji/fluent/svg/1f58b.d.ts +2 -0
  92. package/lib/components/emoji/fluent/svg/1f58b.js +5 -0
  93. package/lib/components/emoji/fluent/svg/1f5b2.d.ts +2 -0
  94. package/lib/components/emoji/fluent/svg/1f5b2.js +5 -0
  95. package/lib/components/emoji/fluent/svg/1f5c3.d.ts +2 -0
  96. package/lib/components/emoji/fluent/svg/1f5c3.js +5 -0
  97. package/lib/components/emoji/fluent/svg/1f5d1.d.ts +2 -0
  98. package/lib/components/emoji/fluent/svg/1f5d1.js +5 -0
  99. package/lib/components/emoji/fluent/svg/1f5dc.d.ts +2 -0
  100. package/lib/components/emoji/fluent/svg/1f5dc.js +5 -0
  101. package/lib/components/emoji/fluent/svg/1f604.d.ts +2 -0
  102. package/lib/components/emoji/fluent/svg/1f604.js +5 -0
  103. package/lib/components/emoji/fluent/svg/1f917.d.ts +2 -0
  104. package/lib/components/emoji/fluent/svg/1f917.js +5 -0
  105. package/lib/components/emoji/fluent/svg/1f9d1-200d-2696.d.ts +2 -0
  106. package/lib/components/emoji/fluent/svg/1f9d1-200d-2696.js +5 -0
  107. package/lib/components/emoji/fluent/svg/1fa79.d.ts +2 -0
  108. package/lib/components/emoji/fluent/svg/1fa79.js +5 -0
  109. package/lib/components/emoji/fluent/svg/1fa9d.d.ts +2 -0
  110. package/lib/components/emoji/fluent/svg/1fa9d.js +5 -0
  111. package/lib/components/emoji/fluent/svg/1fa9f.d.ts +2 -0
  112. package/lib/components/emoji/fluent/svg/1fa9f.js +5 -0
  113. package/lib/components/emoji/fluent/svg/23f3.d.ts +2 -0
  114. package/lib/components/emoji/fluent/svg/23f3.js +5 -0
  115. package/lib/components/emoji/fluent/svg/2611.d.ts +2 -0
  116. package/lib/components/emoji/fluent/svg/2611.js +5 -0
  117. package/lib/components/emoji/fluent/svg/2702.d.ts +2 -0
  118. package/lib/components/emoji/fluent/svg/2702.js +5 -0
  119. package/lib/components/emoji/fluent/svg/270f.d.ts +2 -0
  120. package/lib/components/emoji/fluent/svg/270f.js +5 -0
  121. package/lib/components/emoji/fluent/svg/2728.d.ts +2 -0
  122. package/lib/components/emoji/fluent/svg/2728.js +5 -0
  123. package/lib/components/emoji/fluent/svg/2b1c.d.ts +2 -0
  124. package/lib/components/emoji/fluent/svg/2b1c.js +5 -0
  125. package/lib/components/principal/Principal.js +1 -1
  126. package/lib/components/principal/PrincipalAppearance.d.ts +24 -1
  127. package/lib/components/principal/PrincipalAppearance.js +105 -34
  128. package/lib/components/principal/PrincipalAvatar.d.ts +5 -7
  129. package/lib/components/principal/PrincipalAvatar.js +12 -18
  130. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +4 -7
  131. package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
  132. package/lib/components/principal/PrincipalSwitcherMenu.js +1 -1
  133. package/lib/hooks/unlinkIAMProvider.d.ts +22 -0
  134. package/lib/hooks/unlinkIAMProvider.js +24 -0
  135. package/lib/hooks/useCache.d.ts +8 -1
  136. package/lib/hooks/useCache.js +37 -6
  137. package/lib/hooks/useNavigate.js +8 -2
  138. package/lib/models/IAMProviderLinked.d.ts +9 -0
  139. package/lib/models/IAMProviderLinked.js +34 -5
  140. package/lib/models/McpTask.d.ts +18 -1
  141. package/lib/models/McpTask.js +15 -0
  142. package/lib/models/Profile.d.ts +2 -2
  143. package/lib/models/Secret.d.ts +1 -1
  144. package/lib/models/Secret.js +1 -1
  145. package/lib/models/StartedBy.d.ts +1 -1
  146. package/lib/models/StartedBy.js +1 -1
  147. package/lib/models/User.js +5 -12
  148. package/lib/models/UserOnboarding.d.ts +9 -0
  149. package/lib/models/UserOnboarding.js +33 -0
  150. package/lib/portable/index.d.ts +3 -2
  151. package/lib/portable/index.js +4 -2
  152. package/lib/portable/states.d.ts +39 -0
  153. package/lib/portable/states.js +71 -0
  154. package/lib/state/substates/NavigationState.d.ts +5 -3
  155. package/lib/state/substates/NavigationState.js +12 -3
  156. package/lib/views/mcp/AlertDestinations.js +2 -2
  157. package/lib/views/mcp/ApprovalQueue.js +2 -2
  158. package/lib/views/mcp/EnterpriseConsole.js +2 -2
  159. package/lib/views/mcp/McpDashboard.js +2 -2
  160. package/lib/views/mcp/McpHome.js +4 -4
  161. package/lib/views/mcp/McpObservability.js +5 -5
  162. package/lib/views/mcp/NotebookRuns.js +2 -2
  163. package/lib/views/mcp/OrganizationUsage.js +2 -2
  164. package/lib/views/mcp/Policies.js +2 -2
  165. package/lib/views/mcp/RunDetail.d.ts +1 -1
  166. package/lib/views/mcp/RunDetail.js +4 -1
  167. package/lib/views/mcp/ScimProvisioning.js +2 -2
  168. package/lib/views/mcp/ToolAccess.js +2 -2
  169. package/lib/views/secrets/SecretNew.js +1 -1
  170. package/package.json +1 -1
  171. package/scripts/generate-orchestration-types.py +12 -12
@@ -2,12 +2,41 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ /**
6
+ * The account a provider returned, as IAM keeps it in `linked_account_s`.
7
+ * GitHub, Google and LinkedIn keep a JSON object; Bluesky and X keep a plain
8
+ * handle, which is no object to read, so it reads as `{}`.
9
+ */
10
+ function linkedAccountOf(raw) {
11
+ if (typeof raw !== 'string' || raw.trim() === '') {
12
+ return {};
13
+ }
14
+ try {
15
+ const parsed = JSON.parse(raw);
16
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
17
+ ? parsed
18
+ : {};
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ /**
25
+ * One `iam_providers` child of the user IAM returns (whoami, login): its
26
+ * Solr fields `iam_provider_name_s`, `linked_account_s`,
27
+ * `linked_account_url_s`, `linked_account_id_s`.
28
+ */
5
29
  export function asIAMProviderLinked(iamProvider) {
6
- const info = iamProvider.linkedAccount.replaceAll("'", '"');
7
30
  return {
8
- iamProviderName: iamProvider.iam_ProviderName,
9
- linkedAccount: JSON.parse(info === '' ? '{}' : info),
10
- linkedAccountUrl: iamProvider.linkedAccountUrl,
11
- linkedAccountId: iamProvider.linkedAccountId,
31
+ iamProviderName: String(iamProvider.iam_provider_name_s ?? '')
32
+ .trim()
33
+ .toLowerCase(),
34
+ linkedAccount: linkedAccountOf(iamProvider.linked_account_s),
35
+ linkedAccountUrl: iamProvider.linked_account_url_s ?? '',
36
+ linkedAccountId: iamProvider.linked_account_id_s ?? '',
12
37
  };
13
38
  }
39
+ /** Whether the user has linked this provider in IAM. */
40
+ export function hasLinkedIAMProvider(user, name) {
41
+ return (user?.iamProviders ?? []).some(p => p.iamProviderName === name);
42
+ }
@@ -58,7 +58,12 @@ export interface McpTask {
58
58
  pollIntervalMs?: number | null;
59
59
  /** The `CallToolResult` for small results; a resource URI otherwise. */
60
60
  result?: McpCallToolResult | string | null;
61
- error?: string | null;
61
+ /**
62
+ * Why it failed, as the server keeps it: an object — empty while nothing
63
+ * failed, else its `reason`, and where one applies the `code`, the `step`
64
+ * and what the resource was doing.
65
+ */
66
+ error?: McpTaskError | null;
62
67
  traceId?: string | null;
63
68
  workflowEngine?: McpWorkflowEngine | null;
64
69
  /** Always the task uid, so a retried start cannot create a second execution. */
@@ -92,3 +97,15 @@ export interface McpTaskEvent {
92
97
  /** The statuses from which nothing more happens. */
93
98
  export declare const MCP_TERMINAL_TASK_STATUSES: ReadonlySet<McpTaskStatus>;
94
99
  export declare const isMcpTaskTerminal: (status: McpTaskStatus) => boolean;
100
+ /** A task's failure, as the server keeps it (camel-cased like the rest). */
101
+ export type McpTaskError = {
102
+ reason?: string;
103
+ code?: string;
104
+ step?: string;
105
+ [key: string]: unknown;
106
+ };
107
+ /**
108
+ * Why a task failed, in a line: its reason, after its code where it has one;
109
+ * `null` when the server's object says nothing — an empty one is no failure.
110
+ */
111
+ export declare const taskFailureOf: (error: McpTaskError | null | undefined) => string | null;
@@ -5,3 +5,18 @@
5
5
  /** The statuses from which nothing more happens. */
6
6
  export const MCP_TERMINAL_TASK_STATUSES = new Set(['completed', 'failed', 'cancelled']);
7
7
  export const isMcpTaskTerminal = (status) => MCP_TERMINAL_TASK_STATUSES.has(status);
8
+ /**
9
+ * Why a task failed, in a line: its reason, after its code where it has one;
10
+ * `null` when the server's object says nothing — an empty one is no failure.
11
+ */
12
+ export const taskFailureOf = (error) => {
13
+ if (!error || Object.keys(error).length === 0) {
14
+ return null;
15
+ }
16
+ const reason = typeof error.reason === 'string' ? error.reason : null;
17
+ const code = typeof error.code === 'string' ? error.code : null;
18
+ if (reason) {
19
+ return code ? `${code}: ${reason}` : reason;
20
+ }
21
+ return code ?? JSON.stringify(error);
22
+ };
@@ -34,8 +34,8 @@ export interface Profile {
34
34
  avatar_url_s?: string;
35
35
  avatar_icon_s?: string;
36
36
  banner_s?: string;
37
- /** Onboarding state as JSON string */
38
- onboarding_s?: string;
37
+ /** Onboarding state: the child IAM keeps on the user (read by `userOnboardingOf`). */
38
+ onboarding?: Record<string, unknown>;
39
39
  /** New password request timestamp */
40
40
  new_password_request_ts_dt?: string | null;
41
41
  /** New password confirmation timestamp */
@@ -144,7 +144,7 @@ export declare class SecretDTO {
144
144
  get variant(): ISecretVariant;
145
145
  get name(): string;
146
146
  get description(): string;
147
- /** The value as its owner wrote it (PLAN_ENVS.md E3-05). */
147
+ /** The value as its owner wrote it (E3-05). */
148
148
  get value(): string;
149
149
  /**
150
150
  * Update this secret.
@@ -74,7 +74,7 @@ export class SecretDTO {
74
74
  this._checkDeleted();
75
75
  return this._data.description_t;
76
76
  }
77
- /** The value as its owner wrote it (PLAN_ENVS.md E3-05). */
77
+ /** The value as its owner wrote it (E3-05). */
78
78
  get value() {
79
79
  this._checkDeleted();
80
80
  return this._data.value_s;
@@ -50,7 +50,7 @@ export declare const startedBy: (metadata: UsageMetadata) => StartedBy;
50
50
  * The words a search box matches on for this row.
51
51
  *
52
52
  * "agent" matches every agent-started runtime, the client id matches one
53
- * client, and "you" matches your own — so the filter the plan asks for is the
53
+ * client, and "you" matches your own — so the filter asked for is the
54
54
  * search box that is already there rather than a second control beside it.
55
55
  */
56
56
  export declare const startedBySearchTerms: (metadata: UsageMetadata) => string;
@@ -65,7 +65,7 @@ export const startedBy = (metadata) => {
65
65
  * The words a search box matches on for this row.
66
66
  *
67
67
  * "agent" matches every agent-started runtime, the client id matches one
68
- * client, and "you" matches your own — so the filter the plan asks for is the
68
+ * client, and "you" matches your own — so the filter asked for is the
69
69
  * search box that is already there rather than a second control beside it.
70
70
  */
71
71
  export const startedBySearchTerms = (metadata) => {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { asDisplayName, namesAsInitials, toFriendlyName } from '../utils/index.js';
6
6
  import { asIAMProviderLinked } from './IAMProviderLinked.js';
7
- import { BOOTSTRAP_USER_ONBOARDING } from './UserOnboarding.js';
7
+ import { userOnboardingOf } from './UserOnboarding.js';
8
8
  import { UserSettings } from './UserSettings.js';
9
9
  import { asUserEvent } from './UserEvent.js';
10
10
  export const ANONYMOUS_USER = undefined;
@@ -76,20 +76,13 @@ export class User {
76
76
  this.credits = u.credits_i ? Number(u.credits_i) : 0;
77
77
  this.creditsCustomerId = u.stripe_customer_id_s;
78
78
  this.roles = u.roles_ss ?? [];
79
- let iamProviders = [];
80
- try {
81
- iamProviders = (u.iam_providers ?? []).map(iamProvider => asIAMProviderLinked(iamProvider));
82
- }
83
- catch (e) {
84
- // no-op for backwards compatibility.
85
- }
86
- this.iamProviders = iamProviders;
79
+ // IAM sends one linked provider as an object, several as a list.
80
+ const rawIAMProviders = u.iam_providers ?? [];
81
+ this.iamProviders = (Array.isArray(rawIAMProviders) ? rawIAMProviders : [rawIAMProviders]).map(asIAMProviderLinked);
87
82
  this.settings = new UserSettings(u.settings ?? {});
88
83
  this.unsubscribedFromOutbounds = u.unsubscribed_from_outbounds_b ?? false;
89
84
  this.mfaUrl = u.mfa_url_s;
90
- this.onboarding = u.onboarding_s
91
- ? JSON.parse(u.onboarding_s)
92
- : BOOTSTRAP_USER_ONBOARDING;
85
+ this.onboarding = userOnboardingOf(u.onboarding);
93
86
  this.linkedContactId = u.linked_contact_uid;
94
87
  this.subscription = u.subscription;
95
88
  let events = u.events ?? [];
@@ -12,3 +12,12 @@ export type IUserOnboarding = {
12
12
  position: IOnboardingPosition;
13
13
  tours: IOnboardingTours;
14
14
  };
15
+ /**
16
+ * The user's onboarding, as IAM keeps it: a child of the user whose fields
17
+ * say the guide's position (`position_s`), each client's progress
18
+ * (`client_<client>_i`) and each tour's status (`tour_<tour>_status_s`), over
19
+ * what nothing has said yet. Read from the old `onboarding_s` string, a
20
+ * status saved since was never seen: the welcome tour started again on every
21
+ * load and was saved again (13 October 2026).
22
+ */
23
+ export declare const userOnboardingOf: (child: unknown) => IUserOnboarding;
@@ -17,3 +17,36 @@ export const BOOTSTRAP_USER_ONBOARDING = {
17
17
  },
18
18
  },
19
19
  };
20
+ /**
21
+ * The user's onboarding, as IAM keeps it: a child of the user whose fields
22
+ * say the guide's position (`position_s`), each client's progress
23
+ * (`client_<client>_i`) and each tour's status (`tour_<tour>_status_s`), over
24
+ * what nothing has said yet. Read from the old `onboarding_s` string, a
25
+ * status saved since was never seen: the welcome tour started again on every
26
+ * load and was saved again (13 October 2026).
27
+ */
28
+ export const userOnboardingOf = (child) => {
29
+ const stored = (Array.isArray(child) ? child[0] : child);
30
+ const onboarding = {
31
+ clients: { ...BOOTSTRAP_USER_ONBOARDING.clients },
32
+ position: BOOTSTRAP_USER_ONBOARDING.position,
33
+ tours: { ...BOOTSTRAP_USER_ONBOARDING.tours },
34
+ };
35
+ if (!stored || typeof stored !== 'object') {
36
+ return onboarding;
37
+ }
38
+ if (typeof stored.position_s === 'string') {
39
+ onboarding.position = stored.position_s;
40
+ }
41
+ for (const [key, value] of Object.entries(stored)) {
42
+ const client = /^client_(.+)_i$/.exec(key)?.[1];
43
+ if (client && typeof value === 'number') {
44
+ onboarding.clients[client] = value;
45
+ }
46
+ const tour = /^tour_(.+)_status_s$/.exec(key)?.[1];
47
+ if (tour && typeof value === 'string') {
48
+ onboarding.tours[tour] = { status: value };
49
+ }
50
+ }
51
+ return onboarding;
52
+ };
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * What any JavaScript runtime can import from Core: no React, DOM, Jupyter or
3
- * Node built-in. Re-exports only; the mobile app reaches Core through this
4
- * entry alone.
3
+ * Node built-in. The mobile app reaches Core through this entry alone, and
4
+ * its modules import nothing but each other and Core's pure models.
5
5
  *
6
6
  * @module portable
7
7
  */
8
8
  export { personOf, type Person } from '../models/Person';
9
+ export { detailOf, isProblem, kindOfStatus, Problem, PROBLEM_WORDS, problemOf, retries, STATE_WORDS, type ProblemKind, } from './states';
@@ -4,9 +4,11 @@
4
4
  */
5
5
  /**
6
6
  * What any JavaScript runtime can import from Core: no React, DOM, Jupyter or
7
- * Node built-in. Re-exports only; the mobile app reaches Core through this
8
- * entry alone.
7
+ * Node built-in. The mobile app reaches Core through this entry alone, and
8
+ * its modules import nothing but each other and Core's pure models.
9
9
  *
10
10
  * @module portable
11
11
  */
12
12
  export { personOf } from '../models/Person.js';
13
+ // Loading, empty and problem states, in a person's words (MOBILE M-013b).
14
+ export { detailOf, isProblem, kindOfStatus, Problem, PROBLEM_WORDS, problemOf, retries, STATE_WORDS, } from './states.js';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Loading, empty and problem states as a person is told them (MOBILE M-013b):
3
+ * what went wrong in their words, the diagnostic detail kept behind
4
+ * *Details* (never a token, a prompt or tool arguments, never a query value),
5
+ * whether trying again can help, and the words around them. A renderer on
6
+ * any platform draws these; it does not word them itself.
7
+ *
8
+ * @module portable/states
9
+ */
10
+ /** What went wrong, as a person is told it. */
11
+ export type ProblemKind = 'unauthenticated' | 'forbidden' | 'not-found' | 'conflict' | 'rate-limited' | 'unavailable' | 'timeout' | 'offline' | 'unexpected';
12
+ export declare const PROBLEM_WORDS: Record<ProblemKind, string>;
13
+ /** The words around a state: loading, nothing yet, trying again, the detail. */
14
+ export declare const STATE_WORDS: {
15
+ readonly loading: "Loading";
16
+ readonly empty: "Nothing yet.";
17
+ readonly retry: "Try again";
18
+ readonly details: "Details";
19
+ readonly hideDetails: "Hide details";
20
+ readonly showDetailsLabel: "Show diagnostic details";
21
+ readonly hideDetailsLabel: "Hide diagnostic details";
22
+ };
23
+ /** A failed request: the person's words, and the detail behind *Details*. */
24
+ export declare class Problem extends Error {
25
+ readonly kind: ProblemKind;
26
+ readonly status?: number;
27
+ /** For *Details* only; never a token, a prompt or tool arguments. */
28
+ readonly detail: string;
29
+ constructor(kind: ProblemKind, detail: string, status?: number);
30
+ }
31
+ export declare const isProblem: (error: unknown) => error is Problem;
32
+ /** A thrown value as a Problem: anything unknown is *unexpected*. */
33
+ export declare const problemOf: (error: unknown) => Problem;
34
+ /** The kind of an HTTP status that failed. */
35
+ export declare function kindOfStatus(status: number): ProblemKind;
36
+ /** Whether trying again can help: not for what is gone or not theirs. */
37
+ export declare const retries: (problem: Problem) => boolean;
38
+ /** The detail a server gave, without query values that could carry a token. */
39
+ export declare function detailOf(method: string, url: string, status: number, body: unknown): string;
@@ -0,0 +1,71 @@
1
+ /*
2
+ * Copyright (c) 2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export const PROBLEM_WORDS = {
6
+ unauthenticated: 'Your session has ended. Sign in again.',
7
+ forbidden: 'You do not have access to this.',
8
+ 'not-found': 'This item is no longer available.',
9
+ conflict: 'This changed somewhere else. It has been refreshed.',
10
+ 'rate-limited': 'Too many requests. Try again in a moment.',
11
+ unavailable: 'Datalayer is not answering right now. Try again.',
12
+ timeout: 'This is taking too long. Try again.',
13
+ offline: 'You are offline. Connect to continue.',
14
+ unexpected: 'Something went wrong.',
15
+ };
16
+ /** The words around a state: loading, nothing yet, trying again, the detail. */
17
+ export const STATE_WORDS = {
18
+ loading: 'Loading',
19
+ empty: 'Nothing yet.',
20
+ retry: 'Try again',
21
+ details: 'Details',
22
+ hideDetails: 'Hide details',
23
+ showDetailsLabel: 'Show diagnostic details',
24
+ hideDetailsLabel: 'Hide diagnostic details',
25
+ };
26
+ /** A failed request: the person's words, and the detail behind *Details*. */
27
+ export class Problem extends Error {
28
+ kind;
29
+ status;
30
+ /** For *Details* only; never a token, a prompt or tool arguments. */
31
+ detail;
32
+ constructor(kind, detail, status) {
33
+ super(PROBLEM_WORDS[kind]);
34
+ this.name = 'Problem';
35
+ this.kind = kind;
36
+ this.detail = detail;
37
+ this.status = status;
38
+ }
39
+ }
40
+ export const isProblem = (error) => error instanceof Problem;
41
+ /** A thrown value as a Problem: anything unknown is *unexpected*. */
42
+ export const problemOf = (error) => isProblem(error)
43
+ ? error
44
+ : new Problem('unexpected', error instanceof Error ? error.message : String(error));
45
+ /** The kind of an HTTP status that failed. */
46
+ export function kindOfStatus(status) {
47
+ if (status === 401)
48
+ return 'unauthenticated';
49
+ if (status === 403)
50
+ return 'forbidden';
51
+ if (status === 404)
52
+ return 'not-found';
53
+ if (status === 409)
54
+ return 'conflict';
55
+ if (status === 429)
56
+ return 'rate-limited';
57
+ if (status === 502 || status === 503 || status === 504)
58
+ return 'unavailable';
59
+ return 'unexpected';
60
+ }
61
+ /** Whether trying again can help: not for what is gone or not theirs. */
62
+ export const retries = (problem) => problem.kind !== 'not-found' && problem.kind !== 'forbidden';
63
+ /** The detail a server gave, without query values that could carry a token. */
64
+ export function detailOf(method, url, status, body) {
65
+ const path = url.split('?')[0];
66
+ const said = body && typeof body === 'object'
67
+ ? (body.detail ??
68
+ body.message)
69
+ : undefined;
70
+ return `${method} ${path} → ${status}${typeof said === 'string' ? `: ${said}` : ''}`;
71
+ }
@@ -1,6 +1,5 @@
1
- /** The signed-in shell: the platform, the business cases, or the admin. */
2
1
  /**
3
- * The five shells of the signed-in application.
2
+ * The six shells of the signed-in application.
4
3
  *
5
4
  * `studio` is where an agentic UI application is composed, validated and
6
5
  * shipped — the default for somebody who has not chosen, and what the root
@@ -12,8 +11,11 @@
12
11
  * a sidebar of their own, every address under `/apps`, and the workspace at
13
12
  * `/apps` itself. It was `loop`, at `/loop`: a cookie still saying `loop`
14
13
  * names no shell and is ignored, the reader opening on the Studio.
14
+ * `research` is the research areas, each one a space of the
15
+ * `datalayer-research` organization, at `/research` (the public papers, read
16
+ * signed in, at `/research/papers/…` beside them).
15
17
  */
16
- export type NavigationView = 'studio' | 'home' | 'agentify' | 'admin' | 'apps';
18
+ export type NavigationView = 'studio' | 'home' | 'agentify' | 'research' | 'admin' | 'apps';
17
19
  /** The shells, and where each one opens when it has not been visited. */
18
20
  export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
19
21
  /** Well-known view identifiers for {@link NavigationState.tabsByView}. */
@@ -34,10 +34,13 @@ import { registerSessionState } from '../sessionEnd.js';
34
34
  /** The shells, and where each one opens when it has not been visited. */
35
35
  export const NAVIGATION_VIEW_HOMES = {
36
36
  studio: '/studio',
37
- // Not `/`: the root is the Studio's, so the Power shell has an address of
38
- // its own to be sent to.
39
- home: '/power',
37
+ // Not `/`: the root is the Studio's, so the Platform shell has an address
38
+ // of its own to be sent to. It was `/power` until 2026-10-09, when the
39
+ // shell took the name it is called by everywhere else.
40
+ home: '/platform',
40
41
  agentify: '/agentify',
42
+ // The research areas and their spaces.
43
+ research: '/research',
41
44
  admin: '/admin',
42
45
  // The applications: what a builder opens the Apps shell for.
43
46
  apps: '/apps/apps',
@@ -58,6 +61,7 @@ const COOKIE_MAX_AGE_DAYS = 365;
58
61
  const isView = (value) => value === 'studio' ||
59
62
  value === 'home' ||
60
63
  value === 'agentify' ||
64
+ value === 'research' ||
61
65
  value === 'admin' ||
62
66
  value === 'apps';
63
67
  function readCookie(name) {
@@ -244,6 +248,11 @@ export function viewForRoute(route, current) {
244
248
  if (path === '/agentify' || path.startsWith('/agentify/')) {
245
249
  return 'agentify';
246
250
  }
251
+ // The research areas — and the papers, read signed in under
252
+ // `/research/papers/…`, with the areas' sidebar beside them.
253
+ if (path === '/research' || path.startsWith('/research/')) {
254
+ return 'research';
255
+ }
247
256
  if (path === '/admin' || path.startsWith('/admin/')) {
248
257
  return 'admin';
249
258
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from 'react';
3
3
  import { Button, Flash, FormControl, Heading, Spinner, Text, TextInput, } from '@primer/react';
4
- import { Box } from '@datalayer/primer-addons';
4
+ import { Box, Card } from '@datalayer/primer-addons';
5
5
  import { McpErrorBlankslate } from '../../components/mcp/index.js';
6
6
  import { useAuditSettings, useSetAuditSettings } from '../../hooks/useMcp.js';
7
7
  import { useToast } from '../../hooks/index.js';
@@ -62,6 +62,6 @@ export const AlertDestinations = ({ errorState, orgUid, readOnly = false, }) =>
62
62
  if (settings.isPending && settings.data === undefined) {
63
63
  return (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) }));
64
64
  }
65
- return (_jsxs(Box, { display: "grid", gap: 3, maxWidth: "42rem", p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: "Where alerts go" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Set once for the organization, not per rule \u2014 a URL repeated on twenty rules is nineteen places to forget when it rotates. Every firing also appears in the app, and that copy is sent first so it is not lost when one of these is down." })] }), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Email" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertEmails, onChange: event => set('alertEmails', event.target.value), placeholder: "ops@example.co, oncall@example.co" }), _jsx(FormControl.Caption, { children: "Comma-separated, up to twenty. An alert list is the people who want to know, not a mailing list \u2014 a firing that emails two hundred people is a firing that gets the rule switched off." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Slack" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertSlackWebhookUrl, onChange: event => set('alertSlackWebhookUrl', event.target.value), placeholder: "https://hooks.slack.com/services/\u2026" }), _jsx(FormControl.Caption, { children: "A Slack incoming webhook, and only Slack\u2019s \u2014 anything else belongs in the field below. Through this one Slack gets a coloured attachment with the reading, the severity and the scope as separate fields; through the generic webhook it shows one grey line." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Webhook" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertWebhookUrl, onChange: event => set('alertWebhookUrl', event.target.value), placeholder: "https://events.pagerduty.com/\u2026" }), _jsxs(FormControl.Caption, { children: ["Anything that takes a JSON POST: PagerDuty, a chat relay, your own endpoint. ", _jsx("strong", { children: "https only" }), " \u2014 an alert names your organization and what its agents did. It carries no credential."] })] }), !readOnly && (_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save destinations" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" }))] })), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Each copy is attempted on its own: if the webhook is down you still get the email. A copy that fails is recorded and", ' ', _jsx("strong", { children: "not retried" }), " \u2014 retrying a notice against an endpoint that is down turns its outage into a loop against it, and the alert is already in the app."] })] }));
65
+ return (_jsxs(Card, { border: true, shadow: "none", display: "grid", gap: 3, maxWidth: "42rem", p: 3, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: "Where alerts go" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Set once for the organization, not per rule \u2014 a URL repeated on twenty rules is nineteen places to forget when it rotates. Every firing also appears in the app, and that copy is sent first so it is not lost when one of these is down." })] }), refusal && (_jsx(Flash, { variant: "danger", children: _jsx(Text, { sx: { fontSize: 1 }, children: refusal }) })), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Email" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertEmails, onChange: event => set('alertEmails', event.target.value), placeholder: "ops@example.co, oncall@example.co" }), _jsx(FormControl.Caption, { children: "Comma-separated, up to twenty. An alert list is the people who want to know, not a mailing list \u2014 a firing that emails two hundred people is a firing that gets the rule switched off." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Slack" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertSlackWebhookUrl, onChange: event => set('alertSlackWebhookUrl', event.target.value), placeholder: "https://hooks.slack.com/services/\u2026" }), _jsx(FormControl.Caption, { children: "A Slack incoming webhook, and only Slack\u2019s \u2014 anything else belongs in the field below. Through this one Slack gets a coloured attachment with the reading, the severity and the scope as separate fields; through the generic webhook it shows one grey line." })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Webhook" }), _jsx(TextInput, { block: true, disabled: readOnly, value: draft.alertWebhookUrl, onChange: event => set('alertWebhookUrl', event.target.value), placeholder: "https://events.pagerduty.com/\u2026" }), _jsxs(FormControl.Caption, { children: ["Anything that takes a JSON POST: PagerDuty, a chat relay, your own endpoint. ", _jsx("strong", { children: "https only" }), " \u2014 an alert names your organization and what its agents did. It carries no credential."] })] }), !readOnly && (_jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Button, { variant: "primary", onClick: apply, disabled: !changed || save.isPending, children: "Save destinations" }), changed && (_jsx(Button, { variant: "invisible", onClick: () => setDraft(stored), children: "Discard changes" }))] })), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["Each copy is attempted on its own: if the webhook is down you still get the email. A copy that fails is recorded and", ' ', _jsx("strong", { children: "not retried" }), " \u2014 retrying a notice against an endpoint that is down turns its outage into a loop against it, and the alert is already in the app."] })] }));
66
66
  };
67
67
  export default AlertDestinations;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Button, Heading, Label, RelativeTime, SegmentedControl, Spinner, Text, Textarea, } from '@primer/react';
4
- import { Box } from '@datalayer/primer-addons';
4
+ import { Box, Card } from '@datalayer/primer-addons';
5
5
  import { McpErrorBlankslate } from '../../components/mcp/index.js';
6
6
  import { useDecideMcpApproval, useMcpApprovals } from '../../hooks/useContents.js';
7
7
  /** How each state of an approval is drawn. */
@@ -59,7 +59,7 @@ export const ApprovalQueue = ({ errorState, sourceUid, }) => {
59
59
  : `No ${APPROVAL_LOOK[status].label.toLowerCase()} approvals.` })) : (items.map(approval => {
60
60
  const look = approvalLook(approval);
61
61
  const decidable = isDecidable(approval);
62
- return (_jsxs(Box, { border: "1px solid", borderColor: decidable ? 'attention.emphasis' : '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(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: approval.tool }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: approval.actorUid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["asked ", _jsx(RelativeTime, { datetime: approval.createdAt })] }), decidable && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["expires ", _jsx(RelativeTime, { datetime: approval.expiresAt })] }))] }), _jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 2, m: 0, children: JSON.stringify(approval.argumentsRedacted, null, 2) }), approval.destinationUri && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Writes to", ' ', _jsx(Text, { as: "span", sx: { fontFamily: 'mono' }, children: approval.destinationUri })] })), decidable ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { "aria-label": `Why, for ${approval.tool}`, rows: 2, placeholder: "Why (optional; kept with the decision)", value: noteFor[approval.uid] ?? '', onChange: event => setNoteFor(current => ({
62
+ return (_jsxs(Card, { border: true, shadow: "none", borderColor: decidable ? 'attention.emphasis' : 'border.default', p: 3, display: "grid", gap: 2, minWidth: 0, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: approval.tool }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: approval.actorUid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["asked ", _jsx(RelativeTime, { datetime: approval.createdAt })] }), decidable && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["expires ", _jsx(RelativeTime, { datetime: approval.expiresAt })] }))] }), _jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 2, m: 0, children: JSON.stringify(approval.argumentsRedacted, null, 2) }), approval.destinationUri && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Writes to", ' ', _jsx(Text, { as: "span", sx: { fontFamily: 'mono' }, children: approval.destinationUri })] })), decidable ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { "aria-label": `Why, for ${approval.tool}`, rows: 2, placeholder: "Why (optional; kept with the decision)", value: noteFor[approval.uid] ?? '', onChange: event => setNoteFor(current => ({
63
63
  ...current,
64
64
  [approval.uid]: event.target.value,
65
65
  })) }), _jsxs(Box, { display: "flex", gap: 2, children: [_jsx(Button, { variant: "primary", disabled: decide.isPending, onClick: () => decide.mutate({
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Heading, Label, SegmentedControl, Spinner, Text, } from '@primer/react';
3
3
  import { Blankslate, DataTable, Table } from '@primer/react/experimental';
4
- import { Box } from '@datalayer/primer-addons';
4
+ import { Box, Card } from '@datalayer/primer-addons';
5
5
  import { AlertIcon, CheckCircleIcon, LockIcon, PlugIcon, } from '@primer/octicons-react';
6
6
  import { ClientBadge, McpErrorBlankslate, ScopeList, } from '../../components/mcp/index.js';
7
7
  import { useDisconnectAgent, useMcpActivity, useOrgMcpOverview, } from '../../hooks/useMcp.js';
@@ -90,7 +90,7 @@ export const pagesForRoles = (roles) => {
90
90
  }
91
91
  return [];
92
92
  };
93
- const Metric = ({ label, value, note, tone = 'neutral', }) => (_jsxs(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, minWidth: 0, children: [_jsx(Text, { sx: {
93
+ const Metric = ({ label, value, note, tone = 'neutral', }) => (_jsxs(Card, { border: true, shadow: "none", display: "block", p: 3, minWidth: 0, children: [_jsx(Text, { sx: {
94
94
  display: 'block',
95
95
  fontSize: 4,
96
96
  fontWeight: 'bold',
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useRef, useState } from 'react';
3
3
  import { ActionList, ActionMenu, Button, Heading, Label, Link, RelativeTime, Select, Spinner, Text, } from '@primer/react';
4
4
  import { Blankslate, DataTable, Dialog, Table, } from '@primer/react/experimental';
5
- import { Box } from '@datalayer/primer-addons';
5
+ import { Box, Card } from '@datalayer/primer-addons';
6
6
  import { ChecklistIcon, KebabHorizontalIcon, PlayIcon, PlugIcon, StackIcon, } from '@primer/octicons-react';
7
7
  import { ShareAccessDialog, sandboxSharingUrl } from '../../components/sharing/index.js';
8
8
  import { ClientBadge, McpErrorBlankslate } from '../../components/mcp/index.js';
@@ -54,7 +54,7 @@ const boundTo = (client) => {
54
54
  return '';
55
55
  };
56
56
  const countLook = (tone) => tone === 'danger' ? 'danger.fg' : 'fg.default';
57
- const Count = ({ label, value, tone = 'neutral', note, to, onOpen, }) => (_jsxs(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", minWidth: 0, children: [_jsx(Text, { sx: {
57
+ const Count = ({ label, value, tone = 'neutral', note, to, onOpen, }) => (_jsxs(Card, { border: true, shadow: "none", display: "block", p: 3, minWidth: 0, children: [_jsx(Text, { sx: {
58
58
  display: 'block',
59
59
  fontSize: 4,
60
60
  fontWeight: 'bold',
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useState } from 'react';
3
3
  import { AiAgentIcon } from '@datalayer/icons-react';
4
4
  import { Button, Heading, IconButton, Link, Text } from '@primer/react';
5
- import { Box } from '@datalayer/primer-addons';
5
+ import { Box, Card } from '@datalayer/primer-addons';
6
6
  import { CheckIcon, CopyIcon, KeyIcon, PulseIcon, RssIcon, ShieldCheckIcon, TelescopeIcon, ToolsIcon, } from '@primer/octicons-react';
7
7
  import { useConnectedAgents, useMcpActivity, useMcpMetrics, useTasks, } from '../../hooks/useMcp.js';
8
8
  import { useNavigate } from '../../hooks/index.js';
@@ -56,7 +56,7 @@ export const Copyable = ({ text }) => {
56
56
  } })] }));
57
57
  };
58
58
  /** One of the five summaries: a heading, what it holds, and the way in. */
59
- const Summary = ({ icon: Icon, title, children, action, to, onOpen, }) => (_jsxs(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, display: "grid", gap: 2, alignContent: "start", minWidth: 0, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Icon, { size: 16 }), to ? (_jsx(Link, { href: to, onClick: event => {
59
+ const Summary = ({ icon: Icon, title, children, action, to, onOpen, }) => (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, alignContent: "start", minWidth: 0, children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(Icon, { size: 16 }), to ? (_jsx(Link, { href: to, onClick: event => {
60
60
  // A modified click asks for a new tab; `href` is real, so let it.
61
61
  if (event.metaKey || event.ctrlKey || event.shiftKey) {
62
62
  return;
@@ -77,11 +77,11 @@ export const McpHome = ({ routes, clientDocsBase = '/docs/mcp-clients', showTitl
77
77
  const connected = agents.data ?? [];
78
78
  const runs = tasks.data?.items ?? [];
79
79
  const live = activity.data?.clients.length ?? 0;
80
- return (_jsxs(Box, { display: "grid", gap: 4, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "MCP" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Connect your agents to your notebooks, data and sandboxes through the Model Context Protocol." })] })), showSetup && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap", p: 3, borderRadius: 2, bg: "canvas.subtle", border: "1px solid", borderColor: "border.default", children: [_jsx(Text, { sx: { fontSize: 1 }, children: live > 0
80
+ return (_jsxs(Box, { display: "grid", gap: 4, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "MCP" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "Connect your agents to your notebooks, data and sandboxes through the Model Context Protocol." })] })), showSetup && (_jsxs(_Fragment, { children: [_jsxs(Card, { border: true, shadow: "none", variant: "subtle", flexDirection: "row", display: "flex", alignItems: "center", gap: 3, flexWrap: "wrap", p: 3, children: [_jsx(Text, { sx: { fontSize: 1 }, children: live > 0
81
81
  ? `${plural(live, 'client')} connected right now.`
82
82
  : 'No client is connected right now.' }), _jsx(Button, { size: "small", leadingVisual: PulseIcon, onClick: () => navigate(routes.dashboard), children: "What is going on" })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "The endpoint" }), _jsx(Copyable, { text: endpoint || 'https://mcp.datalayer.run/mcp' }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 2, children: [_jsx(KeyIcon, { size: 14 }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: signedIn
83
83
  ? 'You are signed in, so a client you set up from here authorizes through OAuth without a shared token.'
84
- : 'Sign in to authorize a client through OAuth; without it a client can only use a personal access token.' })] })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Set up a client" }), _jsx(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'], children: MCP_CLIENTS.map(client => (_jsxs(Box, { p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, display: "grid", gap: 2, minWidth: 0, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: client.name }), _jsx(Copyable, { text: mcpSetupCommand(client.setup) }), _jsx(Link, { href: `${clientDocsBase}/${client.setup}`, sx: { fontSize: 0, cursor: 'pointer', textAlign: 'left' }, onClick: event => {
84
+ : 'Sign in to authorize a client through OAuth; without it a client can only use a personal access token.' })] })] }), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Set up a client" }), _jsx(Box, { display: "grid", gap: 3, gridTemplateColumns: ['1fr', 'repeat(2, 1fr)', 'repeat(3, 1fr)'], children: MCP_CLIENTS.map(client => (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, minWidth: 0, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'semibold' }, children: client.name }), _jsx(Copyable, { text: mcpSetupCommand(client.setup) }), _jsx(Link, { href: `${clientDocsBase}/${client.setup}`, sx: { fontSize: 0, cursor: 'pointer', textAlign: 'left' }, onClick: event => {
85
85
  // A modified click is the reader asking for a new tab or
86
86
  // window, and `href` is real so it still leads to the
87
87
  // full page — for that, for the status bar and for the