@datalayer/core 0.0.27 → 1.0.2

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 (62) hide show
  1. package/lib/api/index.d.ts +1 -1
  2. package/lib/api/index.js +1 -1
  3. package/lib/api/spacer/index.d.ts +1 -2
  4. package/lib/api/spacer/index.js +1 -2
  5. package/lib/client/utils/spacerUtils.d.ts +2 -2
  6. package/lib/client/utils/spacerUtils.js +4 -4
  7. package/lib/components/avatars/BoringAvatar.d.ts +3 -1
  8. package/lib/components/avatars/BoringAvatar.js +15 -14
  9. package/lib/components/avatars/BoringAvatar.stories.d.ts +2 -1
  10. package/lib/components/storage/ContentsBrowser.d.ts +6 -0
  11. package/lib/components/storage/ContentsBrowser.js +9 -10
  12. package/lib/hooks/index.d.ts +2 -0
  13. package/lib/hooks/index.js +2 -0
  14. package/lib/hooks/useCache.d.ts +11 -32
  15. package/lib/hooks/useCache.js +54 -226
  16. package/lib/hooks/useProjectStore.d.ts +58 -0
  17. package/lib/hooks/useProjectStore.js +64 -0
  18. package/lib/hooks/useProjects.d.ts +590 -0
  19. package/lib/hooks/useProjects.js +166 -0
  20. package/lib/index.d.ts +0 -1
  21. package/lib/index.js +0 -2
  22. package/lib/models/CreditsDTO.d.ts +1 -1
  23. package/lib/models/CreditsDTO.js +1 -1
  24. package/lib/models/Datasource.d.ts +4 -4
  25. package/lib/models/Datasource.js +7 -7
  26. package/lib/models/EnvironmentDTO.d.ts +3 -3
  27. package/lib/models/EnvironmentDTO.js +3 -3
  28. package/lib/models/HealthCheck.d.ts +2 -2
  29. package/lib/models/HealthCheck.js +2 -2
  30. package/lib/models/ItemDTO.d.ts +3 -3
  31. package/lib/models/ItemDTO.js +10 -10
  32. package/lib/models/LexicalDTO.d.ts +3 -3
  33. package/lib/models/LexicalDTO.js +4 -4
  34. package/lib/models/NotebookDTO.d.ts +3 -3
  35. package/lib/models/NotebookDTO.js +6 -6
  36. package/lib/models/Page.d.ts +2 -0
  37. package/lib/models/RuntimeDTO.d.ts +4 -4
  38. package/lib/models/RuntimeDTO.js +9 -9
  39. package/lib/models/RuntimeSnapshotDTO.d.ts +3 -3
  40. package/lib/models/RuntimeSnapshotDTO.js +7 -7
  41. package/lib/models/Secret.d.ts +4 -4
  42. package/lib/models/Secret.js +7 -7
  43. package/lib/models/Space.js +3 -0
  44. package/lib/models/SpaceDTO.d.ts +3 -3
  45. package/lib/models/SpaceDTO.js +14 -14
  46. package/lib/models/UserDTO.d.ts +2 -2
  47. package/lib/models/UserDTO.js +2 -2
  48. package/lib/views/iam-tokens/Tokens.js +1 -1
  49. package/lib/views/secrets/Secrets.js +1 -1
  50. package/package.json +1 -1
  51. package/lib/api/spacer/agentSpaces.d.ts +0 -193
  52. package/lib/api/spacer/agentSpaces.js +0 -127
  53. package/lib/theme/DatalayerTheme.d.ts +0 -52
  54. package/lib/theme/DatalayerTheme.js +0 -228
  55. package/lib/theme/DatalayerThemeProvider.d.ts +0 -29
  56. package/lib/theme/DatalayerThemeProvider.js +0 -54
  57. package/lib/theme/Palette.d.ts +0 -4
  58. package/lib/theme/Palette.js +0 -10
  59. package/lib/theme/index.d.ts +0 -4
  60. package/lib/theme/index.js +0 -8
  61. package/lib/theme/useSystemColorMode.d.ts +0 -9
  62. package/lib/theme/useSystemColorMode.js +0 -26
