@datalayer/agent-runtimes 1.3.85 → 1.3.86

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.
@@ -0,0 +1,241 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Button, Flash, Heading, Text, Textarea, UnderlineNav, } from '@primer/react';
4
+ import { Box } from '@datalayer/primer-addons';
5
+ import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
6
+ import { AnonymousKeyTimer } from '@datalayer/core/lib/components/anonymous/AnonymousKeyTimer';
7
+ import { Streamdown } from 'streamdown';
8
+ import { ThemedProvider } from './utils/themedProvider';
9
+ import { useBrowserInference } from '../hooks/useBrowserInference';
10
+ import { useAnonymousSessionStore } from '../runtimes/browser/anonymousToken';
11
+ import { connectA2APeer } from '../runtimes/browser/a2aPeer';
12
+ import { A2ATeamGraph, NOTEBOOK_AND_WORDS, PERSON, SceneView, sceneCuesOf, sceneMemberUrl, sceneRuntimePorts, sceneStageOf, teamConnectionsOf, teamToolClassifier, useA2ATeam, } from '../components/teams';
13
+ import { listSceneSpecs } from '../specs/scenes';
14
+ import { classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
15
+ /** Where the members on a runtime are, by application id: the local ports, unless a variable says. */
16
+ const PORTS = sceneRuntimePorts();
17
+ /**
18
+ * Reach a member on a runtime: read its card at `url`, its traffic recorded
19
+ * as spans for the inspectors and the transcript, as asked by `asker`.
20
+ */
21
+ function useReached(url, asker, name, connections, tracer) {
22
+ const [reached, setReached] = useState({ peer: null, error: null });
23
+ useEffect(() => {
24
+ let current = true;
25
+ setReached({ peer: null, error: null });
26
+ connectA2APeer({
27
+ url,
28
+ fetch: traceA2AFetch(globalThis.fetch.bind(globalThis), {
29
+ tracer,
30
+ asker,
31
+ peer: name,
32
+ classifyTool: teamToolClassifier(connections, classifyToolCall),
33
+ }),
34
+ })
35
+ .then(peer => current && setReached({ peer, error: null }))
36
+ .catch(error => current &&
37
+ setReached({
38
+ peer: null,
39
+ error: error instanceof Error ? error.message : String(error),
40
+ }));
41
+ return () => {
42
+ current = false;
43
+ };
44
+ }, [url, asker, name, connections, tracer]);
45
+ return reached;
46
+ }
47
+ /** The members of a scene that run on a runtime, each with its address and connections. */
48
+ function runtimeMembersOf(stage) {
49
+ const env = import.meta.env;
50
+ return stage.members
51
+ .filter(member => member.runsIn === 'runtime')
52
+ .map(member => ({
53
+ ...member,
54
+ url: sceneMemberUrl(member.app.id, env, PORTS),
55
+ connections: teamConnectionsOf(member.app),
56
+ }));
57
+ }
58
+ /** One member on a runtime: reached, and told to the scene. */
59
+ function ReachMember({ member, asker, tracer, onReached, }) {
60
+ const reached = useReached(member.url, asker, member.app.name, member.connections, tracer);
61
+ useEffect(() => onReached(member.id, reached), [member.id, reached, onReached]);
62
+ return null;
63
+ }
64
+ /** What a member not reached needs: its application's setup, and how to serve it. */
65
+ function memberNeeds(member, error) {
66
+ return [
67
+ `${member.app.name} is not reachable at ${member.url}: ${error}`,
68
+ ...member.setup,
69
+ `Serve it with python examples/sales-accounting-a2a/serve_scenes.py (npm run examples does), or set VITE_A2A_${member.app.id
70
+ .replace(/-/g, '_')
71
+ .toUpperCase()}_URL to where it runs.`,
72
+ ];
73
+ }
74
+ function Scene({ scene }) {
75
+ const stage = useMemo(() => sceneStageOf(scene), [scene]);
76
+ const entry = stage.entry;
77
+ const entryInBrowser = entry.runsIn === 'browser';
78
+ const runtimeMembers = useMemo(() => runtimeMembersOf(stage), [stage]);
79
+ const { inference, needsSignIn, anonymous } = useBrowserInference(entryInBrowser);
80
+ // One tracer of what every member does: each inspector shows its own.
81
+ const { tracer: inspector } = useAgentInspector();
82
+ const [reached, setReached] = useState({});
83
+ const onReached = useMemo(() => (id, found) => setReached(prev => prev[id]?.peer === found.peer && prev[id]?.error === found.error
84
+ ? prev
85
+ : { ...prev, [id]: found }), []);
86
+ const reachedOf = (id) => reached[id] ?? { peer: null, error: null };
87
+ // The entry's peers over A2A, in the cast's order; the entry itself when
88
+ // it runs on a runtime.
89
+ const peerOptions = useMemo(() => stage.peers.map(peer => {
90
+ const member = runtimeMembers.find(one => one.id === peer.id);
91
+ return {
92
+ app: peer.app,
93
+ peer: reached[peer.id]?.peer ?? null,
94
+ connections: member?.connections ?? [],
95
+ ...(stage.opensFirst === peer.id
96
+ ? { greeting: peer.cast.persona.line }
97
+ : {}),
98
+ };
99
+ }), [stage, runtimeMembers, reached]);
100
+ const team = useA2ATeam({
101
+ entry: entry.app,
102
+ peers: peerOptions,
103
+ entryPeer: entryInBrowser ? undefined : reachedOf(entry.id).peer,
104
+ inference,
105
+ accept: NOTEBOOK_AND_WORDS,
106
+ inspector,
107
+ });
108
+ // The script's cues, labelled by the entry's starters (A-13): chips in
109
+ // its balloon while it waits for a question.
110
+ const suggestions = useMemo(() => sceneCuesOf(scene, entry.app.interface.starters ?? []), [scene, entry.app]);
111
+ const [draft, setDraft] = useState('');
112
+ const composer = useRef(null);
113
+ // Under the graph: where a notebook a member gives runs.
114
+ const notebookArea = useRef(null);
115
+ const send = (text) => {
116
+ setDraft('');
117
+ void team.send(text);
118
+ };
119
+ // The members as the transcript names them: the spans' services, and the
120
+ // connections a tool call is of.
121
+ const transcriptMembers = useMemo(() => stage.members.map(member => ({
122
+ id: member.app.id,
123
+ name: member.app.name,
124
+ connections: runtimeMembers.find(one => one.id === member.id)?.connections ?? [],
125
+ })), [stage, runtimeMembers]);
126
+ const whereOf = (member) => member.runsIn === 'browser'
127
+ ? 'in your browser'
128
+ : `on a runtime, ${new URL(runtimeMembers.find(one => one.id === member.id)?.url ?? 'http://runtime').host}`;
129
+ const aboutOf = (member) => {
130
+ const peer = reachedOf(member.id).peer;
131
+ const url = runtimeMembers.find(one => one.id === member.id)?.url;
132
+ return {
133
+ name: member.app.name,
134
+ spec: `${member.app.id}:${member.app.version}`,
135
+ model: member.app.model || undefined,
136
+ where: whereOf(member),
137
+ description: peer?.card.description || member.app.description || undefined,
138
+ skills: peer?.card.skills.map(skill => skill.name),
139
+ protocol: peer ? 'a2a' : undefined,
140
+ url: peer ? url : undefined,
141
+ };
142
+ };
143
+ const notReached = runtimeMembers.filter(member => reachedOf(member.id).error);
144
+ const notebookGiver = stage.peers[0] ?? entry;
145
+ return (_jsxs(Box, { "data-scene-example": scene.id, children: [_jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, "data-scene-assumes": "", children: stage.assumes }), anonymous.status === 'active' && anonymous.expiresAt && (_jsx(Box, { sx: { mb: 3 }, children: _jsx(AnonymousKeyTimer, { expiresAt: anonymous.expiresAt, grantedMs: anonymous.grantedMs, label: `${entry.app.name}'s trial key`, onExpire: () => useAnonymousSessionStore.getState().expire() }) })), anonymous.status === 'expired' && (_jsx(Box, { sx: { mb: 3 }, children: _jsx(AnonymousKeyExpired, { agentName: entry.app.name, onSignedIn: () => useAnonymousSessionStore.getState().clear() }) })), entryInBrowser && needsSignIn && (_jsxs(Flash, { variant: "warning", sx: { mb: 3 }, children: [entry.app.name, " asks its model through the Datalayer inference service: sign in, or wait for a visitor\u2019s trial key."] })), notReached.map(member => (_jsx(Flash, { variant: "danger", sx: { mb: 3 }, "data-scene-needs": member.app.id, children: memberNeeds(member, reachedOf(member.id).error ?? '').map(line => (_jsx(Text, { as: "p", sx: { m: 0 }, children: line }, line))) }, member.id))), runtimeMembers.map(member => (_jsx(ReachMember, { member: member,
146
+ // Its requests are the entry's, or the person's own when the
147
+ // page asks it itself.
148
+ asker: member.entry ? PERSON : entry.app.name, tracer: inspector, onReached: onReached }, member.id))), _jsx(Box, { sx: {
149
+ border: '1px solid',
150
+ borderColor: 'border.default',
151
+ borderRadius: 2,
152
+ bg: 'canvas.subtle',
153
+ px: 2,
154
+ pt: 2,
155
+ mb: 3,
156
+ }, children: _jsx(SceneView, { members: transcriptMembers, tracer: inspector, emptyText: `Nothing said yet: ask ${entry.app.name} with one of the suggestions.`, children: _jsx(A2ATeamGraph, { entry: {
157
+ id: entry.app.id,
158
+ name: entry.app.name,
159
+ emoji: entry.cast.persona.face || entry.app.emoji,
160
+ character: entry.app.interface.assistant ?? 'paperclip',
161
+ where: whereOf(entry),
162
+ persona: team.entryPersona,
163
+ onToggle: () => composer.current?.focus(),
164
+ conversationLabel: `Ask ${entry.app.name}`,
165
+ onAway: team.setEntryAway,
166
+ history: team.entryHistory,
167
+ connections: runtimeMembers.find(one => one.id === entry.id)
168
+ ?.connections,
169
+ expandTarget: notebookArea,
170
+ notebookTitle: `${notebookGiver.app.name}’s notebook`,
171
+ suggestions: team.ready && !team.busy ? suggestions : undefined,
172
+ onSuggestion: suggestion => send(suggestion.prompt),
173
+ inspector,
174
+ onStop: team.busy ? team.stop : undefined,
175
+ about: aboutOf(entry),
176
+ }, peers: stage.peers.map(peer => {
177
+ const kept = team.peers.find(one => one.id === peer.app.id);
178
+ return {
179
+ id: peer.app.id,
180
+ name: peer.app.name,
181
+ emoji: reachedOf(peer.id).peer?.face?.emoji ||
182
+ peer.cast.persona.face ||
183
+ peer.app.emoji,
184
+ character: peer.app.interface.assistant ?? 'wizard',
185
+ where: whereOf(peer),
186
+ persona: kept?.persona ?? team.peerPersona,
187
+ onAway: kept?.setAway,
188
+ history: kept?.history,
189
+ connections: runtimeMembers.find(one => one.id === peer.id)
190
+ ?.connections,
191
+ inspector,
192
+ about: aboutOf(peer),
193
+ };
194
+ }), flows: team.flows, calls: team.calls, links: stage.links.map(link => ({
195
+ from: stage.members.find(m => m.id === link.from)?.app.id ??
196
+ link.from,
197
+ to: stage.members.find(m => m.id === link.to)?.app.id ?? link.to,
198
+ })), positions: Object.fromEntries(stage.members
199
+ .filter(member => stage.positions[member.id])
200
+ .map(member => [member.app.id, stage.positions[member.id]])), connected: Object.fromEntries(stage.peers.map(peer => [
201
+ peer.app.id,
202
+ reachedOf(peer.id).peer !== null,
203
+ ])), labels: Object.fromEntries(stage.peers.map(peer => {
204
+ const found = reachedOf(peer.id).peer;
205
+ return [
206
+ peer.app.id,
207
+ found ? `A2A · ${found.skill.name}` : 'A2A · not connected',
208
+ ];
209
+ })),
210
+ // Room for the balloons: suggestions, *more* and a notebook.
211
+ balloonRoom: 320 }) }) }), _jsx(Box, { ref: notebookArea, sx: { mb: team.notebook ? 3 : 0 }, "data-team-notebook-placement": "under-graph" }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsxs(Box, { sx: { flex: '1 1 420px', minWidth: 0 }, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: ["Ask ", entry.app.name] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mt: 0 }, children: "Or choose one of its suggestions in its balloon: the script\u2019s cues." }), _jsx(Textarea, { ref: composer, block: true, rows: 3, value: draft, placeholder: suggestions[0]?.prompt ?? 'Ask…', onChange: event => setDraft(event.target.value), onKeyDown: event => {
212
+ if (event.key === 'Enter' && !event.shiftKey) {
213
+ event.preventDefault();
214
+ send(draft);
215
+ }
216
+ } }), _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { variant: "primary", disabled: !team.ready || team.busy || !draft.trim(), onClick: () => send(draft), children: "Ask" }), team.busy && _jsx(Button, { onClick: team.stop, children: "Stop" })] }), _jsx(Box, { sx: { mt: 3 }, "data-team-conversation": "", children: team.turns.map((turn, index) => (_jsxs(Box, { sx: {
217
+ p: 2,
218
+ mb: 2,
219
+ borderRadius: 2,
220
+ bg: turn.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
221
+ }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block' }, children: turn.role === 'user' ? PERSON : entry.app.name }), _jsx(Streamdown, { children: turn.text })] }, index))) })] }), _jsxs(Box, { sx: { flex: '1 1 360px', minWidth: 0 }, children: [!team.notebook && stage.peers.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: "The report" }), _jsx(Box, { sx: {
222
+ p: 3,
223
+ border: '1px solid',
224
+ borderColor: 'border.default',
225
+ borderRadius: 2,
226
+ minHeight: 120,
227
+ }, "data-team-report": "", children: team.report ? (_jsx(Streamdown, { children: team.report })) : (_jsx(Text, { sx: { color: 'fg.muted' }, children: "What the last member asked returns appears here, as it returned it, or as a notebook to run when it gives one." })) })] })), scene.setup.length > 0 && (_jsxs(Box, { sx: { mt: 3 }, "data-scene-setup": "", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 1 }, children: "What this scene needs" }), scene.setup.map(line => (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: line }, line)))] }))] })] })] }));
228
+ }
229
+ function Scenes() {
230
+ const scenes = useMemo(() => listSceneSpecs(), []);
231
+ const [selected, setSelected] = useState(scenes[0]?.id ?? '');
232
+ const scene = scenes.find(one => one.id === selected) ?? scenes[0];
233
+ return (_jsxs(Box, { sx: { p: 4, maxWidth: 1080, mx: 'auto' }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: "Scenes" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, children: ["The catalogue\u2019s scenes, played against the local servers: each member on a runtime is served over A2A by ", _jsx("code", { children: "serve_scenes.py" }), "; the entry in your browser asks on your token or a visitor\u2019s trial key."] }), _jsx(UnderlineNav, { "aria-label": "Scenes", sx: { mb: 3 }, children: scenes.map(one => (_jsxs(UnderlineNav.Item, { "aria-current": one.id === scene?.id ? 'page' : undefined, onSelect: event => {
234
+ event.preventDefault();
235
+ setSelected(one.id);
236
+ }, "data-scene-tab": one.id, children: [one.emoji, " ", one.name] }, one.id))) }), scene && _jsx(Scene, { scene: scene }, scene.id)] }));
237
+ }
238
+ export function ScenesExample() {
239
+ return (_jsx(ThemedProvider, { children: _jsx(Scenes, {}) }));
240
+ }
241
+ export default ScenesExample;
@@ -112,6 +112,7 @@ export const EXAMPLE_ENTRIES = [
112
112
  // for one sandbox — and a second one that would not agree with the first.
113
113
  ['example', 'loop', 'workspace', 'sandbox', 'owns-sandbox-control']),
