@datalayer/agent-runtimes 1.0.18 → 1.0.20

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 (146) hide show
  1. package/README.md +14 -6
  2. package/lib/AgentNode.js +567 -67
  3. package/lib/App.js +2 -2
  4. package/lib/agent-node/AgentNodeGallery.d.ts +80 -0
  5. package/lib/agent-node/AgentNodeGallery.js +627 -0
  6. package/lib/agent-notebook-main.js +0 -1
  7. package/lib/api/runtimes/runtimes.d.ts +57 -0
  8. package/lib/api/runtimes/runtimes.js +55 -1
  9. package/lib/chat/Chat.d.ts +14 -1
  10. package/lib/chat/Chat.js +22 -7
  11. package/lib/chat/base/ChatBase.js +86 -23
  12. package/lib/chat/document/EphemeralDocument.d.ts +49 -1
  13. package/lib/chat/document/EphemeralDocument.js +141 -15
  14. package/lib/chat/header/ChatHeaderBase.d.ts +3 -1
  15. package/lib/chat/header/ChatHeaderBase.js +5 -3
  16. package/lib/chat/messages/ChatMessageList.d.ts +19 -0
  17. package/lib/chat/messages/ChatMessageList.js +180 -9
  18. package/lib/chat/notebook/EphemeralNotebook.d.ts +40 -1
  19. package/lib/chat/notebook/EphemeralNotebook.js +49 -13
  20. package/lib/chat/prompt/InputPrompt.js +1 -1
  21. package/lib/chat/tools/SubagentActivity.d.ts +20 -0
  22. package/lib/chat/tools/SubagentActivity.js +154 -0
  23. package/lib/chat/tools/index.d.ts +1 -0
  24. package/lib/chat/tools/index.js +1 -0
  25. package/lib/chat/usage/TokenUsageBar.js +3 -3
  26. package/lib/client/AgentRuntimesClient.d.ts +5 -0
  27. package/lib/client/AgentsMixin.js +3 -1
  28. package/lib/client/mixins/RuntimesMixin.d.ts +12 -0
  29. package/lib/client/mixins/RuntimesMixin.js +24 -1
  30. package/lib/components/AgentSummary.d.ts +1 -0
  31. package/lib/components/AgentSummary.js +66 -10
  32. package/lib/components/runtimes/RuntimePickerBase.js +64 -29
  33. package/lib/components/runtimes/RuntimePickerCell.js +1 -1
  34. package/lib/components/runtimes/RuntimePickerNotebook.js +1 -1
  35. package/lib/components/runtimes/RuntimeSimplePicker.js +3 -2
  36. package/lib/components/runtimes/RuntimeUtils.js +2 -2
  37. package/lib/context/GraphFlowChart.js +1 -1
  38. package/lib/context/TokenUsageChart.js +1 -1
  39. package/lib/context/TurnGraphChart.js +2 -2
  40. package/lib/examples/A2UiAgentExample.js +4 -4
  41. package/lib/examples/A2UiComponentsGalleryExample.js +3 -3
  42. package/lib/examples/A2UiContactCardExample.js +3 -3
  43. package/lib/examples/A2UiRestaurantExample.js +2 -2
  44. package/lib/examples/A2UiViewerExample.js +3 -3
  45. package/lib/examples/AgUiAgenticExample.js +3 -3
  46. package/lib/examples/AgUiBackendToolRenderingExample.js +2 -2
  47. package/lib/examples/AgUiHaikuGenUiExample.js +3 -3
  48. package/lib/examples/AgUiHumanInTheLoopExample.js +5 -5
  49. package/lib/examples/AgUiSharedStateExample.js +3 -3
  50. package/lib/examples/AgUiToolsBasedGenUiExample.js +7 -5
  51. package/lib/examples/AgentCheckpointsExample.js +6 -5
  52. package/lib/examples/{AgentSandboxExample.d.ts → AgentCodeSandboxExample.d.ts} +4 -4
  53. package/lib/examples/{AgentSandboxExample.js → AgentCodeSandboxExample.js} +10 -9
  54. package/lib/examples/AgentCodeSandboxesExample.js +2 -2
  55. package/lib/examples/AgentCodemodeExample.js +3 -3
  56. package/lib/examples/AgentCompactionExample.d.ts +12 -0
  57. package/lib/examples/AgentCompactionExample.js +244 -0
  58. package/lib/examples/AgentEvalsExample.js +10 -20
  59. package/lib/examples/AgentGuardrailsExample.js +2 -2
  60. package/lib/examples/AgentHooksExample.js +1 -1
  61. package/lib/examples/AgentInferenceProviderExample.js +6 -8
  62. package/lib/examples/AgentLoopExample.js +3 -3
  63. package/lib/examples/AgentMCPExample.js +3 -3
  64. package/lib/examples/AgentMemoryExample.d.ts +4 -2
  65. package/lib/examples/AgentMemoryExample.js +93 -10
  66. package/lib/examples/AgentMonitoringExample.js +2 -2
  67. package/lib/examples/AgentNotificationsExample.js +1 -1
  68. package/lib/examples/AgentOtelExample.d.ts +2 -3
  69. package/lib/examples/AgentOtelExample.js +7 -12
  70. package/lib/examples/AgentOutputsExample.js +5 -5
  71. package/lib/examples/AgentParametersExample.js +3 -3
  72. package/lib/examples/AgentSkillsExample.js +4 -4
  73. package/lib/examples/AgentSubagentsExample.d.ts +2 -2
  74. package/lib/examples/AgentSubagentsExample.js +43 -34
  75. package/lib/examples/AgentToolApprovalsExample.js +1 -1
  76. package/lib/examples/AgentTriggersExample.js +9 -11
  77. package/lib/examples/AgentspecsExample.js +13 -5
  78. package/lib/examples/CellExample.d.ts +6 -0
  79. package/lib/examples/CellExample.js +84 -0
  80. package/lib/examples/ChatCustomExample.js +1 -1
  81. package/lib/examples/ChatExample.js +1 -1
  82. package/lib/examples/ChatStandaloneExample.js +2 -2
  83. package/lib/examples/HomeExample.js +29 -28
  84. package/lib/examples/Lexical2Example.js +2 -2
  85. package/lib/examples/LexicalAgentExample.js +1 -1
  86. package/lib/examples/LexicalAgentSidebarExample.js +2 -2
  87. package/lib/examples/NotebookAgentSidebarExample.d.ts +1 -1
  88. package/lib/examples/NotebookAgentSidebarExample.js +5 -4
  89. package/lib/examples/NotebookCollaborationExample.js +3 -3
  90. package/lib/examples/NotebookExample.js +2 -2
  91. package/lib/examples/components/ExampleWrapper.js +5 -0
  92. package/lib/examples/components/Header.js +2 -2
  93. package/lib/examples/components/HeaderControls.js +1 -1
  94. package/lib/examples/components/LexicalEditor.js +1 -1
  95. package/lib/examples/components/MainContent.js +1 -1
  96. package/lib/examples/components/MockFileBrowser.js +8 -8
  97. package/lib/examples/components/TimeTravel.js +1 -1
  98. package/lib/examples/components/haiku/HaikuDisplay.js +2 -2
  99. package/lib/examples/example-selector.js +4 -3
  100. package/lib/examples/hooks/useExampleAgentRuntime.js +11 -1
  101. package/lib/examples/hooks/useExampleAgentRuntimes.d.ts +7 -0
  102. package/lib/examples/hooks/useExampleAgentRuntimes.js +82 -0
  103. package/lib/examples/index.d.ts +2 -1
  104. package/lib/examples/index.js +2 -1
  105. package/lib/examples/main.js +359 -46
  106. package/lib/examples/utils/agentSummaryStore.d.ts +17 -0
  107. package/lib/examples/utils/themedProvider.js +32 -2
  108. package/lib/examples/utils/useExampleAgentRuntimesUrl.js +2 -1
  109. package/lib/examples2/NotebookMutationsKernel.js +6 -3
  110. package/lib/examples2/NotebookMutationsServiceManager.js +6 -3
  111. package/lib/examples2/ReactRouterExample.js +5 -5
  112. package/lib/examples2/main.js +38 -0
  113. package/lib/hooks/useAIAgentsWebSocket.js +35 -0
  114. package/lib/hooks/useAgentRuntimes.d.ts +22 -0
  115. package/lib/hooks/useAgentRuntimes.js +192 -5
  116. package/lib/hooks/useCheckpoints.js +2 -2
  117. package/lib/hooks/useToolApprovals.d.ts +10 -4
  118. package/lib/hooks/useToolApprovals.js +11 -5
  119. package/lib/identity/AgentIdentity.js +1 -2
  120. package/lib/models/Runtime.d.ts +10 -8
  121. package/lib/models/RuntimeDTO.d.ts +5 -3
  122. package/lib/models/RuntimeDTO.js +3 -3
  123. package/lib/runtimes/actions.js +3 -1
  124. package/lib/runtimes/apis.d.ts +7 -0
  125. package/lib/shims/primerAddonsBox.d.ts +1 -0
  126. package/lib/shims/primerAddonsBox.js +7 -0
  127. package/lib/specs/agents/agents.d.ts +51 -0
  128. package/lib/specs/agents/agents.js +3389 -94
  129. package/lib/specs/models.d.ts +2 -0
  130. package/lib/specs/models.js +37 -0
  131. package/lib/stores/agentRuntimeStore.d.ts +32 -1
  132. package/lib/stores/agentRuntimeStore.js +65 -1
  133. package/lib/stores/index.d.ts +1 -1
  134. package/lib/stores/index.js +1 -1
  135. package/lib/types/agents.d.ts +9 -3
  136. package/lib/types/agentspecs.d.ts +2 -0
  137. package/lib/types/chat.d.ts +83 -0
  138. package/lib/types/models.d.ts +5 -0
  139. package/lib/types/stream.d.ts +58 -1
  140. package/package.json +6 -5
  141. package/scripts/codegen/generate_agents.py +7 -0
  142. package/scripts/codegen/generate_models.py +22 -9
  143. package/scripts/gen_workers.py +340 -0
  144. package/lib/examples/CellSimpleExample.d.ts +0 -6
  145. package/lib/examples/CellSimpleExample.js +0 -20
  146. package/scripts/codegen/__pycache__/generate_sandbox_agents.cpython-313.pyc +0 -0