@@ -272,14 +272,6 @@ export const queryKeys = {
272
272
  bySpace: (spaceId) => [...queryKeys.items.all(), 'space', spaceId],
273
273
  search: (opts) => [...queryKeys.items.all(), 'search', opts],
274
274
  },
275
- // Agent Spaces
276
- agentSpaces: {
277
- all: () => ['agentSpaces'],
278
- lists: () => [...queryKeys.agentSpaces.all(), 'list'],
279
- details: () => [...queryKeys.agentSpaces.all(), 'detail'],
280
- detail: (id) => [...queryKeys.agentSpaces.details(), id],
281
- public: () => [...queryKeys.agentSpaces.all(), 'public'],
282
- },
283
275
  // Agent Runtimes (runtimes with ai-agents environment)
284
276
  agentRuntimes: {
285
277
  all: () => ['agentRuntimes'],
@@ -1321,17 +1313,20 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1321
1313
  });
1322
1314
  };
1323
1315
  /**
1324
- * Update space with optimistic update
1316
+ * Update space with optimistic update.
1317
+ * Any extra fields (e.g. attached_agent_pod_name_s) are forwarded to the backend.
1325
1318
  */
1326
1319
  const useUpdateSpace = () => {
1327
1320
  return useMutation({
1328
1321
  mutationFn: async (space) => {
1322
+ const { id, name, description, ...extraFields } = space;
1329
1323
  return requestDatalayer({
1330
- url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${space.id}/users/${user?.id}`,
1324
+ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${id}/users/${user?.id}`,
1331
1325
  method: 'PUT',
1332
1326
  body: {
1333
- name: space.name,
1334
- description: space.description,
1327
+ name,
1328
+ description,
1329
+ ...extraFields,
1335
1330
  },
1336
1331
  });
1337
1332
  },
@@ -1348,219 +1343,25 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1348
1343
  },
1349
1344
  });
1350
1345
  };
1351
- // ============================================================================
1352
- // Agent Spaces Hooks
1353
- // ============================================================================
1354
- /**
1355
- * Get agent space by ID
1356
- */
1357
- const useAgentSpace = (uid) => {
1358
- return useQuery({
1359
- queryKey: queryKeys.agentSpaces.detail(uid || ''),
1360
- queryFn: async () => {
1361
- const resp = await requestDatalayer({
1362
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/${uid}`,
1363
- method: 'GET',
1364
- });
1365
- if (resp.success && resp.agentSpace) {
1366
- return resp.agentSpace;
1367
- }
1368
- throw new Error(resp.message || 'Failed to fetch agent space');
1369
- },
1370
- ...DEFAULT_QUERY_OPTIONS,
1371
- enabled: !!uid,
1372
- });
1373
- };
1374
- /**
1375
- * List user's agent spaces
1376
- */
1377
- const useAgentSpaces = () => {
1378
- return useQuery({
1379
- queryKey: queryKeys.agentSpaces.lists(),
1380
- queryFn: async () => {
1381
- const resp = await requestDatalayer({
1382
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces`,
1383
- method: 'GET',
1384
- });
1385
- if (resp.success && resp.agentSpaces) {
1386
- const agentSpaces = resp.agentSpaces;
1387
- // Set detail cache for each agent space
1388
- agentSpaces.forEach((agentSpace) => {
1389
- queryClient.setQueryData(queryKeys.agentSpaces.detail(agentSpace.id), agentSpace);
1390
- });
1391
- return agentSpaces;
1392
- }
1393
- return [];
1394
- },
1395
- ...DEFAULT_QUERY_OPTIONS,
1396
- enabled: !!user,
1397
- });
1398
- };
1399
1346
  /**
1400
- * List public agent spaces (Library)
1347
+ * Delete a space and all its contents.
1401
1348
  */