114
114
  makeEntry('LoopAppComputerExample', () => import('./LoopAppComputerExample'), 'An application beside its computer: what its agent ran on its sandbox, its files, and Take over and Hand back (LOOP R-23).', ['example', 'loop', 'app', 'computer', 'sandbox', 'owns-sandbox-control']),
115
+ makeEntry('ScenesExample', () => import('./ScenesExample'), 'The scenes of the catalogue in tabs — Sales & Accounting, Month-end close, Crop monitoring, Disaster assessment — each its graph and transcript, its cues as suggestions, played against the local servers (LOOP A-15).', ['example', 'loop', 'scenes', 'a2a', 'agentspecs', 'team']),
115
116
  makeEntry('LoopShellExample', () => import('./LoopShellExample'), 'The Loop shell at its most naked: a blank canvas, a floating draggable prompt, and an editor selector in the corner — none, notebook or document.',
116
117
  // `owns-sandbox-control`: the shell is pinned to the browser sandbox, so
117
118
  // the page's selector would offer three targets it cannot move to.
@@ -23,7 +23,10 @@ export const EXAMPLE_GROUP_ORDER = [
23
23
  'CopilotKit',
24
24
  ];
25
25
  export const getExampleGroup = (id) => {
26
+ // The library of specs, the Loop shells, and the scenes of the catalogue
27
+ // (LOOP A-15): the applications and what is staged with them.
26
28
  if (id === 'AgentspecsExample' ||
29
+ id === 'ScenesExample' ||
27
30
  id.startsWith('Loop') ||
28
31
  id === 'DecksAgent') {
29
32
  return 'Apps';
@@ -27,6 +27,7 @@ export { default as AgentCodemodeExample } from './AgentCodemodeExample';
27
27
  export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
28
28
  export { default as AgentDecideExample } from './AgentDecideExample';
29
29
  export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
30
+ export { default as ScenesExample } from './ScenesExample';
30
31
  export { default as AgentEvalsExample } from './AgentEvalsExample';
31
32
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
32
33
  export { default as AgentHooksExample } from './AgentHooksExample';
@@ -33,6 +33,7 @@ export { default as AgentCodemodeExample } from './AgentCodemodeExample';
33
33
  export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
34
34
  export { default as AgentDecideExample } from './AgentDecideExample';
35
35
  export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
36
+ export { default as ScenesExample } from './ScenesExample';
36
37
  export { default as AgentEvalsExample } from './AgentEvalsExample';
37
38
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
38
39
  export { default as AgentHooksExample } from './AgentHooksExample';
@@ -62,6 +62,9 @@ const ANONYMOUS_EXAMPLES = new Set([
62
62
  // The Sales and Accounting team: Sales in the page on a visitor's trial
63
63
  // key, Accounting on the local server, which answers the machine itself.
64
64
  'AgentA2ATeamExample',
65
+ // The scenes of the catalogue: each entry in the page on a visitor's trial
66
+ // key, the members on a runtime on local servers that answer the machine.
67
+ 'ScenesExample',
65
68
  // Decide, on the local agent-runtimes server as above: Jev is asked
66
69
  // through the server's ai-inference, with the server's own key.
67
70
  'AgentDecideExample',
@@ -1045,6 +1048,7 @@ const ExampleAppThemed = ({ selectedExample, isChangingExample, error, ExampleCo
1045
1048
  'LoopStrategyExample',
1046
1049
  'LoopAppComputerExample',
1047
1050
  'AgentspecsExample',
1051
+ 'ScenesExample',
1048
1052
  ];
1049
1053
  const loopOrder = (id) => {
1050
1054
  const index = LOOP_ORDER.indexOf(id);
@@ -27,5 +27,6 @@ export * from './gates';
27
27
  export * from './tracks';
28
28
  export * from './ops';
29
29
  export * from './apps';
30
+ export * from './scenes';
30
31
  export * from './actions';
31
32
  export * from './appspecSchema';
@@ -31,5 +31,6 @@ export * from './gates';
31
31
  export * from './tracks';
32
32
  export * from './ops';
33
33
  export * from './apps';
34
+ export * from './scenes';
34
35
  export * from './actions';
35
36
  export * from './appspecSchema';
@@ -68,12 +68,18 @@ export const EARTHDATA_MCP_SERVER_0_0_1 = {
68
68
  emoji: '🌍',
69
69
  url: '',
70
70
  command: 'npx',
71
- args: ['-y', 'earthdata-mcp-server'],
71
+ args: [
72
+ '-y',
73
+ 'mcp-remote',
74
+ 'https://mcp.datalayer.run/mcp?only=earthdata',
75
+ '--header',
76
+ 'Authorization: Bearer ${DATALAYER_API_KEY}',
77
+ ],
72
78
  transport: 'stdio',
73
79
  enabled: true,
74
80
  isAvailable: false,
75
81
  tools: [],
76
- requiredEnvVars: ['EARTHDATA_USERNAME:0.0.1', 'EARTHDATA_PASSWORD:0.0.1'],
82
+ requiredEnvVars: ['DATALAYER_API_KEY:0.0.1'],
77
83
  };
78
84
  export const EURUS_MCP_SERVER_0_0_1 = {
79
85
  id: 'eurus',
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Scene Catalog.
3
+ *
4
+ * A team, staged: the setting, the script, the stage directions, the
5
+ * audience, the rehearsal and where it plays. The cast is resolved.
6
+ *
7
+ * This file is AUTO-GENERATED from YAML specifications.
8
+ * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.
9
+ */
10
+ import type { SceneSpec } from '../types/scenes';
11
+ export declare const CROP_MONITORING_SCENE_0_0_1: SceneSpec;
12
+ export declare const DISASTER_ASSESSMENT_SCENE_0_0_1: SceneSpec;
13
+ export declare const MONTH_END_CLOSE_SCENE_0_0_1: SceneSpec;
14
+ export declare const SALES_AND_ACCOUNTING_SCENE_0_0_1: SceneSpec;
15
+ export declare const SCENE_CATALOGUE: Record<string, SceneSpec>;
16
+ /** A scene, by `id` or `id:version`, or undefined. */
17
+ export declare function getSceneSpec(ref: string): SceneSpec | undefined;
18
+ /** Every scene of the catalogue, in the catalogue's order, or those carrying a tag. */
19
+ export declare function listSceneSpecs(tag?: string): SceneSpec[];
20
+ /** Every scene that stages a team, by its id with or without a version. */
21
+ export declare function scenesStaging(teamId: string): SceneSpec[];