package/lib/AgentNode.js CHANGED
@@ -3,25 +3,116 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  * Copyright (c) 2025-2026 Datalayer, Inc.
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
7
7
  import { useQueryClient } from '@tanstack/react-query';
8
- import { Box, DatalayerLogoText, DatalayerThemeProvider, getLogoColors, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
9
- import { AppearanceControlsWithStore } from '@datalayer/primer-addons/lib/components/appearance';
10
- import { ActionList, ActionMenu, Avatar, Button, FormControl, Heading, Label, PageHeader, PageLayout, Text, } from '@primer/react';
11
- import { CommentIcon, HomeIcon, KeyAsteriskIcon, LockIcon, MoonIcon, PeopleIcon, PersonIcon, GearIcon, SignInIcon, SignOutIcon, } from '@primer/octicons-react';
8
+ import { AiAgentIcon, AlienIcon } from '@datalayer/icons-react';
9
+ import { DatalayerLogoText, DatalayerThemeProvider, getColorPalette, getLogoColors, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
10
+ import { AppearanceMenu } from '@datalayer/primer-addons/lib/components/appearance';
11
+ import { ActionList, ActionMenu, Box, Button, Heading, Label, PageHeader, PageLayout, Text, } from '@primer/react';
12
+ import { CommentIcon, HomeIcon, KeyAsteriskIcon, LockIcon, MoonIcon, PeopleIcon, PersonIcon, GearIcon, SignOutIcon, } from '@primer/octicons-react';
12
13
  import { SignInSimple } from '@datalayer/core/lib/views/iam';
14
+ import { UserAvatar } from '@datalayer/core/lib/components/avatars';
13
15
  import { UserBadge } from '@datalayer/core/lib/views/profile';
14
16
  import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
15
17
  import { useIAMStore } from '@datalayer/core/lib/state';
16
18
  import { BillingEntitySelect, } from '@datalayer/core/lib/components/billing';
17
19
  import { ShareAccessComponent } from '@datalayer/core/lib/components/sharing';
18
20
  import { useAgentNodeThemeStore } from './agent-node/themeStore';
21
+ import { AgentNodeGallery } from './agent-node/AgentNodeGallery';
19
22
  import { Chat } from './chat';
23
+ import { DatalayerCollaborationProvider } from './collaboration';
20
24
  import '../style/primer-primitives.css';
21
25
  setupPrimerPortals();
22
26
  const AGENT_RUNTIMES_BASE_URL = (import.meta.env.VITE_DATALAYER_AGENT_RUNTIMES_URL ||
23
27
  import.meta.env.VITE_BASE_URL ||
24
28
  window.location.origin).replace(/\/$/, '');
29
+ /**
30
+ * LOCAL Jupyter sandbox endpoint shared by the node's agent and the chat's
31
+ * ephemeral notebook/document surfaces. When set (node-local dev, injected via
32
+ * `VITE_JUPYTER_SANDBOX_URL`), the surfaces bind their kernel straight to this
33
+ * Jupyter server — no proxy or tunnel — exactly like `NotebookAgentExample`.
34
+ * Parsed once into a clean base URL + token for `ServerConnection`.
35
+ */
36
+ const LOCAL_JUPYTER_SANDBOX = (() => {
37
+ const raw = String(import.meta.env.VITE_JUPYTER_SANDBOX_URL || '').trim();
38
+ if (!raw) {
39
+ return undefined;
40
+ }
41
+ try {
42
+ const url = new URL(raw);
43
+ const token = url.searchParams.get('token') || undefined;
44
+ url.searchParams.delete('token');
45
+ const baseUrl = `${url.origin}${url.pathname}`.replace(/\/$/, '');
46
+ return { baseUrl, token };
47
+ }
48
+ catch {
49
+ return { baseUrl: raw.replace(/\/$/, ''), token: undefined };
50
+ }
51
+ })();
52
+ const DEFAULT_DATALAYER_URL = 'https://prod1.datalayer.run';
53
+ /**
54
+ * localStorage key recording that the user explicitly signed out. When set, the
55
+ * UI must not silently re-authenticate from the env-supplied DATALAYER_API_KEY
56
+ * via /auth/bootstrap, so the sign-out survives a page refresh.
57
+ */
58
+ const AUTO_BOOTSTRAP_DISABLED_KEY = 'agent-node-auto-bootstrap-disabled';
59
+ /** Persist (or clear) the signed-out intent in localStorage. */
60
+ function setAutoBootstrapDisabled(disabled) {
61
+ try {
62
+ if (disabled) {
63
+ window.localStorage.setItem(AUTO_BOOTSTRAP_DISABLED_KEY, 'true');
64
+ }
65
+ else {
66
+ window.localStorage.removeItem(AUTO_BOOTSTRAP_DISABLED_KEY);
67
+ }
68
+ }
69
+ catch {
70
+ // Ignore storage failures (private mode, disabled storage, etc.).
71
+ }
72
+ }
73
+ /**
74
+ * Read a service URL from the server-injected `datalayer-config-data` script
75
+ * tag. The agent-runtimes Python server injects this tag (populated from the
76
+ * `DATALAYER_*_URL` environment variables, e.g. from `plane local`) when it
77
+ * serves the built agent pages. Reading it at runtime lets the node target the
78
+ * configured services (IAM, runtimes, ...) instead of the build-time-baked
79
+ * `VITE_DATALAYER_URL`, which defaults to production because `make build` runs
80
+ * without the local environment.
81
+ */
82
+ const getConfigUrlFromDocument = (...keys) => {
83
+ if (typeof document === 'undefined') {
84
+ return undefined;
85
+ }
86
+ const el = document.getElementById('datalayer-config-data');
87
+ if (!el?.textContent) {
88
+ return undefined;
89
+ }
90
+ try {
91
+ const config = JSON.parse(el.textContent);
92
+ for (const key of keys) {
93
+ const value = config?.[key];
94
+ if (typeof value === 'string' && value.trim()) {
95
+ return value.replace(/\/$/, '');
96
+ }
97
+ }
98
+ }
99
+ catch {
100
+ // Ignore malformed config; fall back to env / default.
101
+ }
102
+ return undefined;
103
+ };
104
+ /** Resolve the base Datalayer URL: injected config → VITE env → production. */
105
+ const resolveDatalayerUrl = () => getConfigUrlFromDocument('datalayerUrl', 'iamUrl') ||
106
+ import.meta.env?.VITE_DATALAYER_URL ||
107
+ DEFAULT_DATALAYER_URL;
108
+ /** Resolve the runtimes URL: injected config → VITE env → base Datalayer URL. */
109
+ const resolveRuntimesUrl = () => getConfigUrlFromDocument('runtimesUrl') ||
110
+ import.meta.env?.VITE_DATALAYER_RUNTIMES_URL ||
111
+ resolveDatalayerUrl();
112
+ /** Resolve the spacer URL: injected config → VITE env → base Datalayer URL. */
113
+ const resolveSpacerUrl = () => getConfigUrlFromDocument('spacerUrl') ||
114
+ import.meta.env?.VITE_DATALAYER_SPACER_URL ||
115
+ resolveDatalayerUrl();
25
116
  const MODE_CARDS = [
26
117
  {
27
118
  mode: 'private',
@@ -52,21 +143,33 @@ const DEFAULT_CONFIGURATION = {
52
143
  * but assembled exclusively from core building blocks so agent-runtimes does
53
144
  * not pull in the `ui` package.
54
145
  */
55
- function AgentNodeProfileView({ token }) {
146
+ function AgentNodeProfileView({ token, onTokenExpired, avatarFallbackBackground, avatarFallbackForeground, }) {
56
147
  const user = useIAMStore(state => state.user);
148
+ const hasRealAvatar = (url) => {
149
+ if (!url) {
150
+ return false;
151
+ }
152
+ if (url.startsWith('https://www.gravatar.com/avatar')) {
153
+ return false;
154
+ }
155
+ return true;
156
+ };
57
157
  const display = useMemo(() => {
58
158
  if (!user)
59
159
  return null;
60
160
  const u = user;
61
- const displayName = [u.first_name, u.last_name].filter(Boolean).join(' ').trim() ||
62
- u.display_name ||
63
- u.name ||
64
- u.handle ||
65
- u.email ||
66
- 'Datalayer user';
161
+ const displayName = u.display_name ||
162
+ u.displayName ||
163
+ u.profile?.display_name ||
164
+ u.profile?.displayName ||
165
+ u.full_name ||
166
+ u.fullName ||
167
+ [u.first_name, u.last_name].filter(Boolean).join(' ').trim() ||
168
+ '';
67
169
  const username = u.username || u.handle || (u.email ? String(u.email).split('@')[0] : '');
170
+ const headingDisplayName = displayName || username || u.email || 'Datalayer user';
68
171
  const initials = u.initials ||
69
- String(displayName)
172
+ String(headingDisplayName)
70
173
  .split(/\s+/)
71
174
  .filter(Boolean)
72
175
  .slice(0, 2)
@@ -76,6 +179,7 @@ function AgentNodeProfileView({ token }) {
76
179
  id: String(u.id || u.uid || ''),
77
180
  username,
78
181
  displayName,
182
+ headingDisplayName,
79
183
  initials,
80
184
  origin: String(u.origin || 'datalayer'),
81
185
  handle: u.handle ? `@${u.handle}` : '',
@@ -92,29 +196,52 @@ function AgentNodeProfileView({ token }) {
92
196
  display: 'flex',
93
197
  flexDirection: 'column',
94
198
  gap: 3,
95
- }, children: !token ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Sign in to view your profile." })) : !display ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading profile\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Identity" }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [display.avatarUrl ? (_jsx(Avatar, { src: display.avatarUrl, size: 72, alt: display.displayName })) : (_jsx(Box, { sx: {
96
- width: 72,
97
- height: 72,
98
- borderRadius: '50%',
99
- bg: 'canvas.subtle',
100
- display: 'inline-flex',
101
- alignItems: 'center',
102
- justifyContent: 'center',
103
- color: 'fg.muted',
104
- }, children: _jsx(PersonIcon, { size: 30 }) })), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Heading, { sx: { fontSize: 3, mb: 1 }, children: display.displayName }), display.handle && (_jsx(Text, { sx: { color: 'fg.muted' }, children: display.handle })), display.email && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: display.email }))] })] }), display.id && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(Label, { size: "large", variant: "secondary", children: display.id }) })), _jsxs(Box, { sx: {
199
+ }, children: !token ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Sign in to view your profile." })) : !display ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading profile\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Profile" }), _jsxs(Box, { sx: {
105
200
  display: 'grid',
106
- gridTemplateColumns: '1fr 2fr',
107
- rowGap: 2,
108
- columnGap: 3,
109
- maxWidth: 560,
110
- }, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: "Username" }), _jsx(Text, { children: display.username || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Display name" }), _jsx(Text, { children: display.displayName || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Initials" }), _jsx(Text, { children: display.initials || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Origin" }), _jsx(Text, { children: display.origin || '-' })] }), display.roles.length > 0 && (_jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: display.roles.map(role => (_jsx(Label, { size: "small", variant: "secondary", children: role }, role))) }))] })) }));
201
+ gridTemplateColumns: [
202
+ '1fr',
203
+ null,
204
+ 'minmax(280px, 1fr) minmax(320px, 2fr)',
205
+ ],
206
+ gap: 4,
207
+ alignItems: 'start',
208
+ }, children: [_jsxs(Box, { sx: {
209
+ border: '1px solid',
210
+ borderColor: 'border.default',
211
+ borderRadius: 2,
212
+ p: 3,
213
+ display: 'flex',
214
+ flexDirection: 'column',
215
+ gap: 3,
216
+ }, children: [_jsx(Box, { sx: { textAlign: 'left' }, children: _jsx(UserBadge, { token: token, variant: "small", onTokenExpired: onTokenExpired }) }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [hasRealAvatar(display.avatarUrl) ? (_jsx(UserAvatar, { avatarUrl: display.avatarUrl, size: 72, square: false })) : (_jsx(Box, { sx: {
217
+ width: 72,
218
+ height: 72,
219
+ borderRadius: '50%',
220
+ bg: avatarFallbackBackground || 'accent.subtle',
221
+ display: 'flex',
222
+ alignItems: 'center',
223
+ justifyContent: 'center',
224
+ '--datalayer-icon-fg': avatarFallbackForeground || 'accent.fg',
225
+ }, children: _jsx(AlienIcon, { size: 34, themed: true, colormode: true }) })), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column' }, children: [_jsx(Heading, { sx: { fontSize: 3, mb: 1 }, children: display.headingDisplayName }), display.handle && (_jsx(Text, { sx: { color: 'fg.muted' }, children: display.handle }))] })] }), display.id && (_jsx(Box, { children: _jsx(Label, { size: "large", variant: "secondary", children: display.id }) }))] }), _jsxs(Box, { sx: {
226
+ border: '1px solid',
227
+ borderColor: 'border.default',
228
+ borderRadius: 2,
229
+ p: 3,
230
+ display: 'grid',
231
+ gridTemplateColumns: '1fr 2fr',
232
+ rowGap: 2,
233
+ columnGap: 3,
234
+ }, children: [_jsx(Text, { sx: { fontWeight: 'bold' }, children: "Username" }), _jsx(Text, { children: display.username || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Display name" }), _jsx(Text, { children: display.displayName || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Email" }), _jsx(Text, { children: display.email || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Initials" }), _jsx(Text, { children: display.initials || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Origin" }), _jsx(Text, { children: display.origin || '-' }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: "Roles" }), _jsx(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: display.roles.length > 0 ? (display.roles.map(role => (_jsx(Label, { size: "small", variant: "secondary", children: role }, role)))) : (_jsx(Text, { children: "-" })) })] })] })] })) }));
111
235
  }