1402
- const usePublicAgentSpaces = () => {
1403
- return useQuery({
1404
- queryKey: queryKeys.agentSpaces.public(),
1405
- queryFn: async () => {
1406
- const resp = await requestDatalayer({
1407
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/public`,
1408
- method: 'GET',
1409
- });
1410
- if (resp.success && resp.agentSpaces) {
1411
- return resp.agentSpaces;
1412
- }
1413
- return [];
1414
- },
1415
- ...DEFAULT_QUERY_OPTIONS,
1416
- });
1417
- };
1418
- /**
1419
- * Create agent space
1420
- */
1421
- const useCreateAgentSpace = () => {
1349
+ const useDeleteSpace = () => {
1422
1350
  return useMutation({
1423
- mutationFn: async (data) => {
1351
+ mutationFn: async (spaceUid) => {
1424
1352
  return requestDatalayer({
1425
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces`,
1426
- method: 'POST',
1427
- body: data,
1428
- });
1429
- },
1430
- onSuccess: resp => {
1431
- if (resp.success && resp.agentSpace) {
1432
- const agentSpace = resp.agentSpace;
1433
- // Set detail cache
1434
- queryClient.setQueryData(queryKeys.agentSpaces.detail(agentSpace.id), agentSpace);
1435
- // Invalidate all agent space queries
1436
- queryClient.invalidateQueries({
1437
- queryKey: queryKeys.agentSpaces.all(),
1438
- });
1439
- }
1440
- },
1441
- });
1442
- };
1443
- /**
1444
- * Update agent space
1445
- */
1446
- const useUpdateAgentSpace = () => {
1447
- return useMutation({
1448
- mutationFn: async ({ uid, data, }) => {
1449
- return requestDatalayer({
1450
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/${uid}`,
1451
- method: 'PUT',
1452
- body: data,
1453
- });
1454
- },
1455
- onSuccess: (resp, { uid }) => {
1456
- if (resp.success) {
1457
- // Invalidate detail cache
1458
- queryClient.invalidateQueries({
1459
- queryKey: queryKeys.agentSpaces.detail(uid),
1460
- });
1461
- // Invalidate all agent space queries
1462
- queryClient.invalidateQueries({
1463
- queryKey: queryKeys.agentSpaces.all(),
1464
- });
1465
- }
1466
- },
1467
- });
1468
- };
1469
- /**
1470
- * Delete agent space
1471
- */
1472
- const useDeleteAgentSpace = () => {
1473
- return useMutation({
1474
- mutationFn: async (uid) => {
1475
- return requestDatalayer({
1476
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/${uid}`,
1353
+ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${spaceUid}`,
1477
1354
  method: 'DELETE',
1478
1355
  });
1479
1356
  },
1480
1357
  onSuccess: () => {
1481
- // Invalidate all agent space queries
1358
+ // Invalidate all space queries
1482
1359
  queryClient.invalidateQueries({
1483
- queryKey: queryKeys.agentSpaces.all(),
1484
- });
1485
- },
1486
- });
1487
- };
1488
- /**
1489
- * Make agent space public
1490
- */
1491
- const useMakeAgentSpacePublic = () => {
1492
- return useMutation({
1493
- mutationFn: async (uid) => {
1494
- return requestDatalayer({
1495
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/${uid}/public`,
1496
- method: 'POST',
1497
- });
1498
- },
1499
- onSuccess: (resp, uid) => {
1500
- if (resp.success) {
1501
- queryClient.invalidateQueries({
1502
- queryKey: queryKeys.agentSpaces.detail(uid),
1503
- });
1504
- queryClient.invalidateQueries({
1505
- queryKey: queryKeys.agentSpaces.all(),
1506
- });
1507
- }
1508
- },
1509
- });
1510
- };
1511
- /**
1512
- * Make agent space private
1513
- */
1514
- const useMakeAgentSpacePrivate = () => {
1515
- return useMutation({
1516
- mutationFn: async (uid) => {
1517
- return requestDatalayer({
1518
- url: `${configuration.spacerRunUrl}/api/spacer/v1/agent-spaces/${uid}/private`,
1519
- method: 'POST',
1360
+ queryKey: queryKeys.spaces.all(),
1520
1361
  });
1521
1362
  },
1522
- onSuccess: (resp, uid) => {
1523
- if (resp.success) {
1524
- queryClient.invalidateQueries({
1525
- queryKey: queryKeys.agentSpaces.detail(uid),
1526
- });
1527
- queryClient.invalidateQueries({
1528
- queryKey: queryKeys.agentSpaces.all(),
1529
- });
1530
- }
1531
- },
1532
1363
  });
1533
1364
  };
