@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
@@ -0,0 +1,627 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useCallback, useEffect, useMemo, useState } from 'react';
7
+ import { Box, Button, Heading, Label, Spinner, Text, TextInput, } from '@primer/react';
8
+ import { AgentIcon, BeakerIcon, BookIcon, BugIcon, CheckCircleFillIcon, CloudIcon, CodeIcon, CopilotIcon, CpuIcon, DatabaseIcon, DependabotIcon, EyeIcon, FileIcon, GearIcon, GitBranchIcon, GlobeIcon, GraphIcon, HeartIcon, HomeIcon, IssueOpenedIcon, LightBulbIcon, LinkIcon, MailIcon, MegaphoneIcon, NoteIcon, PackageIcon, PencilIcon, PeopleIcon, PlayIcon, PlugIcon, PulseIcon, RocketIcon, SearchIcon, ShareIcon, ShieldCheckIcon, ShieldIcon, SparklesFillIcon, SquirrelIcon, StarIcon, SyncIcon, TableIcon, TagIcon, TelescopeIcon, TerminalIcon, ToolsIcon, ZapIcon, } from '@primer/octicons-react';
9
+ const GRID_TEMPLATE = ['1fr', '1fr 1fr', 'repeat(5, minmax(0, 1fr))'];
10
+ const ICON_REGISTRY = {
11
+ // Primary agentspec icon values.
12
+ agent: AgentIcon,
13
+ database: DatabaseIcon,
14
+ globe: GlobeIcon,
15
+ 'git-branch': GitBranchIcon,
16
+ 'trending-up': GraphIcon,
17
+ 'share-2': ShareIcon,
18
+ // Additional mappings used across the agentspec catalog.
19
+ rocket: RocketIcon,
20
+ sparkles: SparklesFillIcon,
21
+ search: SearchIcon,
22
+ code: CodeIcon,
23
+ cpu: CpuIcon,
24
+ beaker: BeakerIcon,
25
+ book: BookIcon,
26
+ bug: BugIcon,
27
+ cloud: CloudIcon,
28
+ copilot: CopilotIcon,
29
+ dependabot: DependabotIcon,
30
+ eye: EyeIcon,
31
+ file: FileIcon,
32
+ gear: GearIcon,
33
+ graph: GraphIcon,
34
+ heart: HeartIcon,
35
+ home: HomeIcon,
36
+ lightbulb: LightBulbIcon,
37
+ link: LinkIcon,
38
+ mail: MailIcon,
39
+ megaphone: MegaphoneIcon,
40
+ note: NoteIcon,
41
+ package: PackageIcon,
42
+ pencil: PencilIcon,
43
+ people: PeopleIcon,
44
+ play: PlayIcon,
45
+ plug: PlugIcon,
46
+ pulse: PulseIcon,
47
+ 'issue-opened': IssueOpenedIcon,
48
+ shield: ShieldIcon,
49
+ 'shield-check': ShieldCheckIcon,
50
+ squirrel: SquirrelIcon,
51
+ star: StarIcon,
52
+ sync: SyncIcon,
53
+ table: TableIcon,
54
+ tag: TagIcon,
55
+ telescope: TelescopeIcon,
56
+ terminal: TerminalIcon,
57
+ tools: ToolsIcon,
58
+ zap: ZapIcon,
59
+ };
60
+ const DEFAULT_ICON = SparklesFillIcon;
61
+ function hashString(value) {
62
+ let hash = 0;
63
+ for (let i = 0; i < value.length; i++) {
64
+ hash = ((hash << 5) - hash + value.charCodeAt(i)) | 0;
65
+ }
66
+ return Math.abs(hash);
67
+ }
68
+ function resolveAgentspecIcon(iconId, seed) {
69
+ if (iconId) {
70
+ return ICON_REGISTRY[iconId] || DEFAULT_ICON;
71
+ }
72
+ const iconValues = Object.values(ICON_REGISTRY);
73
+ if (iconValues.length === 0) {
74
+ return DEFAULT_ICON;
75
+ }
76
+ return iconValues[hashString(seed) % iconValues.length];
77
+ }
78
+ /**
79
+ * URL (optionally with `?token=`) of the LOCAL Jupyter server that the node's
80
+ * agents should use as their `jupyter` code sandbox. This mirrors
81
+ * `NotebookAgentExample`: the agent and the chat's ephemeral notebook share the
82
+ * same local Jupyter server directly — no proxy or tunnel is involved. When it
83
+ * is undefined (e.g. SaaS deployments) the launch falls back to the backend's
84
+ * default sandbox configuration.
85
+ */
86
+ const LOCAL_JUPYTER_SANDBOX_URL = import.meta.env.VITE_JUPYTER_SANDBOX_URL || undefined;
87
+ /**
88
+ * Agent gallery picker shown on the Agent Node.
89
+ if (!createResponse.ok && createResponse.status === 409) {
90
+ // If the named agent already exists, reload running agents and try
91
+ // to reuse the matching instance instead of failing the launch.
92
+ const reloadResponse = await fetch(`${baseUrl}/api/v1/agents`, {
93
+ headers: authHeaders,
94
+ });
95
+ if (reloadResponse.ok) {
96
+ const reloadPayload = await reloadResponse
97
+ .json()
98
+ .catch(() => null);
99
+ const nextRunning: RunningAgent[] = Array.isArray(reloadPayload)
100
+ ? reloadPayload
101
+ : reloadPayload?.agents || reloadPayload?.items || [];
102
+ setRunning(nextRunning);
103
+ const existing = nextRunning.find(candidate => {
104
+ const candidateId = candidate.agent_id || candidate.id || '';
105
+ return (
106
+ candidate.agent_spec_id === spec.id ||
107
+ candidate.name === requestedName ||
108
+ candidateId === requestedName
109
+ );
110
+ });
111
+ agentId = existing?.agent_id || existing?.id || null;
112
+
113
+ // If a same-name agent exists but is not tied to this spec,
114
+ // recreate it so configure/spec lookups and sandbox settings
115
+ // match the selected gallery card.
116
+ if (existing && existing.agent_spec_id !== spec.id) {
117
+ const staleId = String(existing.agent_id || existing.id || '').trim();
118
+ if (staleId) {
119
+ await fetch(
120
+ `${baseUrl}/api/v1/agents/${encodeURIComponent(staleId)}`,
121
+ {
122
+ method: 'DELETE',
123
+ headers: authHeaders,
124
+ },
125
+ );
126
+ }
127
+ agentId = null;
128
+ }
129
+ }
130
+ * Sources cards from the agentspec library and highlights the agent that is
131
+ * already running on the node. Launching a card creates the agent locally and
132
+ * marks it active so tunneled prompts from the main UI reach it.
133
+ */
134
+ export function AgentNodeGallery({ baseUrl, token, activeAgentId, onLaunched, onTerminated, onLaunchError, }) {
135
+ const [specs, setSpecs] = useState([]);
136
+ const [running, setRunning] = useState([]);
137
+ const [loading, setLoading] = useState(true);
138
+ const [error, setError] = useState(null);
139
+ const [launchingId, setLaunchingId] = useState(null);
140
+ const [terminating, setTerminating] = useState(false);
141
+ const [search, setSearch] = useState('');
142
+ const authHeaders = useMemo(() => {
143
+ const headers = {
144
+ 'Content-Type': 'application/json',
145
+ };
146
+ if (token) {
147
+ headers.Authorization = `Bearer ${token}`;
148
+ }
149
+ return headers;
150
+ }, [token]);
151
+ const load = useCallback(async () => {
152
+ setLoading(true);
153
+ setError(null);
154
+ try {
155
+ const [libraryResponse, agentsResponse] = await Promise.all([
156
+ fetch(`${baseUrl}/api/v1/agents/library`, { headers: authHeaders }),
157
+ fetch(`${baseUrl}/api/v1/agents`, { headers: authHeaders }),
158
+ ]);
159
+ if (!libraryResponse.ok) {
160
+ throw new Error(`Failed to load agent library (${libraryResponse.status})`);
161
+ }
162
+ const libraryPayload = await libraryResponse.json().catch(() => null);
163
+ const nextSpecs = Array.isArray(libraryPayload)
164
+ ? libraryPayload
165
+ : libraryPayload?.agents || libraryPayload?.items || [];
166
+ setSpecs(nextSpecs);
167
+ if (agentsResponse.ok) {
168
+ const agentsPayload = await agentsResponse.json().catch(() => null);
169
+ const nextRunning = Array.isArray(agentsPayload)
170
+ ? agentsPayload
171
+ : agentsPayload?.agents || agentsPayload?.items || [];
172
+ setRunning(nextRunning);
173
+ }
174
+ }
175
+ catch (reason) {
176
+ setError(reason?.message || 'Unable to load agents.');
177
+ }
178
+ finally {
179
+ setLoading(false);
180
+ }
181
+ }, [authHeaders, baseUrl]);
182
+ useEffect(() => {
183
+ void load();
184
+ }, [load]);
185
+ const runningBySpec = useMemo(() => {
186
+ const map = new Map();
187
+ for (const agent of running) {
188
+ const agentId = agent.agent_id || agent.id;
189
+ if (agent.agent_spec_id && agentId) {
190
+ map.set(agent.agent_spec_id, agentId);
191
+ }
192
+ }
193
+ return map;
194
+ }, [running]);
195
+ const normalizedActiveAgentId = String(activeAgentId || '').trim();
196
+ const activeAgent = useMemo(() => {
197
+ if (!normalizedActiveAgentId) {
198
+ return null;
199
+ }
200
+ return (running.find(candidate => {
201
+ const candidateId = String(candidate.agent_id || candidate.id || '').trim();
202
+ return candidateId === normalizedActiveAgentId;
203
+ }) || null);
204
+ }, [running, normalizedActiveAgentId]);
205
+ // An "active" agent must be both configured and currently running.
206
+ const hasActiveAgent = Boolean(normalizedActiveAgentId && activeAgent);
207
+ const activeAgentSpec = useMemo(() => {
208
+ if (!activeAgent) {
209
+ return null;
210
+ }
211
+ const directSpecId = String(activeAgent.agent_spec_id || '').trim();
212
+ if (directSpecId) {
213
+ const directMatch = specs.find(spec => spec.id === directSpecId);
214
+ if (directMatch) {
215
+ return directMatch;
216
+ }
217
+ }
218
+ const nameCandidate = String(activeAgent.name || '').trim();
219
+ if (!nameCandidate) {
220
+ return null;
221
+ }
222
+ return specs.find(spec => spec.id === nameCandidate) || null;
223
+ }, [activeAgent, specs]);
224
+ const normalizedSearch = search.trim().toLowerCase();
225
+ const filteredSpecs = useMemo(() => {
226
+ if (!normalizedSearch) {
227
+ return specs;
228
+ }
229
+ return specs.filter(spec => {
230
+ const haystack = [
231
+ spec.id,
232
+ spec.name,
233
+ spec.description,
234
+ spec.model,
235
+ spec.inference_provider,
236
+ ...(spec.tags || []),
237
+ ]
238
+ .filter(Boolean)
239
+ .join(' ')
240
+ .toLowerCase();
241
+ return haystack.includes(normalizedSearch);
242
+ });
243
+ }, [specs, normalizedSearch]);
244
+ const launch = useCallback(async (spec) => {
245
+ setLaunchingId(spec.id);
246
+ setError(null);
247
+ try {
248
+ // Reuse the already-running agent for this spec when present so we do
249
+ // not spawn duplicates on the node.
250
+ let agentId = runningBySpec.get(spec.id) || null;
251
+ if (!agentId) {
252
+ const requestedName = spec.id;
253
+ const createResponse = await fetch(`${baseUrl}/api/v1/agents`, {
254
+ method: 'POST',
255
+ headers: authHeaders,
256
+ // Keep local node launches aligned with the working examples:
257
+ // POST /api/v1/agents requires `name` and optionally agent_spec_id.
258
+ body: JSON.stringify({
259
+ name: requestedName,
260
+ agent_spec_id: spec.id,
261
+ transport: 'ag-ui',
262
+ sandbox_variant: 'jupyter',
263
+ sandboxVariant: 'jupyter',
264
+ ...(LOCAL_JUPYTER_SANDBOX_URL
265
+ ? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
266
+ : {}),
267
+ }),
268
+ });
269
+ if (!createResponse.ok && createResponse.status === 409) {
270
+ // If the named agent already exists, reload running agents and try
271
+ // to reuse the matching instance instead of failing the launch.
272
+ const reloadResponse = await fetch(`${baseUrl}/api/v1/agents`, {
273
+ headers: authHeaders,
274
+ });
275
+ if (reloadResponse.ok) {
276
+ const reloadPayload = await reloadResponse
277
+ .json()
278
+ .catch(() => null);
279
+ const nextRunning = Array.isArray(reloadPayload)
280
+ ? reloadPayload
281
+ : reloadPayload?.agents || reloadPayload?.items || [];
282
+ setRunning(nextRunning);
283
+ const existing = nextRunning.find(candidate => {
284
+ const candidateId = candidate.agent_id || candidate.id || '';
285
+ return (candidate.agent_spec_id === spec.id ||
286
+ candidate.name === requestedName ||
287
+ candidateId === requestedName);
288
+ });
289
+ agentId = existing?.agent_id || existing?.id || null;
290
+ // If a same-name agent exists but is not tied to this spec,
291
+ // recreate it so configure/spec lookups and sandbox settings
292
+ // match the selected gallery card.
293
+ if (existing && existing.agent_spec_id !== spec.id) {
294
+ const staleId = String(existing.agent_id || existing.id || '').trim();
295
+ if (staleId) {
296
+ await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(staleId)}`, {
297
+ method: 'DELETE',
298
+ headers: authHeaders,
299
+ });
300
+ }
301
+ agentId = null;
302
+ }
303
+ }
304
+ }
305
+ if (!createResponse.ok && !agentId) {
306
+ // Conflict with stale same-name agent is handled above by deleting
307
+ // and recreating. Other failures remain hard errors.
308
+ if (createResponse.status !== 409) {
309
+ throw new Error(`Failed to launch agent (${createResponse.status})`);
310
+ }
311
+ }
312
+ if (!agentId && createResponse.ok) {
313
+ const createPayload = await createResponse.json().catch(() => null);
314
+ agentId =
315
+ createPayload?.agent_id ||
316
+ createPayload?.id ||
317
+ createPayload?.agent?.agent_id ||
318
+ createPayload?.agent?.id ||
319
+ null;
320
+ }
321
+ if (!agentId) {
322
+ const recreateResponse = await fetch(`${baseUrl}/api/v1/agents`, {
323
+ method: 'POST',
324
+ headers: authHeaders,
325
+ body: JSON.stringify({
326
+ name: requestedName,
327
+ agent_spec_id: spec.id,
328
+ transport: 'ag-ui',
329
+ sandbox_variant: 'jupyter',
330
+ sandboxVariant: 'jupyter',
331
+ ...(LOCAL_JUPYTER_SANDBOX_URL
332
+ ? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
333
+ : {}),
334
+ }),
335
+ });
336
+ if (!recreateResponse.ok) {
337
+ throw new Error(`Failed to launch agent (${recreateResponse.status})`);
338
+ }
339
+ const recreatePayload = await recreateResponse
340
+ .json()
341
+ .catch(() => null);
342
+ agentId =
343
+ recreatePayload?.agent_id ||
344
+ recreatePayload?.id ||
345
+ recreatePayload?.agent?.agent_id ||
346
+ recreatePayload?.agent?.id ||
347
+ null;
348
+ }
349
+ }
350
+ if (!agentId) {
351
+ throw new Error('Launch did not return an agent id.');
352
+ }
353
+ // Best-effort guard: if spec metadata is missing for this agent id,
354
+ // or if it is not configured with a jupyter sandbox, recreate once
355
+ // from the selected spec so notebook/document chat surfaces can bind
356
+ // a live local kernel.
357
+ const specResponse = await fetch(`${baseUrl}/api/v1/configure/agents/${encodeURIComponent(agentId)}/spec`, { headers: authHeaders });
358
+ let mustRecreateForSandbox = false;
359
+ if (specResponse.ok) {
360
+ const specPayload = await specResponse.json().catch(() => null);
361
+ const variant = String(specPayload?.sandbox_variant || specPayload?.sandboxVariant || '')
362
+ .trim()
363
+ .toLowerCase();
364
+ mustRecreateForSandbox = variant !== 'jupyter';
365
+ }
366
+ if (specResponse.status === 404 || mustRecreateForSandbox) {
367
+ await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(agentId)}`, {
368
+ method: 'DELETE',
369
+ headers: authHeaders,
370
+ });
371
+ const recreateResponse = await fetch(`${baseUrl}/api/v1/agents`, {
372
+ method: 'POST',
373
+ headers: authHeaders,
374
+ body: JSON.stringify({
375
+ name: spec.id,
376
+ agent_spec_id: spec.id,
377
+ transport: 'ag-ui',
378
+ sandbox_variant: 'jupyter',
379
+ sandboxVariant: 'jupyter',
380
+ ...(LOCAL_JUPYTER_SANDBOX_URL
381
+ ? { jupyter_sandbox: LOCAL_JUPYTER_SANDBOX_URL }
382
+ : {}),
383
+ }),
384
+ });
385
+ if (!recreateResponse.ok) {
386
+ throw new Error(`Failed to recreate agent with spec (${recreateResponse.status})`);
387
+ }
388
+ const recreatePayload = await recreateResponse
389
+ .json()
390
+ .catch(() => null);
391
+ agentId =
392
+ recreatePayload?.agent_id ||
393
+ recreatePayload?.id ||
394
+ recreatePayload?.agent?.agent_id ||
395
+ recreatePayload?.agent?.id ||
396
+ null;
397
+ }
398
+ if (!agentId) {
399
+ throw new Error('Launch did not return an agent id.');
400
+ }
401
+ const activeResponse = await fetch(`${baseUrl}/api/v1/agent-node/active-agent`, {
402
+ method: 'POST',
403
+ headers: authHeaders,
404
+ body: JSON.stringify({ agent_id: agentId }),
405
+ });
406
+ if (!activeResponse.ok) {
407
+ throw new Error(`Failed to set active agent (${activeResponse.status})`);
408
+ }
409
+ onLaunched(agentId);
410
+ }
411
+ catch (reason) {
412
+ const message = reason?.message || 'Unable to launch agent.';
413
+ setError(message);
414
+ onLaunchError?.(message);
415
+ }
416
+ finally {
417
+ setLaunchingId(null);
418
+ }
419
+ }, [authHeaders, baseUrl, onLaunched, onLaunchError, runningBySpec]);
420
+ const terminateActiveAgent = useCallback(async () => {
421
+ if (!normalizedActiveAgentId) {
422
+ return;
423
+ }
424
+ setTerminating(true);
425
+ setError(null);
426
+ try {
427
+ const deleteResponse = await fetch(`${baseUrl}/api/v1/agents/${encodeURIComponent(normalizedActiveAgentId)}`, {
428
+ method: 'DELETE',
429
+ headers: authHeaders,
430
+ });
431
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
432
+ throw new Error(`Failed to terminate active agent (${deleteResponse.status})`);
433
+ }
434
+ const clearResponse = await fetch(`${baseUrl}/api/v1/agent-node/active-agent`, {
435
+ method: 'POST',
436
+ headers: authHeaders,
437
+ body: JSON.stringify({ agent_id: null }),
438
+ });
439
+ if (!clearResponse.ok) {
440
+ throw new Error(`Failed to clear active agent (${clearResponse.status})`);
441
+ }
442
+ setRunning(prev => prev.filter(candidate => {
443
+ const candidateId = String(candidate.agent_id || candidate.id || '').trim();
444
+ return candidateId !== normalizedActiveAgentId;
445
+ }));
446
+ onTerminated?.(normalizedActiveAgentId);
447
+ }
448
+ catch (reason) {
449
+ const message = reason?.message || 'Unable to terminate active agent.';
450
+ setError(message);
451
+ onLaunchError?.(message);
452
+ }
453
+ finally {
454
+ setTerminating(false);
455
+ }
456
+ }, [
457
+ authHeaders,
458
+ baseUrl,
459
+ normalizedActiveAgentId,
460
+ onTerminated,
461
+ onLaunchError,
462
+ ]);
463
+ if (loading) {
464
+ return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 6 }, children: _jsx(Spinner, { size: "large" }) }));
465
+ }
466
+ return (_jsxs(Box, { children: [_jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: hasActiveAgent ? 'Running agent' : 'Choose an agent' }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: hasActiveAgent
467
+ ? 'Your node is currently running an active agent. You can terminate it to go back to the gallery.'
468
+ : 'Pick an agent from the gallery to launch it on this node. The active agent handles prompts sent directly to the node and through the Datalayer platform.' })] }), error && (_jsx(Box, { sx: {
469
+ mb: 3,
470
+ p: 2,
471
+ borderRadius: 2,
472
+ bg: 'danger.subtle',
473
+ color: 'danger.fg',
474
+ fontSize: 1,
475
+ }, children: error })), hasActiveAgent ? (_jsxs(Box, { sx: {
476
+ p: 4,
477
+ border: '1px solid',
478
+ borderColor: 'accent.muted',
479
+ borderRadius: 2,
480
+ bg: 'accent.subtle',
481
+ display: 'flex',
482
+ flexDirection: 'column',
483
+ gap: 3,
484
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsxs(Label, { variant: "secondary", sx: {
485
+ display: 'inline-flex',
486
+ alignItems: 'center',
487
+ gap: 1,
488
+ color: 'accent.fg',
489
+ }, children: [_jsx(CheckCircleFillIcon, { size: 12 }), "Active agent"] }), _jsx(Text, { sx: { fontWeight: 600 }, children: activeAgent?.name ||
490
+ activeAgent?.agent_spec_id ||
491
+ normalizedActiveAgentId })] }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Chat is enabled while an active agent is running on this node. Terminate it to return to the agent cards picker." }), activeAgentSpec && (_jsxs(Box, { sx: {
492
+ p: 3,
493
+ border: '1px solid',
494
+ borderColor: 'border.default',
495
+ borderRadius: 2,
496
+ bg: 'canvas.default',
497
+ display: 'flex',
498
+ flexDirection: 'column',
499
+ gap: 2,
500
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Label, { size: "small", variant: "accent", children: "Spec" }), _jsx(Text, { sx: { fontWeight: 600 }, children: activeAgentSpec.name || activeAgentSpec.id }), activeAgentSpec.version && (_jsxs(Label, { size: "small", variant: "secondary", children: ["v", activeAgentSpec.version] }))] }), activeAgentSpec.description && (_jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: activeAgentSpec.description })), _jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [_jsxs(Label, { size: "small", variant: "secondary", children: ["id: ", activeAgentSpec.id] }), activeAgentSpec.model && (_jsxs(Label, { size: "small", variant: "secondary", children: ["model: ", activeAgentSpec.model] })), activeAgentSpec.inference_provider && (_jsxs(Label, { size: "small", variant: "secondary", children: ["provider: ", activeAgentSpec.inference_provider] })), Array.isArray(activeAgentSpec.tags)
501
+ ? activeAgentSpec.tags.map(tag => (_jsx(Label, { size: "small", variant: "secondary", children: tag }, tag)))
502
+ : null] })] })), _jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-start' }, children: _jsx(Button, { variant: "danger", disabled: terminating, onClick: () => void terminateActiveAgent(), children: terminating ? 'Terminating…' : 'Terminate' }) })] })) : specs.length === 0 ? (_jsx(Box, { sx: {
503
+ p: 4,
504
+ textAlign: 'center',
505
+ border: '1px solid',
506
+ borderColor: 'border.default',
507
+ borderRadius: 2,
508
+ color: 'fg.muted',
509
+ }, children: "No agents are available in the library yet." })) : (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { mb: 3, maxWidth: 420 }, children: _jsx(TextInput, { value: search, onChange: event => setSearch(event.target.value), placeholder: "Filter agents by name, description, model, or tag...", block: true }) }), _jsx(Box, { sx: {
510
+ display: 'grid',
511
+ gridTemplateColumns: GRID_TEMPLATE,
512
+ gap: 3,
513
+ }, children: filteredSpecs.map(spec => {
514
+ const IconComponent = resolveAgentspecIcon(spec.icon, spec.name || spec.id);
515
+ const isActive = !!activeAgentId && runningBySpec.get(spec.id) === activeAgentId;
516
+ const isLaunching = launchingId === spec.id;
517
+ const isRunning = runningBySpec.has(spec.id);
518
+ const launchLabel = isActive
519
+ ? 'Open chat'
520
+ : isLaunching
521
+ ? 'Launching…'
522
+ : isRunning
523
+ ? 'Set active'
524
+ : 'Launch';
525
+ return (_jsxs(Box, { role: "button", tabIndex: isLaunching ? -1 : 0, onClick: () => {
526
+ if (!isLaunching) {
527
+ void launch(spec);
528
+ }
529
+ }, onKeyDown: event => {
530
+ if ((event.key === 'Enter' || event.key === ' ') &&
531
+ !isLaunching) {
532
+ event.preventDefault();
533
+ void launch(spec);
534
+ }
535
+ }, "aria-label": `${launchLabel} ${spec.name || spec.id}`, "aria-busy": isLaunching, "aria-disabled": isLaunching, sx: {
536
+ display: 'flex',
537
+ flexDirection: 'column',
538
+ textAlign: 'left',
539
+ cursor: isLaunching ? 'progress' : 'pointer',
540
+ p: 3,
541
+ border: '1px solid',
542
+ borderColor: isActive
543
+ ? 'success.emphasis'
544
+ : 'border.default',
545
+ borderRadius: 2,
546
+ bg: 'canvas.default',
547
+ // Hover feedback is border + shadow only. No transform: a
548
+ // transform here promotes the card to its own layer and makes
549
+ // the line-clamped description re-rasterize (per-paragraph
550
+ // motion artifact).
551
+ transition: 'box-shadow 180ms ease, border-color 180ms ease',
552
+ '&:hover': {
553
+ boxShadow: 'shadow.medium',
554
+ borderColor: isActive
555
+ ? 'success.emphasis'
556
+ : 'accent.emphasis',
557
+ },
558
+ '&:focus-visible': {
559
+ outline: '2px solid',
560
+ outlineColor: 'accent.fg',
561
+ outlineOffset: '2px',
562
+ },
563
+ '&[aria-disabled="true"]': {
564
+ opacity: 0.8,
565
+ },
566
+ }, children: [_jsxs(Box, { sx: {
567
+ display: 'flex',
568
+ alignItems: 'center',
569
+ gap: 2,
570
+ mb: 2,
571
+ }, children: [_jsx(Box, { sx: {
572
+ display: 'flex',
573
+ alignItems: 'center',
574
+ justifyContent: 'center',
575
+ width: 40,
576
+ height: 40,
577
+ borderRadius: 2,
578
+ fontSize: 3,
579
+ bg: 'canvas.subtle',
580
+ }, children: spec.emoji ? (_jsx(Text, { sx: { fontSize: 3, lineHeight: 1 }, children: spec.emoji })) : (_jsx(IconComponent, { size: 20 })) }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Heading, { as: "h3", sx: {
581
+ fontSize: 2,
582
+ mb: 0,
583
+ overflow: 'hidden',
584
+ textOverflow: 'ellipsis',
585
+ whiteSpace: 'nowrap',
586
+ }, children: spec.name || spec.id }), spec.version && (_jsxs(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: ["v", spec.version] }))] }), isActive && (_jsxs(Label, { variant: "success", sx: {
587
+ display: 'inline-flex',
588
+ alignItems: 'center',
589
+ gap: 1,
590
+ }, children: [_jsx(CheckCircleFillIcon, { size: 12 }), "Active"] }))] }), spec.description && (_jsx(Text, { sx: {
591
+ color: 'fg.muted',
592
+ fontSize: 1,
593
+ mb: 2,
594
+ display: '-webkit-box',
595
+ WebkitLineClamp: 3,
596
+ WebkitBoxOrient: 'vertical',
597
+ overflow: 'hidden',
598
+ }, children: spec.description })), spec.tags && spec.tags.length > 0 && (_jsx(Box, { sx: {
599
+ display: 'flex',
600
+ flexWrap: 'wrap',
601
+ gap: 1,
602
+ mb: 3,
603
+ }, children: spec.tags.slice(0, 4).map(tag => (_jsx(Label, { size: "small", variant: "secondary", children: tag }, tag))) })), _jsx(Box, { sx: { mt: 'auto', pt: 1 }, children: _jsxs(Box, { as: "span", sx: {
604
+ display: 'inline-flex',
605
+ alignItems: 'center',
606
+ gap: 1,
607
+ px: 2,
608
+ py: 1,
609
+ borderRadius: 2,
610
+ border: '1px solid',
611
+ borderColor: isActive
612
+ ? 'border.default'
613
+ : 'accent.muted',
614
+ bg: isActive ? 'canvas.subtle' : 'accent.subtle',
615
+ color: isActive ? 'fg.default' : 'accent.fg',
616
+ fontSize: 1,
617
+ fontWeight: 600,
618
+ }, children: [_jsx(IconComponent, { size: 14 }), launchLabel] }) })] }, spec.id));
619
+ }) }), filteredSpecs.length === 0 && (_jsx(Box, { sx: {
620
+ mt: 3,
621
+ p: 3,
622
+ border: '1px solid',
623
+ borderColor: 'border.default',
624
+ borderRadius: 2,
625
+ }, children: _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "No agents match this filter." }) }))] }))] }));
626
+ }
627
+ export default AgentNodeGallery;
@@ -4,7 +4,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
6
  import { createRoot } from 'react-dom/client';
7
- // import { Agent as AgentNotebook } from './Agent';
8
7
  import { AgentNotebook } from './AgentNotebook';
9
8
  const rootElement = document.getElementById('root');
10
9
  if (rootElement) {