112
236
  export function AgentNode() {
113
237
  const { token, setAuth, clearAuth } = useSimpleAuthStore();
114
238
  const tokenForCore = token ?? undefined;
239
+ const signInLoginUrl = `${resolveDatalayerUrl()}/api/iam/v1/login`;
115
240
  const queryClient = useQueryClient();
116
241
  const iamUser = useIAMStore(state => state.user);
117
242
  const { colorMode, theme: themeVariant } = useAgentNodeThemeStore();
243
+ const setColorMode = useAgentNodeThemeStore(state => state.setColorMode);
244
+ const setTheme = useAgentNodeThemeStore(state => state.setTheme);
118
245
  const cfg = themeConfigs[themeVariant];
119
246
  const logoColors = getLogoColors(themeVariant, colorMode);
120
247
  const resolvedMode = colorMode === 'auto'
@@ -125,16 +252,243 @@ export function AgentNode() {
125
252
  : colorMode === 'dark'
126
253
  ? 'dark'
127
254
  : 'light';
255
+ const avatarPalette = getColorPalette(themeVariant, resolvedMode);
128
256
  const authGradient = cfg.cardGradient[resolvedMode];
129
257
  const [step, setStep] = useState('auth');
130
- const [disableAutoBootstrap, setDisableAutoBootstrap] = useState(false);
258
+ const [selectedAgentId, setSelectedAgentId] = useState('default');
259
+ // Persisted across refreshes: once the user explicitly signs out we must
260
+ // not silently re-authenticate them from the env-supplied DATALAYER_API_KEY
261
+ // via /auth/bootstrap. Initialise from localStorage so the intent survives a
262
+ // page reload (React state alone resets to false on refresh).
263
+ const [disableAutoBootstrap, setDisableAutoBootstrap] = useState(() => {
264
+ try {
265
+ return (window.localStorage.getItem(AUTO_BOOTSTRAP_DISABLED_KEY) === 'true');
266
+ }
267
+ catch {
268
+ return false;
269
+ }
270
+ });
131
271
  const [configuration, setConfiguration] = useState(DEFAULT_CONFIGURATION);
132
272
  const [, setSelectedBillingEntity] = useState(undefined);
133
273
  const [isSaving, setIsSaving] = useState(false);
134
274
  const [error, setError] = useState(null);
275
+ // ── Ephemeral notebook RTC (shared room with the SaaS UI) ───────────────
276
+ // The node provisions a spacer notebook room on registration and stores its
277
+ // uid in the configuration. When present, both the node-local chat and the
278
+ // SaaS gallery view join that room so the agent's notebook edits transit via
279
+ // RTC instead of the tunnel. Absent (e.g. before sign-in) the ephemeral
280
+ // notebook stays in-memory, unchanged.
281
+ const ephemeralCollaborationDocumentId = token && configuration.collaboration_notebook_uid
282
+ ? configuration.collaboration_notebook_uid
283
+ : undefined;
284
+ const ephemeralNotebookCollaborationProvider = useMemo(() => {
285
+ if (!ephemeralCollaborationDocumentId || !token) {
286
+ return undefined;
287
+ }
288
+ const spacerUrl = resolveSpacerUrl();
289
+ return new DatalayerCollaborationProvider({
290
+ datalayerUrl: spacerUrl,
291
+ token,
292
+ });
293
+ }, [ephemeralCollaborationDocumentId, token]);
294
+ // ── Ephemeral document RTC (shared Lexical/Loro room with the SaaS UI) ────
295
+ // The node provisions a spacer *lexical* room (distinct from the notebook
296
+ // room) on registration and stores its uid as `collaboration_document_uid`.
297
+ // Both peers join that Loro room over the spacer lexical WebSocket. The token
298
+ // is embedded in the ws URL (createWebsocketProvider forwards it as a query
299
+ // param) so the spacer authenticates the connection.
300
+ const collaborationDocumentUser = useIAMStore(state => state.user);
301
+ const ephemeralDocumentCollaboration = useMemo(() => {
302
+ const documentRoomId = configuration.collaboration_document_uid;
303
+ if (!token || !documentRoomId) {
304
+ return undefined;
305
+ }
306
+ const spacerUrl = resolveSpacerUrl();
307
+ const wsSpacer = String(spacerUrl).replace(/^http/, 'ws');
308
+ const websocketUrl = `${wsSpacer}/api/spacer/v1/lexical/ws?token=${encodeURIComponent(token)}`;
309
+ const u = collaborationDocumentUser;
310
+ return {
311
+ websocketUrl,
312
+ roomId: documentRoomId,
313
+ identity: u
314
+ ? {
315
+ userId: u.uid,
316
+ handle: u.handle,
317
+ displayName: u.displayName,
318
+ initials: u.initials,
319
+ avatarUrl: u.avatarUrl,
320
+ }
321
+ : undefined,
322
+ };
323
+ }, [
324
+ configuration.collaboration_document_uid,
325
+ token,
326
+ collaborationDocumentUser,
327
+ ]);
135
328
  const [inferenceProvider, setInferenceProvider] = useState('datalayer');
136
329
  const [inferenceModels, setInferenceModels] = useState([]);
137
330
  const [inferenceDefaultModel, setInferenceDefaultModel] = useState(null);
331
+ const [configurationLoaded, setConfigurationLoaded] = useState(false);
332
+ const [isActiveAgentRunning, setIsActiveAgentRunning] = useState(false);
333
+ const chatRestoreAttemptRef = useRef('');
334
+ const hasActiveAgent = Boolean(String(configuration.active_agent_id || '').trim()) &&
335
+ isActiveAgentRunning;
336
+ const isSaasOnlyChat = configuration.chat_access_mode === 'saas_only' ||
337
+ configuration.deployment_target === 'aws';
338
+ // ── Local sandbox runtime override for the ephemeral surfaces ─────────────
339
+ // In the node-local webapp the agent's Jupyter sandbox is a LOCAL server, not
340
+ // a Kubernetes pod resolvable by `pod_name`, so the ephemeral notebook and
341
+ // document surfaces cannot bind their kernel through the runtimes pod lookup.
342
+ // Instead we read the live sandbox Jupyter endpoint from `/health/startup`
343
+ // and pass it as an explicit override so the surfaces connect a service
344
+ // manager straight to the sandbox kernel. This is what makes the surfaces
345
+ // actually render and the chat header's kernel indicator reflect the sandbox
346
+ // kernel (rather than the browser/Pyodide placeholder).
347
+ const [sandboxRuntimeOverride, setSandboxRuntimeOverride] = useState(undefined);
348
+ // Environment name shown in the chat header's kernel indicator details, so
349
+ // it reads e.g. "Local Agent Sandbox (python3)" instead of the indicator's
350
+ // generic "browser-runtime" placeholder once the local sandbox is bound.
351
+ const [sandboxEnvironmentName, setSandboxEnvironmentName] = useState(undefined);
352
+ useEffect(() => {
353
+ const selected = String(selectedAgentId || '').trim();
354
+ if (step !== 'chat' || !selected || selected === 'default') {
355
+ setSandboxRuntimeOverride(undefined);
356
+ setSandboxEnvironmentName(undefined);
357
+ return;
358
+ }
359
+ // Fast path: in node-local dev the agent's Jupyter sandbox is a LOCAL
360
+ // server whose URL/token are known up front (VITE_JUPYTER_SANDBOX_URL).
361
+ // Bind the ephemeral surfaces straight to it — no /health/startup polling,
362
+ // no proxy, no tunnel — mirroring NotebookAgentExample.
363
+ if (LOCAL_JUPYTER_SANDBOX?.baseUrl) {
364
+ setSandboxEnvironmentName('Local Agent Sandbox');
365
+ setSandboxRuntimeOverride(prev => {
366
+ if (prev?.baseUrl === LOCAL_JUPYTER_SANDBOX.baseUrl &&
367
+ (prev?.token || '') === (LOCAL_JUPYTER_SANDBOX.token || '')) {
368
+ return prev;
369
+ }
370
+ return {
371
+ baseUrl: LOCAL_JUPYTER_SANDBOX.baseUrl,
372
+ token: LOCAL_JUPYTER_SANDBOX.token || undefined,
373
+ podName: 'agent-node-sandbox',
374
+ };
375
+ });
376
+ return;
377
+ }
378
+ let cancelled = false;
379
+ let intervalId = null;
380
+ const stopPolling = () => {
381
+ if (intervalId !== null) {
382
+ window.clearInterval(intervalId);
383
+ intervalId = null;
384
+ }
385
+ };
386
+ const poll = async () => {
387
+ try {
388
+ const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/health/startup`);
389
+ if (!resp.ok) {
390
+ return;
391
+ }
392
+ const payload = await resp.json();
393
+ const sandbox = payload?.sandbox;
394
+ const jupyterUrl = String(sandbox?.jupyter_url || '').trim();
395
+ if (sandbox?.variant !== 'jupyter' || !jupyterUrl) {
396
+ return;
397
+ }
398
+ const jupyterToken = String(sandbox?.jupyter_token || '').trim();
399
+ if (cancelled) {
400
+ return;
401
+ }
402
+ const kernelName = String(sandbox?.kernel_name || '').trim();
403
+ setSandboxEnvironmentName(kernelName
404
+ ? `Local Agent Sandbox (${kernelName})`
405
+ : 'Local Agent Sandbox');
406
+ setSandboxRuntimeOverride(prev => {
407
+ if (prev?.baseUrl === jupyterUrl &&
408
+ (prev?.token || '') === jupyterToken) {
409
+ return prev;
410
+ }
411
+ return {
412
+ baseUrl: jupyterUrl,
413
+ token: jupyterToken || undefined,
414
+ podName: 'agent-node-sandbox',
415
+ };
416
+ });
417
+ // Endpoint resolved — stop the fast poll.
418
+ stopPolling();
419
+ }
420
+ catch {
421
+ // Sandbox not ready yet; keep polling.
422
+ }
423
+ };
424
+ void poll();
425
+ intervalId = window.setInterval(() => {
426
+ void poll();
427
+ }, 2000);
428
+ return () => {
429
+ cancelled = true;
430
+ stopPolling();
431
+ };
432
+ }, [step, selectedAgentId]);
433
+ // ── Agent spec info for the chat header (title/description/suggestions) ──
434
+ // Mirrors the `ui` runtime chat view: the chat header shows the agent spec's
435
+ // display name and welcome message, and the empty state offers the spec's
436
+ // suggested prompts. The node-local agent is launched from a library spec
437
+ // whose id is used as the agent id (see AgentNodeGallery `launch`), so the
438
+ // spec is looked up by that same id via `GET /api/v1/agents/library/{id}`.
439
+ const [agentSpecInfo, setAgentSpecInfo] = useState(null);
440
+ useEffect(() => {
441
+ const agentId = String(selectedAgentId || '').trim();
442
+ if (step !== 'chat' || !agentId || agentId === 'default') {
443
+ setAgentSpecInfo(null);
444
+ return;
445
+ }
446
+ let cancelled = false;
447
+ (async () => {
448
+ try {
449
+ const resp = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents/library/${encodeURIComponent(agentId)}`, {
450
+ headers: token ? { Authorization: `Bearer ${token}` } : undefined,
451
+ });
452
+ if (!resp.ok) {
453
+ if (!cancelled) {
454
+ setAgentSpecInfo(null);
455
+ }
456
+ return;
457
+ }
458
+ const spec = await resp.json().catch(() => null);
459
+ if (cancelled || !spec) {
460
+ return;
461
+ }
462
+ setAgentSpecInfo({
463
+ name: typeof spec.name === 'string' ? spec.name : undefined,
464
+ description: typeof spec.description === 'string' ? spec.description : undefined,
465
+ welcomeMessage: typeof spec.welcomeMessage === 'string'
466
+ ? spec.welcomeMessage
467
+ : typeof spec.welcome_message === 'string'
468
+ ? spec.welcome_message
469
+ : undefined,
470
+ suggestions: Array.isArray(spec.suggestions)
471
+ ? spec.suggestions.filter((s) => typeof s === 'string')
472
+ : undefined,
473
+ });
474
+ }
475
+ catch {
476
+ if (!cancelled) {
477
+ setAgentSpecInfo(null);
478
+ }
479
+ }
480
+ })();
481
+ return () => {
482
+ cancelled = true;
483
+ };
484
+ }, [step, selectedAgentId, token]);
485
+ const chatTitle = agentSpecInfo?.name || 'Agent Node Chat';
486
+ const chatDescription = agentSpecInfo?.welcomeMessage ||
487
+ agentSpecInfo?.description ||
488
+ 'Node-local chat';
489
+ const chatSuggestions = agentSpecInfo?.suggestions && agentSpecInfo.suggestions.length > 0
490
+ ? agentSpecInfo.suggestions.map(s => ({ title: s, message: s }))
491
+ : undefined;
138
492
  const [banner, setBanner] = useState(null);