1534
- /**
1535
- * Refresh agent space data
1536
- */
1537
- const useRefreshAgentSpace = () => {
1538
- return (uid) => {
1539
- queryClient.invalidateQueries({
1540
- queryKey: queryKeys.agentSpaces.detail(uid),
1541
- });
1542
- };
1543
- };
1544
- /**
1545
- * Refresh agent spaces list
1546
- */
1547
- const useRefreshAgentSpaces = () => {
1548
- return () => {
1549
- queryClient.invalidateQueries({
1550
- queryKey: queryKeys.agentSpaces.all(),
1551
- });
1552
- };
1553
- };
1554
- /**
1555
- * Refresh public agent spaces list
1556
- */
1557
- const useRefreshPublicAgentSpaces = () => {
1558
- return () => {
1559
- queryClient.invalidateQueries({
1560
- queryKey: queryKeys.agentSpaces.public(),
1561
- });
1562
- };
1563
- };
1564
1365
  /**
1565
1366
  * List agent runtimes (runtimes with ai-agents-env environment)
1566
1367
  */
@@ -1604,6 +1405,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1604
1405
  // Map ingress URL to url for UI consistency
1605
1406
  url: rt.ingress,
1606
1407
  messageCount: 0, // Default for UI compatibility
1408
+ agent_spec_id: rt.agent_spec_id || undefined,
1607
1409
  }));
1608
1410
  // Set detail cache for each runtime
1609
1411
  agentRuntimes.forEach((runtime) => {
@@ -1652,12 +1454,22 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1652
1454
  // Map ingress URL to url for UI consistency
1653
1455
  url: rt.ingress,
1654
1456
  messageCount: 0,
1457
+ agent_spec_id: rt.agent_spec_id || undefined,
1655
1458
  };
1656
1459
  }
1657
1460
  throw new Error('Failed to fetch agent runtime');
1658
1461
  },
1659
1462
  ...DEFAULT_QUERY_OPTIONS,
1660
- refetchInterval: 5000, // Refetch every 5 seconds for status
1463
+ // Poll every 5 seconds while the runtime exists. Stop polling on error
1464
+ // (e.g. 404 — runtime deleted, 500 — broken state) to avoid hammering the server.
1465
+ refetchInterval: query => {
1466
+ if (query.state.error)
1467
+ return false;
1468
+ return 5000;
1469
+ },
1470
+ // Don't retry failed detail requests. The refetchInterval handles
1471
+ // periodic re-checks, so retrying only generates duplicate failing requests.
1472
+ retry: false,
1661
1473
  enabled: !!podName,
1662
1474
  });
1663
1475
  };
@@ -1669,11 +1481,12 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1669
1481
  method: 'POST',
1670
1482
  body: {
1671
1483
  environment_name: data.environmentName || 'ai-agents-env',
1672
- given_name: data.givenName || 'Agent Space',
1484
+ given_name: data.givenName || 'Agent',
1673
1485
  credits_limit: data.creditsLimit || 10,
1674
1486
  type: data.type || 'notebook',
1675
1487
  editor_variant: data.editorVariant || 'none',
1676
1488
  enable_codemode: data.enableCodemode ?? false,
1489
+ agent_spec_id: data.agentSpecId || undefined,
1677
1490
  },
1678
1491
  });
1679
1492
  },
@@ -1691,6 +1504,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
1691
1504
  // Map ingress URL to url for UI consistency
1692
1505
  url: rt.ingress,
1693
1506
  messageCount: 0,
1507
+ agent_spec_id: rt.agent_spec_id || undefined,
1694
1508
  });
1695
1509
  // Invalidate list
1696
1510
  queryClient.invalidateQueries({
@@ -3613,6 +3427,30 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
3613
3427
  ...DEFAULT_QUERY_OPTIONS,
3614
3428
  });
3615
3429
  };
3430
+ /**
3431
+ * Get default items (notebook UID & document UID) for a space / project.
3432
+ * Calls GET /api/spacer/v1/spaces/{spaceId}/default-items
3433
+ */
3434
+ const useSpaceDefaultItems = (spaceId) => {
3435
+ return useQuery({
3436
+ queryKey: ['spaces', spaceId, 'default-items'],
3437
+ queryFn: async () => {
3438
+ const resp = await requestDatalayer({
3439
+ url: `${configuration.spacerRunUrl}/api/spacer/v1/spaces/${spaceId}/default-items`,
3440
+ method: 'GET',
3441
+ });
3442
+ if (resp.success) {
3443
+ return {
3444
+ defaultNotebookUid: resp.default_notebook_uid,
3445
+ defaultDocumentUid: resp.default_document_uid,
3446
+ };
3447
+ }
3448
+ throw new Error(resp.message || 'Failed to fetch default items');
3449
+ },
3450
+ enabled: !!spaceId,
3451
+ ...DEFAULT_QUERY_OPTIONS,
3452
+ });
3453
+ };
3616
3454
  /**
3617
3455
  * Make item public
3618
3456
  */
@@ -6467,6 +6305,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6467
6305
  useUserSpaces,
6468
6306
  useCreateSpace,
6469
6307
  useUpdateSpace,
6308
+ useDeleteSpace,
6470
6309
  useUpdateOrganizationSpace,
6471
6310
  useAddMemberToOrganizationSpace,
6472
6311
  useRemoveMemberFromOrganizationSpace,
@@ -6478,18 +6317,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6478
6317
  useRefreshUserSpace,
6479
6318
  useRefreshLayout,
6480
6319
  useExportSpace,
6481
- // Agent Spaces
6482
- useAgentSpace,
6483
- useAgentSpaces,
6484
- usePublicAgentSpaces,
6485
- useCreateAgentSpace,
6486
- useUpdateAgentSpace,
6487
- useDeleteAgentSpace,
6488
- useMakeAgentSpacePublic,
6489
- useMakeAgentSpacePrivate,
6490
- useRefreshAgentSpace,
6491
- useRefreshAgentSpaces,
6492
- useRefreshPublicAgentSpaces,
6493
6320
  // Agent Runtimes (runtimes with ai-agents environment)
6494
6321
  useAgentRuntime,
6495
6322
  useAgentRuntimes,
@@ -6576,6 +6403,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6576
6403
  // Items (Generic)
6577
6404
  useDeleteItem,
6578
6405
  useSpaceItems,
6406
+ useSpaceDefaultItems,
6579
6407
  useMakeItemPublic,
6580
6408
  useMakeItemPrivate,
6581
6409
  useSearchPublicItems,