139
493
  const bannerTimerRef = useRef(null);
140
494
  const showBanner = useCallback((kind, message) => {
@@ -158,9 +512,7 @@ export function AgentNode() {
158
512
  };
159
513
  }, []);
160
514
  const pushCredentials = useCallback((authToken, payloadToken = authToken) => {
161
- const datalayerUrl = import.meta.env?.VITE_DATALAYER_URL ||
162
- 'https://prod1.datalayer.run';
163
- const runtimesUrl = import.meta.env?.VITE_DATALAYER_RUNTIMES_URL || datalayerUrl;
515
+ const runtimesUrl = resolveRuntimesUrl();
164
516
  const headers = {
165
517
  'Content-Type': 'application/json',
166
518
  };
@@ -213,6 +565,7 @@ export function AgentNode() {
213
565
  try {
214
566
  const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`);
215
567
  if (!response.ok) {
568
+ setConfigurationLoaded(true);
216
569
  return;
217
570
  }
218
571
  const payload = await response.json();
@@ -221,10 +574,16 @@ export function AgentNode() {
221
574
  ...(payload?.configuration || {}),
222
575
  };
223
576
  setConfiguration(loadedConfiguration);
577
+ if (loadedConfiguration.active_agent_id) {
578
+ setSelectedAgentId(loadedConfiguration.active_agent_id);
579
+ }
224
580
  }
225
581
  catch {
226
582
  // Ignore initial-load failures in local development.
227
583
  }
584
+ finally {
585
+ setConfigurationLoaded(true);
586
+ }
228
587
  };
229
588
  load();
230
589
  }, []);
@@ -319,7 +678,7 @@ export function AgentNode() {
319
678
  }
320
679
  if (data?.has_key && data?.token) {
321
680
  setAuth(String(data.token), String(data.handle || 'api-key-user'));
322
- setStep('config');
681
+ setStep('gallery');
323
682
  }
324
683
  }
325
684
  catch {
@@ -343,10 +702,10 @@ export function AgentNode() {
343
702
  }, [token, pushCredentials]);
344
703
  useEffect(() => {
345
704
  import('@datalayer/core/lib/state').then(({ iamStore, coreStore }) => {
346
- const datalayerUrl = import.meta.env?.VITE_DATALAYER_URL ||
347
- 'https://prod1.datalayer.run';
348
- const runtimesUrl = import.meta.env?.VITE_DATALAYER_RUNTIMES_URL || datalayerUrl;
349
- const aiInferenceUrl = import.meta.env?.VITE_DATALAYER_AI_INFERENCE_URL ||
705
+ const datalayerUrl = resolveDatalayerUrl();
706
+ const runtimesUrl = resolveRuntimesUrl();
707
+ const aiInferenceUrl = getConfigUrlFromDocument('aiInferenceUrl') ||
708
+ import.meta.env?.VITE_DATALAYER_AI_INFERENCE_URL ||
350
709
  datalayerUrl;
351
710
  // Seed all per-service URLs to match the main UI login behavior.
352
711
  const coreApi = coreStore.getState();
@@ -387,11 +746,75 @@ export function AgentNode() {
387
746
  setStep('auth');
388
747
  }
389
748
  }, [token, step]);
749
+ useEffect(() => {
750
+ if (!token || !String(configuration.active_agent_id || '').trim()) {
751
+ setIsActiveAgentRunning(false);
752
+ }
753
+ }, [token, configuration.active_agent_id]);
754
+ useEffect(() => {
755
+ if (!token || !configurationLoaded) {
756
+ return;
757
+ }
758
+ const activeAgentId = (configuration.active_agent_id || '').trim();
759
+ if (!activeAgentId) {
760
+ return;
761
+ }
762
+ const restoreKey = `${activeAgentId}`;
763
+ if (chatRestoreAttemptRef.current === restoreKey) {
764
+ return;
765
+ }
766
+ chatRestoreAttemptRef.current = restoreKey;
767
+ let cancelled = false;
768
+ (async () => {
769
+ try {
770
+ const response = await fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agents`, {
771
+ headers: {
772
+ Authorization: `Bearer ${token}`,
773
+ },
774
+ });
775
+ if (!response.ok) {
776
+ return;
777
+ }
778
+ const payload = await response.json().catch(() => null);
779
+ if (cancelled) {
780
+ return;
781
+ }
782
+ const agents = Array.isArray(payload)
783
+ ? payload
784
+ : payload?.agents || payload?.items || [];
785
+ const running = agents.some(agent => {
786
+ const id = String(agent?.agent_id || agent?.id || '').trim();
787
+ return id === activeAgentId;
788
+ });
789
+ setIsActiveAgentRunning(running);
790
+ if (running) {
791
+ setSelectedAgentId(activeAgentId);
792
+ setConfiguration(prev => prev.mode === 'private' ? prev : { ...prev, mode: 'private' });
793
+ setStep('chat');
794
+ }
795
+ else if (selectedAgentId === activeAgentId) {
796
+ setSelectedAgentId('default');
797
+ }
798
+ }
799
+ catch {
800
+ setIsActiveAgentRunning(false);
801
+ // Best-effort restore only.
802
+ }
803
+ })();
804
+ return () => {
805
+ cancelled = true;
806
+ };
807
+ }, [
808
+ token,
809
+ configurationLoaded,
810
+ configuration.active_agent_id,
811
+ selectedAgentId,
812
+ ]);
390
813
  useEffect(() => {
391
814
  // If a persisted session is already authenticated on first load,
392
- // skip the auth screen and land on the main configuration view.
815
+ // skip the auth screen and land on the agents gallery.
393
816
  if (token && step === 'auth') {
394
- setStep('config');
817
+ setStep('gallery');
395
818
  }
396
819
  }, [token, step]);
397
820
  useEffect(() => {
@@ -399,16 +822,26 @@ export function AgentNode() {
399
822
  setStep('config');
400
823
  }
401
824
  }, [step, configuration.mode]);
825
+ useEffect(() => {
826
+ if (step === 'chat' && !hasActiveAgent) {
827
+ setStep('gallery');
828
+ }
829
+ }, [step, hasActiveAgent]);
830
+ useEffect(() => {
831
+ if (step === 'chat' && isSaasOnlyChat) {
832
+ setStep('gallery');
833
+ }
834
+ }, [step, isSaasOnlyChat]);
402
835
  const handleSignIn = (newToken, handle) => {
836
+ setAutoBootstrapDisabled(false);
403
837
  setDisableAutoBootstrap(false);
404
838
  setAuth(newToken, handle);
405
- setStep('config');
839
+ setStep('gallery');
406
840
  };
407
841
  // API keys are exchanged for a session token before login so billing and
408
842
  // plans endpoints (/api/iam/v1/plans/*) resolve the correct paid plan.
409
843
  const handleApiKeySignIn = async (apiKey) => {
410
- const datalayerUrl = import.meta.env?.VITE_DATALAYER_URL ||
411
- 'https://prod1.datalayer.run';
844
+ const datalayerUrl = resolveDatalayerUrl();
412
845
  try {
413
846
  const resp = await fetch(`${datalayerUrl}/api/iam/v1/login`, {
414
847
  method: 'POST',
@@ -428,6 +861,9 @@ export function AgentNode() {
428
861
  }
429
862
  };
430
863
  const handleSignOut = () => {
864
+ // Persist the signed-out intent so a refresh does not re-bootstrap the
865
+ // session from the env-supplied DATALAYER_API_KEY.
866
+ setAutoBootstrapDisabled(true);
431
867
  setDisableAutoBootstrap(true);
432
868
  // Clear backend fallback credentials while still authenticated.
433
869
  void pushCredentials(token, null).catch(() => {
@@ -467,9 +903,12 @@ export function AgentNode() {
467
903
  ...(payload?.configuration || {}),
468
904
  };
469
905
  setConfiguration(saved);
906
+ if (saved.active_agent_id) {
907
+ setSelectedAgentId(saved.active_agent_id);
908
+ }
470
909
  showBanner('success', 'Agent Node configuration saved.');
471
910
  if (saved.mode === 'private') {
472
- setStep('chat');
911
+ setStep('gallery');
473
912
  }
474
913
  }
475
914
  catch (reason) {
@@ -486,8 +925,11 @@ export function AgentNode() {
486
925
  return true;
487
926
  if (!token)
488
927
  return false;
489
- if (nextStep === 'chat')
490
- return configuration.mode === 'private';
928
+ if (nextStep === 'gallery')
929
+ return true;
930
+ if (nextStep === 'chat') {
931
+ return (configuration.mode === 'private' && hasActiveAgent && !isSaasOnlyChat);
932
+ }
491
933
  return true;
492
934
  };
493
935
  const StepEntry = ({ entryStep, label, leadingVisual, }) => {
@@ -508,21 +950,13 @@ export function AgentNode() {
508
950
  minHeight: '100vh',
509
951
  bg: 'canvas.default',
510
952
  color: 'fg.default',
511
- }, children: _jsxs(PageLayout, { containerWidth: "full", padding: "normal", sx: { bg: 'canvas.default', color: 'fg.default' }, style: { minHeight: '100vh', overflow: 'visible' }, children: [_jsx(PageLayout.Header, { children: _jsx(PageHeader, { children: _jsx(PageHeader.Actions, { children: _jsxs(Box, { sx: {
512
- display: 'flex',
513
- alignItems: 'center',
514
- gap: 3,
515
- fontSize: 2,
516
- lineHeight: '22px',
517
- }, children: [_jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: token && (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "chat", label: "Chat", leadingVisual: CommentIcon }), _jsx(StepEntry, { entryStep: "config", label: "Configuration", leadingVisual: GearIcon })] })) }), _jsx(AppearanceControlsWithStore, { useStore: useAgentNodeThemeStore }), !token ? (_jsx(Button, { size: "small", variant: "invisible", onClick: () => setStep('auth'), leadingVisual: SignInIcon, sx: { color: 'fg.muted' }, children: "Sign in" })) : (_jsxs(Box, { sx: {
518
- display: 'inline-flex',
519
- alignItems: 'center',
520
- gap: 1,
521
- }, children: [_jsx(Box, { sx: {
522
- textAlign: 'left',
523
- display: 'inline-flex',
524
- alignItems: 'center',
525
- }, children: _jsx(UserBadge, { token: token, variant: "small", onTokenExpired: handleSignOut }) }), _jsx(StepEntry, { entryStep: "profile", label: "Profile", leadingVisual: PersonIcon }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleSignOut, leadingVisual: SignOutIcon, sx: { color: 'fg.default' }, children: "Sign out" })] })), _jsx(Box, { as: "a", href: "https://datalayer.ai", target: "_blank", rel: "noopener noreferrer", "aria-label": "Open Datalayer website", sx: { display: 'inline-flex', alignItems: 'center' }, children: _jsx(DatalayerLogoText, { size: 24, variant: themeVariant, colorMode: colorMode, primaryColor: logoColors.primary, secondaryColor: logoColors.secondary, textColor: logoColors.textColor, primaryGradient: logoColors.primaryGradient, secondaryGradient: logoColors.secondaryGradient, gradient: true }) })] }) }) }) }), _jsxs(PageLayout.Content, { children: [_jsx(Box, { "aria-live": "polite", sx: {
953
+ }, children: _jsxs(PageLayout, { containerWidth: "full", padding: "normal", sx: { bg: 'canvas.default', color: 'fg.default' }, style: { minHeight: '100vh', overflow: 'visible' }, children: [_jsx(PageLayout.Header, { children: _jsxs(PageHeader, { children: [_jsx(PageHeader.TitleArea, { children: _jsx(PageHeader.Title, { children: _jsx(Box, { as: "a", href: "https://datalayer.ai", target: "_blank", rel: "noopener noreferrer", "aria-label": "Open Datalayer website", sx: { display: 'inline-flex', alignItems: 'center' }, children: _jsx(DatalayerLogoText, { size: 24, inverse: true, variant: themeVariant, colorMode: colorMode, primaryColor: logoColors.primary, secondaryColor: logoColors.secondary, textColor: logoColors.textColor, primaryGradient: logoColors.primaryGradient, secondaryGradient: logoColors.secondaryGradient, gradient: true }) }) }) }), _jsx(PageHeader.Actions, { children: _jsxs(Box, { sx: {
954
+ display: 'flex',
955
+ alignItems: 'center',
956
+ gap: 2,
957
+ fontSize: 2,
958
+ lineHeight: '22px',
959
+ }, children: [token && (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "chat", label: "Chat", leadingVisual: CommentIcon }), _jsx(StepEntry, { entryStep: "gallery", label: "Agents", leadingVisual: AiAgentIcon }), _jsx(StepEntry, { entryStep: "config", label: "Configuration", leadingVisual: GearIcon })] })), !token ? null : (_jsxs(_Fragment, { children: [_jsx(StepEntry, { entryStep: "profile", label: "Profile", leadingVisual: PersonIcon }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleSignOut, leadingVisual: SignOutIcon, sx: { color: 'fg.default' }, children: "Sign out" })] })), _jsx(AppearanceMenu, { colorMode: colorMode, themeVariant: themeVariant, onColorModeChange: setColorMode, onThemeChange: nextTheme => setTheme(nextTheme, false), shape: "circle" })] }) })] }) }), _jsxs(PageLayout.Content, { children: [_jsx(Box, { "aria-live": "polite", sx: {
526
960
  position: 'fixed',
527
961
  top: 0,
528
962
  left: 0,
@@ -560,7 +994,7 @@ export function AgentNode() {
560
994
  ? 'opacity 600ms ease, transform 700ms cubic-bezier(0.16, 1, 0.3, 1)'
561
995
  : 'opacity 250ms ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1)',
562
996
  textAlign: 'center',
563
- }, children: _jsx(Text, { sx: { fontSize: 3, fontWeight: 'bold' }, children: banner?.message ?? '' }) }) }), _jsxs(Box, { sx: { mb: 3 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 1 }, children: [_jsx(Box, { sx: { color: 'fg.muted', display: 'inline-flex' }, children: _jsx(KeyAsteriskIcon, { size: 18 }) }), _jsx(Heading, { sx: { fontSize: 3, m: 0 }, children: "Agent Node" })] }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Authenticate, configure Private/Shared/Sleep mode, then chat from this node." })] }), step === 'auth' && (_jsx(Box, { sx: {
997
+ }, children: _jsx(Text, { sx: { fontSize: 3, fontWeight: 'bold' }, children: banner?.message ?? '' }) }) }), !token && (_jsxs(Box, { sx: { mb: 3 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 1 }, children: [_jsx(Box, { sx: { color: 'fg.muted', display: 'inline-flex' }, children: _jsx(KeyAsteriskIcon, { size: 18 }) }), _jsx(Heading, { sx: { fontSize: 3, m: 0 }, children: "Agent Node" })] }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Authenticate, configure Private/Shared/Sleep mode, then chat from this node." })] })), step === 'auth' && (_jsx(Box, { sx: {
564
998
  border: '1px solid',
565
999
  borderColor: 'border.default',
566
1000
  borderTop: '3px solid',
@@ -582,13 +1016,17 @@ export function AgentNode() {
582
1016
  '& > div > div': {
583
1017
  width: '100%',
584
1018
  bg: 'canvas.default',
1019
+ border: '1px solid',
585
1020
  borderColor: `${cfg.brandColor}66`,
586
1021
  boxShadow: `0 0 0 1px ${cfg.brandColor}2B`,
1022
+ borderRadius: 2,
1023
+ px: [3, 4],
1024
+ py: [3, 4],
587
1025
  },
588
1026
  '& h2, & h3': {
589
1027
  color: cfg.brandColor,
590
1028
  },
591
- }, children: _jsx(SignInSimple, { onSignIn: handleSignIn, onApiKeySignIn: handleApiKeySignIn, title: "Agent Node", description: "Sign in to configure node settings and run authenticated chat sessions.", leadingIcon: _jsx(Box, { sx: { color: cfg.brandColor, display: 'inline-flex' }, children: _jsx(HomeIcon, { size: 24 }) }) }) }) })), step === 'config' && (_jsxs(Box, { sx: {
1029
+ }, children: _jsx(SignInSimple, { loginUrl: signInLoginUrl, onSignIn: handleSignIn, onApiKeySignIn: handleApiKeySignIn, title: "Agent Node", description: "Sign in to configure node settings and run authenticated chat sessions.", leadingIcon: _jsx(Box, { sx: { color: cfg.brandColor, display: 'inline-flex' }, children: _jsx(HomeIcon, { size: 24 }) }) }) }) })), step === 'config' && (_jsxs(Box, { sx: {
592
1030
  border: '1px solid',
593
1031
  borderColor: 'border.default',
594
1032
  borderRadius: 2,
@@ -605,7 +1043,12 @@ export function AgentNode() {
605
1043
  display: 'flex',
606
1044
  flexDirection: 'column',
607
1045
  gap: 3,
608
- }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Mode" }), _jsx(Box, { sx: {
1046
+ }, children: [_jsxs(Box, { sx: {
1047
+ border: '1px solid',
1048
+ borderColor: 'border.default',
1049
+ borderRadius: 2,
1050
+ p: 3,
1051
+ }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Mode" }), _jsx(Box, { sx: {
609
1052
  display: 'grid',
610
1053
  gridTemplateColumns: 'repeat(3, 1fr)',
611
1054
  gap: 3,
@@ -645,7 +1088,12 @@ export function AgentNode() {
645
1088
  ? cfg.brandColor
646
1089
  : 'fg.default',
647
1090
  }, children: [_jsx(Icon, { size: 20 }), _jsx(Text, { sx: { fontWeight: 'bold' }, children: card.name })] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: card.description })] }, card.mode));