@@ -0,0 +1,58 @@
1
+ import type { Session } from '@jupyterlab/services';
2
+ import type { IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel';
3
+ /**
4
+ * Per-project runtime state tracked in memory.
5
+ *
6
+ * This is **not** persisted — it represents live runtime objects
7
+ * (kernel connections, sessions) that only exist while the project
8
+ * view is mounted.
9
+ */
10
+ export type ProjectRuntimeEntry = {
11
+ /** The Jupyter session connection (notebook ↔ kernel bridge). */
12
+ sessionConnection?: Session.ISessionConnection;
13
+ /** The assigned agent pod name (mirrors project.attachedAgentPodName). */
14
+ agentPodName?: string;
15
+ /** Display name of the assigned agent. */
16
+ agentName?: string;
17
+ /** Agent runtime status (running, starting, terminated, etc.). */
18
+ agentStatus?: string;
19
+ /** The agent spec ID used to create the runtime. */
20
+ agentSpecId?: string;
21
+ };
22
+ export type ProjectStoreState = {
23
+ /** Map of projectId → runtime entry. */
24
+ projects: Record<string, ProjectRuntimeEntry>;
25
+ /** The currently active/viewed project ID (for sidebar highlighting). */
26
+ currentProjectId: string | undefined;
27
+ /** Set the currently active project (call on mount, clear on unmount). */
28
+ setCurrentProjectId: (projectId: string | undefined) => void;
29
+ /** Set or update the session connection for a project. */
30
+ setSessionConnection: (projectId: string, sessionConnection: Session.ISessionConnection | undefined) => void;
31
+ /** Set the assigned agent info for a project. */
32
+ setAgent: (projectId: string, agentPodName: string | undefined, agentName?: string, agentStatus?: string, agentSpecId?: string) => void;
33
+ /** Remove all runtime state for a project (e.g. on unmount). */
34
+ clearProject: (projectId: string) => void;
35
+ /** Get the kernel connection for a project, or undefined. */
36
+ getKernel: (projectId: string) => IKernelConnection | null | undefined;
37
+ /** Get the full entry for a project. */
38
+ getEntry: (projectId: string) => ProjectRuntimeEntry | undefined;
39
+ };
40
+ /**
41
+ * Zustand store for per-project runtime state (kernel connections, agents).
42
+ *
43
+ * Not persisted — the data is ephemeral and only valid while the project
44
+ * component is mounted and a kernel/agent is active.
45
+ *
46
+ * Usage:
47
+ * ```ts
48
+ * import { useProjectStore } from '@datalayer/core/lib/hooks';
49
+ *
50
+ * // In Project.tsx — store session when Notebook connects:
51
+ * const setSessionConnection = useProjectStore(s => s.setSessionConnection);
52
+ * <Notebook onSessionConnection={sc => setSessionConnection(projectId, sc)} />
53
+ *
54
+ * // Anywhere — read the kernel for a project:
55
+ * const kernel = useProjectStore(s => s.getKernel(projectId));
56
+ * ```
57
+ */
58
+ export declare const useProjectStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ProjectStoreState>>;
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { create } from 'zustand';
6
+ /**
7
+ * Zustand store for per-project runtime state (kernel connections, agents).
8
+ *
9
+ * Not persisted — the data is ephemeral and only valid while the project
10
+ * component is mounted and a kernel/agent is active.
11
+ *
12
+ * Usage:
13
+ * ```ts
14
+ * import { useProjectStore } from '@datalayer/core/lib/hooks';
15
+ *
16
+ * // In Project.tsx — store session when Notebook connects:
17
+ * const setSessionConnection = useProjectStore(s => s.setSessionConnection);
18
+ * <Notebook onSessionConnection={sc => setSessionConnection(projectId, sc)} />
19
+ *
20
+ * // Anywhere — read the kernel for a project:
21
+ * const kernel = useProjectStore(s => s.getKernel(projectId));
22
+ * ```
23
+ */
24
+ export const useProjectStore = create()((set, get) => ({
25
+ projects: {},
26
+ currentProjectId: undefined,
27
+ setCurrentProjectId: projectId => set({ currentProjectId: projectId }),
28
+ setSessionConnection: (projectId, sessionConnection) => set(state => ({
29
+ projects: {
30
+ ...state.projects,
31
+ [projectId]: {
32
+ ...state.projects[projectId],
33
+ sessionConnection,
34
+ },
35
+ },
36
+ })),
37
+ setAgent: (projectId, agentPodName, agentName, agentStatus, agentSpecId) => set(state => ({
38
+ projects: {
39
+ ...state.projects,
40
+ [projectId]: {
41
+ ...state.projects[projectId],
42
+ agentPodName,
43
+ agentName,
44
+ // When removing the agent, also clear the status.
45
+ // When setting, use provided status or preserve existing.
46
+ agentStatus: agentPodName !== undefined
47
+ ? (agentStatus ?? state.projects[projectId]?.agentStatus)
48
+ : undefined,
49
+ agentSpecId: agentPodName !== undefined
50
+ ? (agentSpecId ?? state.projects[projectId]?.agentSpecId)
51
+ : undefined,
52
+ },
53
+ },
54
+ })),
55
+ clearProject: projectId => set(state => {
56
+ const { [projectId]: _, ...rest } = state.projects;
57
+ return { projects: rest };
58
+ }),
59
+ getKernel: projectId => {
60
+ const entry = get().projects[projectId];
61
+ return entry?.sessionConnection?.kernel;
62
+ },
63
+ getEntry: projectId => get().projects[projectId],
64
+ }));