648
- }) })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Inference" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1, mb: 2 }, children: "Used inference provider for newly launched agent sessions." }), _jsxs(Box, { sx: {
1091
+ }) })] }), _jsxs(Box, { sx: {
1092
+ border: '1px solid',
1093
+ borderColor: 'border.default',
1094
+ borderRadius: 2,
1095
+ p: 3,
1096
+ }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Inference" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1, mb: 2 }, children: "Used inference provider for newly launched agent sessions." }), _jsxs(Box, { sx: {
649
1097
  display: 'flex',
650
1098
  gap: 2,
651
1099
  flexWrap: 'wrap',
@@ -663,15 +1111,67 @@ export function AgentNode() {
663
1111
  display: 'flex',
664
1112
  flexDirection: 'column',
665
1113
  gap: 1,
666
- }, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Bedrock Anthropic model" }), inferenceModels.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No model list available." })) : (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { children: inferenceDefaultModel || inferenceModels[0] }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { selectionVariant: "single", children: inferenceModels.map(model => (_jsx(ActionList.Item, { selected: inferenceDefaultModel === model, inactiveText: "Selection is locked", children: model }, model))) }) })] }))] }))] }), iamUser ? (_jsx(BillingEntitySelect, { value: configuration.billing_entity_uid || '', onChange: handleBillingEntityChange, onSelectedAccountChange: handleSelectedAccountChange, onAccountsResolved: handleAccountsResolved })) : (_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Run this agent under" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Loading billing entitys..." })] }))] }), _jsxs(Box, { sx: {
1114
+ }, children: [_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Bedrock Anthropic model" }), inferenceModels.length === 0 ? (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "No model list available." })) : (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { children: inferenceDefaultModel || inferenceModels[0] }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsx(ActionList, { selectionVariant: "single", children: inferenceModels.map(model => (_jsx(ActionList.Item, { selected: inferenceDefaultModel === model, inactiveText: "Selection is locked", children: model }, model))) }) })] }))] }))] }), iamUser ? (_jsxs(Box, { sx: {
1115
+ border: '1px solid',
1116
+ borderColor: 'border.default',
1117
+ borderRadius: 2,
1118
+ p: 3,
1119
+ }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Billing Entity" }), _jsx(BillingEntitySelect, { value: configuration.billing_entity_uid || '', onChange: handleBillingEntityChange, onSelectedAccountChange: handleSelectedAccountChange, onAccountsResolved: handleAccountsResolved })] })) : (_jsxs(Box, { sx: {
1120
+ border: '1px solid',
1121
+ borderColor: 'border.default',
1122
+ borderRadius: 2,
1123
+ p: 3,
1124
+ }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Billing Entity" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Loading billing entitys..." })] }))] }), _jsx(Box, { sx: {
667
1125
  display: 'flex',
668
1126
  flexDirection: 'column',
669
1127
  gap: 2,
670
- }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0 }, children: "Share" }), _jsx(ShareAccessComponent, { isOpen: true, displayMode: "inline", requestUrl: `${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/sharing`, resourceLabel: "Agent Node", resourceName: "this Agent Node", onClose: handleSharingInlineClose })] })] }), error && _jsx(Text, { sx: { color: 'danger.fg' }, children: error }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx(Button, { variant: "primary", onClick: saveConfiguration, disabled: isSaving, children: isSaving ? 'Saving...' : 'Save' }) })] })), step === 'profile' && _jsx(AgentNodeProfileView, { token: token }), step === 'chat' && (_jsx(Box, { sx: {
1128
+ }, children: _jsxs(Box, { sx: {
1129
+ border: '1px solid',
1130
+ borderColor: 'border.default',
1131
+ borderRadius: 2,
1132
+ p: 3,
1133
+ }, children: [_jsx(Heading, { sx: { fontSize: 2, m: 0, mb: 2 }, children: "Share" }), _jsx(ShareAccessComponent, { isOpen: true, displayMode: "inline", requestUrl: `${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/sharing`, resourceLabel: "Agent Node", resourceName: "this Agent Node", onClose: handleSharingInlineClose })] }) })] }), error && _jsx(Text, { sx: { color: 'danger.fg' }, children: error }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx(Button, { variant: "primary", onClick: saveConfiguration, disabled: isSaving, children: isSaving ? 'Saving...' : 'Save' }) })] })), step === 'profile' && (_jsx(AgentNodeProfileView, { token: token, onTokenExpired: handleSignOut, avatarFallbackBackground: avatarPalette.bgAlt, avatarFallbackForeground: avatarPalette.primary })), step === 'gallery' && (_jsx(AgentNodeGallery, { baseUrl: AGENT_RUNTIMES_BASE_URL, token: token, activeAgentId: configuration.active_agent_id, onLaunchError: message => {
1134
+ showBanner('error', message);
1135
+ }, onLaunched: agentId => {
1136
+ setSelectedAgentId(agentId);
1137
+ setIsActiveAgentRunning(true);
1138
+ const nextConfiguration = {
1139
+ ...configuration,
1140
+ active_agent_id: agentId,
1141
+ mode: 'private',
1142
+ };
1143
+ setConfiguration(nextConfiguration);
1144
+ // Persist mode=private so Chat stays enabled and the node
1145
+ // advertises the active agent state to the sync loop.
1146
+ void fetch(`${AGENT_RUNTIMES_BASE_URL}/api/v1/agent-node/configuration`, {
1147
+ method: 'POST',
1148
+ headers: {
1149
+ 'Content-Type': 'application/json',
1150
+ },
1151
+ body: JSON.stringify(nextConfiguration),
1152
+ }).catch(() => {
1153
+ // Best-effort persistence; local state already switched.
1154
+ });
1155
+ if (isSaasOnlyChat) {
1156
+ showBanner('info', 'This node is configured for SaaS chat. Open Agent Nodes in Datalayer to chat with it.');
1157
+ setStep('gallery');
1158
+ }
1159
+ else {
1160
+ setStep('chat');
1161
+ }
1162
+ }, onTerminated: () => {
1163
+ setSelectedAgentId('default');
1164
+ setIsActiveAgentRunning(false);
1165
+ setConfiguration(prev => ({
1166
+ ...prev,
1167
+ active_agent_id: undefined,
1168
+ }));
1169
+ setStep('gallery');
1170
+ } })), step === 'chat' && (_jsx(Box, { sx: {
671
1171
  border: '1px solid',
672
1172
  borderColor: 'border.default',
673
1173
  borderRadius: 2,
674
1174
  overflow: 'hidden',
675
- }, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: AGENT_RUNTIMES_BASE_URL, agentId: "default", title: "Agent Node Chat", placeholder: "Send a message...", description: "Node-local chat", showHeader: true, height: '70vh', showModelSelector: true, showToolsMenu: true, showSkillsMenu: true, showTokenUsage: true, showInformation: true, autoFocus: true, runtimeId: "default", historyEndpoint: `${AGENT_RUNTIMES_BASE_URL}/api/v1/history` }) }))] })] }) }) }));
1175
+ }, children: isSaasOnlyChat ? (_jsxs(Box, { sx: { p: 4 }, children: [_jsx(Heading, { sx: { fontSize: 2, mb: 2 }, children: "Chat From SaaS" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "This Agent Node deployment is configured for SaaS-only chat. Use the Datalayer Agent Nodes view to open chat sessions over the runtimes tunnel." })] })) : (_jsx(Chat, { protocol: "ag-ui", baseUrl: AGENT_RUNTIMES_BASE_URL, agentId: selectedAgentId, title: chatTitle, placeholder: "Send a message...", description: chatDescription, suggestions: chatSuggestions, submitOnSuggestionClick: true, showHeader: true, height: '70vh', showModelSelector: true, showToolsMenu: true, showSkillsMenu: true, showTokenUsage: true, showInformation: true, autoFocus: true, enableEphemeralNotebook: true, enableEphemeralDocument: true, initialEphemeralSurfaceMode: "notebook", runtimeId: selectedAgentId, ephemeralRuntimeOverride: sandboxRuntimeOverride, kernelEnvironmentName: sandboxEnvironmentName, themeVariant: themeVariant, colorMode: colorMode, ephemeralNotebookCollaborationProvider: ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId: ephemeralCollaborationDocumentId, ephemeralDocumentCollaboration: ephemeralDocumentCollaboration, historyEndpoint: `${AGENT_RUNTIMES_BASE_URL}/api/v1/history` })) }))] })] }) }) }));
676
1176
  }
677
1177
  export default AgentNode;