@datalayer/agent-runtimes 1.5.89 → 1.5.91

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 (87) hide show
  1. package/README.md +29 -297
  2. package/dist-embed/datalayer-app-main.js +40 -36
  3. package/lib/apps/embed/element.js +20 -5
  4. package/lib/apps/embed/visitorApp.d.ts +23 -45
  5. package/lib/apps/embed/visitorApp.js +65 -13
  6. package/lib/components/teams/A2ATeamGraph.d.ts +58 -152
  7. package/lib/components/teams/A2ATeamGraph.js +169 -410
  8. package/lib/components/teams/SceneTranscript.d.ts +5 -1
  9. package/lib/components/teams/SceneTranscript.js +2 -2
  10. package/lib/components/teams/SceneView.d.ts +17 -5
  11. package/lib/components/teams/SceneView.js +7 -3
  12. package/lib/components/teams/a2aTeamFlow.d.ts +16 -7
  13. package/lib/components/teams/a2aTeamFlow.js +17 -14
  14. package/lib/components/teams/backdrops/catalogue.d.ts +35 -0
  15. package/lib/components/teams/backdrops/catalogue.js +34 -0
  16. package/lib/components/teams/backdrops/edges.d.ts +20 -0
  17. package/lib/components/teams/backdrops/edges.js +29 -0
  18. package/lib/components/teams/backdrops/faces.d.ts +24 -0
  19. package/lib/components/teams/backdrops/faces.js +39 -0
  20. package/lib/components/teams/backdrops/halo.d.ts +14 -0
  21. package/lib/components/teams/backdrops/halo.js +5 -0
  22. package/lib/components/teams/backdrops/hero.d.ts +14 -0
  23. package/lib/components/teams/backdrops/hero.js +178 -0
  24. package/lib/components/teams/backdrops/index.d.ts +22 -0
  25. package/lib/components/teams/backdrops/index.js +38 -0
  26. package/lib/components/teams/backdrops/nodes.d.ts +123 -0
  27. package/lib/components/teams/backdrops/nodes.js +265 -0
  28. package/lib/components/teams/backdrops/plain.d.ts +14 -0
  29. package/lib/components/teams/backdrops/plain.js +116 -0
  30. package/lib/components/teams/backdrops/studio.d.ts +17 -0
  31. package/lib/components/teams/backdrops/studio.js +122 -0
  32. package/lib/components/teams/backdrops/theme.d.ts +14 -0
  33. package/lib/components/teams/backdrops/theme.js +19 -0
  34. package/lib/components/teams/backdrops/types.d.ts +214 -0
  35. package/lib/components/teams/backdrops/types.js +5 -0
  36. package/lib/components/teams/index.d.ts +2 -0
  37. package/lib/components/teams/index.js +2 -0
  38. package/lib/components/teams/sceneTranscriptRules.d.ts +32 -1
  39. package/lib/components/teams/sceneTranscriptRules.js +104 -2
  40. package/lib/components/teams/transcript/RecordingLabel.d.ts +17 -0
  41. package/lib/components/teams/transcript/RecordingLabel.js +7 -0
  42. package/lib/components/teams/transcript/SequenceDiagram.d.ts +39 -0
  43. package/lib/components/teams/transcript/SequenceDiagram.js +190 -0
  44. package/lib/components/teams/transcript/TranscriptDetail.d.ts +27 -0
  45. package/lib/components/teams/transcript/TranscriptDetail.js +48 -0
  46. package/lib/components/teams/transcript/TranscriptTable.d.ts +23 -0
  47. package/lib/components/teams/transcript/TranscriptTable.js +112 -0
  48. package/lib/components/teams/transcript/TranscriptViews.d.ts +66 -0
  49. package/lib/components/teams/transcript/TranscriptViews.js +135 -0
  50. package/lib/components/teams/transcript/TranscriptViewsTypes.d.ts +13 -0
  51. package/lib/components/teams/transcript/TranscriptViewsTypes.js +5 -0
  52. package/lib/components/teams/transcript/index.d.ts +23 -0
  53. package/lib/components/teams/transcript/index.js +27 -0
  54. package/lib/components/teams/transcript/recordingPlayback.d.ts +52 -0
  55. package/lib/components/teams/transcript/recordingPlayback.js +146 -0
  56. package/lib/components/teams/transcript/selection.d.ts +59 -0
  57. package/lib/components/teams/transcript/selection.js +79 -0
  58. package/lib/components/teams/transcript/sequenceDiagramRules.d.ts +35 -0
  59. package/lib/components/teams/transcript/sequenceDiagramRules.js +158 -0
  60. package/lib/components/teams/transcript/transcriptDetailRules.d.ts +80 -0
  61. package/lib/components/teams/transcript/transcriptDetailRules.js +208 -0
  62. package/lib/components/teams/transcript/transcriptExport.d.ts +31 -0
  63. package/lib/components/teams/transcript/transcriptExport.js +82 -0
  64. package/lib/components/teams/transcript/transcriptRows.d.ts +76 -0
  65. package/lib/components/teams/transcript/transcriptRows.js +187 -0
  66. package/lib/components/teams/transcript/useSceneRecording.d.ts +45 -0
  67. package/lib/components/teams/transcript/useSceneRecording.js +96 -0
  68. package/lib/examples/ScenesExample.js +16 -3
  69. package/lib/portable/hosts/agentClient.d.ts +8 -0
  70. package/lib/portable/hosts/agentClient.js +2 -0
  71. package/lib/portable/hosts/approvalRenderers.d.ts +10 -2
  72. package/lib/portable/hosts/approvalRenderers.js +81 -2
  73. package/lib/portable/hosts/contracts.d.ts +1 -0
  74. package/lib/portable/hosts/contracts.js +2 -0
  75. package/lib/portable/hosts/contracts.json +56 -2
  76. package/lib/specs/actions.js +2 -0
  77. package/lib/specs/apps.d.ts +1 -0
  78. package/lib/specs/apps.js +219 -0
  79. package/lib/specs/sceneRecordings.d.ts +14 -0
  80. package/lib/specs/sceneRecordings.js +12 -0
  81. package/lib/specs/sceneSchema.js +11 -0
  82. package/lib/specs/scenes.d.ts +1 -0
  83. package/lib/specs/scenes.js +159 -0
  84. package/lib/types/scenes.d.ts +54 -0
  85. package/package.json +2 -1
  86. package/scripts/codegen/generate_mcp_servers.py +5 -16
  87. package/scripts/codegen/generate_scenes.py +80 -0
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { BaseEdge, EdgeLabelRenderer, } from '@xyflow/react';
4
+ import { Label } from '@primer/react';
5
+ import { Box } from '@datalayer/primer-addons';
6
+ import { ConnectionNodeBase, MemberNodeBase, } from './nodes.js';
7
+ import { arrowPath, straightPath } from './edges.js';
8
+ import { EdgeHalo } from './halo.js';
9
+ import { sceneBackdropChoice } from './catalogue.js';
10
+ /** The graph's own coordinates: the members, and room above them for their balloons. */
11
+ const NODE_WIDTH = 200;
12
+ const GAP = 220;
13
+ const CONNECTION_WIDTH = NODE_WIDTH / 2;
14
+ /** The studio's layout: the entry and a peer 220 apart, a connection half a member's size to its right. */
15
+ export const STUDIO_LAYOUT = {
16
+ nodeWidth: NODE_WIDTH,
17
+ gap: GAP,
18
+ rowGap: 48,
19
+ // The character, its name and where it runs.
20
+ memberHeight: size => size + 56,
21
+ connectionScale: 0.5,
22
+ connectionWidth: CONNECTION_WIDTH,
23
+ // Room for the MCP edge's words, as the A2A edge has room for its own.
24
+ connectionGap: 140,
25
+ connectionsApart: 16,
26
+ // Its mark, its label and how it is reached (about 45px under the mark),
27
+ // and room under it before the graph's edge.
28
+ connectionHeight: size => size + 58,
29
+ // The first one's mark level with the character's middle: a mark half
30
+ // the character's size, so a quarter down.
31
+ connectionsTop: size => size / 4,
32
+ balloonRoom: 144,
33
+ };
34
+ const MEMBER_LOOK = {
35
+ width: NODE_WIDTH,
36
+ figure: member => member.character,
37
+ name: { fontWeight: 600, textAlign: 'center' },
38
+ where: { fontSize: 0, color: 'fg.muted', textAlign: 'center' },
39
+ };
40
+ const CONNECTION_LOOK = {
41
+ width: CONNECTION_WIDTH,
42
+ mark: busy => ({
43
+ borderRadius: 2,
44
+ border: '1px solid',
45
+ borderColor: busy ? 'accent.emphasis' : 'border.default',
46
+ bg: 'canvas.default',
47
+ }),
48
+ markScale: 0.66,
49
+ label: { fontWeight: 600, fontSize: 1, textAlign: 'center' },
50
+ via: { fontSize: '10px', color: 'fg.muted', textAlign: 'center' },
51
+ details: { fontSize: '10px', color: 'fg.muted' },
52
+ };
53
+ /** A member: its character, its name and where it runs. */
54
+ export const MemberNode = memo(function MemberNode(props) {
55
+ return _jsx(MemberNodeBase, { ...props, look: MEMBER_LOOK });
56
+ });
57
+ /** A connection of a member's: the server's mark, what it reaches, and how. */
58
+ export const ConnectionNode = memo(function ConnectionNode(props) {
59
+ return _jsx(ConnectionNodeBase, { ...props, look: CONNECTION_LOOK });
60
+ });
61
+ /** The A2A link between two members: still, or flowing the way the message goes. */
62
+ export const LinkEdge = memo(function LinkEdge({ id, sourceX, sourceY, targetX, targetY, data, }) {
63
+ const flow = data?.flow ?? 'still';
64
+ const source = { x: sourceX, y: sourceY };
65
+ const target = { x: targetX, y: targetY };
66
+ const path = straightPath(source, target);
67
+ // The arrow at the end the message goes to, along the edge.
68
+ const towardPeer = flow === 'asking';
69
+ const arrow = arrowPath(source, target, towardPeer);
70
+ return (_jsxs(_Fragment, { children: [data?.selected && (_jsx(EdgeHalo, { path: path, color: "var(--bgColor-accent-emphasis)" })), _jsx(BaseEdge, { id: id, path: path, style: {
71
+ stroke: data?.connected
72
+ ? 'var(--borderColor-accent-emphasis)'
73
+ : 'var(--borderColor-muted)',
74
+ strokeWidth: 2,
75
+ strokeDasharray: data?.connected ? undefined : '6 6',
76
+ opacity: flow === 'still' ? 0.6 : 0.25,
77
+ } }), flow !== 'still' && (_jsxs("g", { "data-a2a-edge-flow": flow, children: [_jsx("path", { d: path, fill: "none", className: `a2a-team-flow a2a-team-flow-${flow}`, stroke: "var(--fgColor-accent)", strokeWidth: 3, strokeLinecap: "round", strokeDasharray: "10 14" }), _jsx("path", { d: arrow, fill: "var(--fgColor-accent)" })] })), _jsx(EdgeLabelRenderer, { children: _jsx(Box, { className: "nodrag nopan", position: "absolute", transform: `translate(-50%, 12px) translate(${(sourceX + targetX) / 2}px, ${(sourceY + targetY) / 2}px)`, pointerEvents: "none", textAlign: "center", children: _jsx(Label, { variant: data?.connected ? 'accent' : 'secondary', children: data?.label }) }) })] }));
78
+ });
79
+ /**
80
+ * A member's link to a connection, drawn as the A2A link is: still, flowing
81
+ * toward the connection while a call runs, and back with its result.
82
+ */
83
+ export const CallEdge = memo(function CallEdge({ id, sourceX, sourceY, targetX, targetY, data, }) {
84
+ const flow = data?.flow ?? 'still';
85
+ const source = { x: sourceX, y: sourceY };
86
+ const target = { x: targetX, y: targetY };
87
+ const path = straightPath(source, target);
88
+ // The arrow at the end the call or its result goes to, along the edge.
89
+ const arrow = arrowPath(source, target, flow === 'asking');
90
+ return (_jsxs(_Fragment, { children: [data?.selected && (_jsx(EdgeHalo, { path: path, color: "var(--bgColor-accent-emphasis)" })), _jsx(BaseEdge, { id: id, path: path, style: {
91
+ stroke: 'var(--borderColor-accent-emphasis)',
92
+ strokeWidth: 2,
93
+ opacity: flow === 'still' ? 0.6 : 0.25,
94
+ } }), flow !== 'still' && (_jsxs("g", { "data-mcp-edge-call": data?.calling, "data-mcp-edge-flow": flow, children: [_jsx("path", { d: path, fill: "none", className: `a2a-team-flow a2a-team-flow-${flow}`, stroke: "var(--fgColor-accent)", strokeWidth: 3, strokeLinecap: "round", strokeDasharray: "10 14" }), _jsx("path", { d: arrow, fill: "var(--fgColor-accent)" })] })), _jsx(EdgeLabelRenderer, { children: _jsx(Box, { className: "nodrag nopan", position: "absolute", transform: `translate(-50%, 12px) translate(${(sourceX + targetX) / 2}px, ${(sourceY + targetY) / 2}px)`, pointerEvents: "none", textAlign: "center", whiteSpace: "nowrap", "data-mcp-edge-label": "", children: _jsx(Label, { variant: "accent", children: data?.label }) }) })] }));
95
+ });
96
+ const NODE_TYPES = { member: MemberNode, connection: ConnectionNode };
97
+ const EDGE_TYPES = { a2a: LinkEdge, mcp: CallEdge };
98
+ /** The `studio` backdrop. */
99
+ export const STUDIO_BACKDROP = {
100
+ ...sceneBackdropChoice('studio'),
101
+ figure: MEMBER_LOOK.figure,
102
+ nodeTypes: NODE_TYPES,
103
+ edgeTypes: EDGE_TYPES,
104
+ theme: {
105
+ // The page's own colours: nothing of its own.
106
+ light: {},
107
+ dark: {},
108
+ sx: {
109
+ '@keyframes a2aTeamFlow': {
110
+ from: { strokeDashoffset: 24 },
111
+ to: { strokeDashoffset: 0 },
112
+ },
113
+ '& .a2a-team-flow-asking': {
114
+ animation: 'a2aTeamFlow 0.6s linear infinite',
115
+ },
116
+ '& .a2a-team-flow-answering': {
117
+ animation: 'a2aTeamFlow 0.6s linear infinite reverse',
118
+ },
119
+ },
120
+ },
121
+ layout: STUDIO_LAYOUT,
122
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The rules a backdrop's theme puts on a stage's box: the variables light
3
+ * and dark, and the box's own rules — for the graph, and for a stage that
4
+ * draws one member alone in the same look.
5
+ *
6
+ * @module components/teams/backdrops/theme
7
+ */
8
+ import type { SceneBackdropTheme, StageSx } from './types';
9
+ /**
10
+ * A theme's rules on a box: its own (its background, its keyframes) and its
11
+ * variables, light and dark (Primer's `data-color-mode`: `dark`, or `auto`
12
+ * on a dark system).
13
+ */
14
+ export declare function sceneStageSx(theme: SceneBackdropTheme): StageSx;
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A theme's rules on a box: its own (its background, its keyframes) and its
7
+ * variables, light and dark (Primer's `data-color-mode`: `dark`, or `auto`
8
+ * on a dark system).
9
+ */
10
+ export function sceneStageSx(theme) {
11
+ return {
12
+ ...theme.sx,
13
+ ...theme.light,
14
+ '[data-color-mode="dark"] &': theme.dark,
15
+ '@media (prefers-color-scheme: dark)': {
16
+ '[data-color-mode="auto"] &': theme.dark,
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * A backdrop: how a scene's stage is drawn. The same members, systems and
3
+ * links — what `A2ATeamGraph` computes of a team: who stands where, which
4
+ * way each link carries a message, which connection answers a call — drawn
5
+ * in another look: React Flow's node and edge types, the stage's own
6
+ * colours as CSS variables on its box, and how a member is sized and its
7
+ * connections placed beside it.
8
+ *
9
+ * The scene keeps its backdrop (`SceneStageSpec.backdrop`, agentspecs'
10
+ * `Backdrop`): `studio`, the drawing every scene had first; `hero`, round
11
+ * faces on a deep tint with a dot that flies with each message; `plain`,
12
+ * boxes and arrows that do not move.
13
+ *
14
+ * @module components/teams/backdrops/types
15
+ */
16
+ import type { EdgeTypes, NodeTypes } from '@xyflow/react';
17
+ import type { BoxProps } from '@datalayer/primer-addons';
18
+ import type { AssistantModel3D } from '../../../chat/assistant/avatar3dModel';
19
+ import type { AssistantAbout, AssistantStageProps } from '../../../chat/assistant/AssistantStage';
20
+ import type { BalloonSuggestion } from '../../../chat/assistant/SpeechBalloon';
21
+ import type { DisplayItem } from '../../../types/chat';
22
+ import type { AssistantMenuItem } from '../../../chat/assistant/AssistantContextMenu';
23
+ import type { AssistantSandbox } from '../../../chat/assistant/assistantDetails';
24
+ import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
25
+ import type { BalloonExpandTarget } from '../../../chat/assistant/BalloonVisual';
26
+ import type { BalloonDisplay } from '../../../chat/assistant/toolLine';
27
+ import type { A2ATeamConnection, A2ATeamFlow } from '../a2aTeamFlow';
28
+ import type { A2ATeamPersona } from '../useA2ATeam';
29
+ import type { SceneBackdropId } from '../../../types/scenes';
30
+ export type { SceneBackdropId };
31
+ /** Rules on the stage's box or a node, as `Box`'s `sx` reads them: Primer's tokens, nested selectors, keyframes. */
32
+ export type StageSx = NonNullable<BoxProps['sx']>;
33
+ /** One member of the team, as the graph draws it. */
34
+ export type A2ATeamGraphMember = {
35
+ id: string;
36
+ name: string;
37
+ emoji?: string;
38
+ /** Its avatar, as its Appspec's `interface.avatar` names it: an id, or a 3D model. */
39
+ character: string | AssistantModel3D;
40
+ /** Where it runs, in a few words: "in your browser", "on a runtime". */
41
+ where: string;
42
+ persona: A2ATeamPersona;
43
+ /** A click on the character. */
44
+ onToggle?: () => void;
45
+ /** It was sent away, or called back. */
46
+ onAway?: (away: boolean) => void;
47
+ /** The MCP servers it reaches, drawn to its right (`teamConnectionsOf`). */
48
+ connections?: A2ATeamConnection[];
49
+ /**
50
+ * How its balloon shows what it says (T-23): only what it says or
51
+ * does now (`current`, a team's default) — the words, the tool it calls,
52
+ * a notebook it was given — or a peek (`history`).
53
+ */
54
+ balloonDisplay?: BalloonDisplay;
55
+ /**
56
+ * The notebook in its balloon was clicked: by default, it is expanded —
57
+ * into `expandTarget`, scrolled to and focused, or in a dialog.
58
+ */
59
+ onNotebookOpen?: () => void;
60
+ /**
61
+ * Where the notebook it was given runs (`TeamNotebook`, editable, on the
62
+ * browser sandbox): an element of the page — the area under the graph —
63
+ * where it is drawn as it arrives and when it is expanded; without one,
64
+ * *Expand* opens it in a dialog over the page.
65
+ */
66
+ expandTarget?: BalloonExpandTarget;
67
+ /** What its notebook is called: `Accounting's notebook`. */
68
+ notebookTitle?: string;
69
+ /**
70
+ * What the person may ask it: chips in its balloon while it waits for a
71
+ * question, and a group of its menu; one chosen is sent (`onSuggestion`).
72
+ */
73
+ suggestions?: readonly BalloonSuggestion[];
74
+ onSuggestion?: (suggestion: BalloonSuggestion) => void;
75
+ /**
76
+ * The team's Agent Inspector's tracer: its menu offers *Inspect the
77
+ * agent…*, its own spans (what it did, and the A2A requests sent to it).
78
+ */
79
+ inspector?: OtelLiveTracer | null;
80
+ /** Stops its turn, from its menu, while it works. */
81
+ onStop?: () => void;
82
+ /** What its menu's *About* and *Agent Details…* say. */
83
+ about?: AssistantAbout;
84
+ /**
85
+ * Its code sandbox, for *Code Sandbox Details…*. The entry — the agent in
86
+ * the page — has the kernel of the notebook open under the graph, while
87
+ * one is.
88
+ */
89
+ sandbox?: AssistantSandbox;
90
+ /** The host's own entries of its menu. */
91
+ contextMenu?: readonly AssistantMenuItem[];
92
+ /**
93
+ * What its menu calls a click on it (`onToggle`): `Ask Sales` where it
94
+ * takes the person to the composer. Without `onToggle`, its menu has no
95
+ * such entry.
96
+ */
97
+ conversationLabel?: string;
98
+ /**
99
+ * What it said and did, as its `history` balloon lists it (`useA2ATeam`'s
100
+ * `entryHistory`, `peerHistory`): shown when its menu chooses *History*.
101
+ */
102
+ history?: readonly DisplayItem[];
103
+ /** Its balloon's display was chosen in its menu: the page may keep it. */
104
+ onBalloonDisplayChange?: (display: BalloonDisplay) => void;
105
+ };
106
+ /** Who talks to whom over A2A: an edge of the graph. */
107
+ export type A2ATeamGraphLink = {
108
+ from: string;
109
+ to: string;
110
+ };
111
+ /** Where a member stands: fractions of the box, as a scene's stage says. */
112
+ export type A2ATeamGraphPosition = {
113
+ x: number;
114
+ y: number;
115
+ };
116
+ /** What a member's node is given: the rest is read from the graph's contexts. */
117
+ export type MemberData = {
118
+ id: string;
119
+ /** Which half of the graph it stands in: its balloon opens toward the middle. */
120
+ side: 'left' | 'right';
121
+ /** Its figure's size, in pixels at full scale. */
122
+ size: number;
123
+ };
124
+ /** What a connection's node is given. */
125
+ export type ConnectionData = {
126
+ member: string;
127
+ connection: string;
128
+ /** Its mark's size, in pixels at full scale. */
129
+ size: number;
130
+ };
131
+ /** What an A2A link's edge is given. */
132
+ export type LinkData = {
133
+ flow: A2ATeamFlow;
134
+ connected: boolean;
135
+ /** What it says: `A2A`, and the flow's words while it carries a message. */
136
+ label: string;
137
+ /** The exchange selected in the transcript is on it: drawn with a halo (S3-02). */
138
+ selected: boolean;
139
+ };
140
+ /** What a member's link to a connection is given. */
141
+ export type CallData = {
142
+ /** Which way it carries a call: out with the call, back with its result. */
143
+ flow: A2ATeamFlow;
144
+ /** The tool being called, in words (`list invoices`); empty while none is. */
145
+ calling: string;
146
+ /** What it says: `MCP`, and what it carries while it carries something. */
147
+ label: string;
148
+ /** The call selected in the transcript is on it: drawn with a halo (S3-02). */
149
+ selected: boolean;
150
+ };
151
+ /**
152
+ * The stage's own colours and type, as CSS variables on the graph's box —
153
+ * `--scene-stage-bg` and the others a backdrop's nodes and edges read —
154
+ * for the light mode and the dark one (Primer's `data-color-mode`), and
155
+ * the box's own rules beside them: its background, its corners, the
156
+ * keyframes its edges move by.
157
+ */
158
+ export interface SceneBackdropTheme {
159
+ /** The variables in the light mode. */
160
+ light: Record<`--${string}`, string>;
161
+ /** The variables in the dark mode: `data-color-mode="dark"`, or `auto` on a dark system. */
162
+ dark: Record<`--${string}`, string>;
163
+ /** The box's rules: its background, padding and corners, the classes and keyframes its drawing uses. */
164
+ sx?: StageSx;
165
+ }
166
+ /** How a member is sized, and how its connections are placed beside it, in the graph's coordinates at full scale. */
167
+ export interface SceneBackdropLayout {
168
+ /** A member's node, wide. */
169
+ nodeWidth: number;
170
+ /** Between two columns of members. */
171
+ gap: number;
172
+ /** Between two rows of members. */
173
+ rowGap: number;
174
+ /** A member's node, tall: its figure of `size`, its name and where it runs. */
175
+ memberHeight: (size: number) => number;
176
+ /** A connection's mark, as a fraction of its member's figure. */
177
+ connectionScale: number;
178
+ /** A connection's node, wide. */
179
+ connectionWidth: number;
180
+ /** Between a member and its connections, to its right: room for the edge's words. */
181
+ connectionGap: number;
182
+ /** Between two connections of one member. */
183
+ connectionsApart: number;
184
+ /** A connection's node, tall: its mark of `size`, its label and how it is reached. */
185
+ connectionHeight: (size: number) => number;
186
+ /** Where a member's first connection's top is, from the member's top. */
187
+ connectionsTop: (size: number) => number;
188
+ /** The least room above the members for their balloons, when the page says none. */
189
+ balloonRoom: number;
190
+ }
191
+ /** A backdrop: how a scene's stage is drawn. */
192
+ export interface SceneBackdrop {
193
+ id: SceneBackdropId;
194
+ /** `Studio`, `Hero`, `Plain`. */
195
+ name: string;
196
+ /** One line of what the stage looks like, for a chooser. */
197
+ description: string;
198
+ /** React Flow's node types: `member`, `connection`. */
199
+ nodeTypes: NodeTypes;
200
+ /** React Flow's edge types: `a2a`, `mcp`. */
201
+ edgeTypes: EdgeTypes;
202
+ theme: SceneBackdropTheme;
203
+ layout: SceneBackdropLayout;
204
+ /**
205
+ * What a member is drawn as in this look — its own character, or a face
206
+ * made from its emoji, its character or its initial — for a stage that
207
+ * draws one member alone (the home page's solo scenes) in the graph's
208
+ * look. The same object for the same member, so the figure is not drawn
209
+ * anew.
210
+ */
211
+ figure: (member: SceneBackdropFace) => AssistantStageProps['character'];
212
+ }
213
+ /** What a face is made from: the member's id, name, emoji and character. */
214
+ export type SceneBackdropFace = Pick<A2ATeamGraphMember, 'id' | 'name' | 'emoji' | 'character'>;
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export {};
@@ -11,11 +11,13 @@ export * from './teamConnections';
11
11
  export * from './useA2ATeam';
12
12
  export * from './usePeerSandbox';
13
13
  export * from './A2ATeamGraph';
14
+ export * from './backdrops';
14
15
  export * from './TeamNotebook';
15
16
  export * from './NotebookPreview';
16
17
  export * from './sceneTranscriptRules';
17
18
  export * from './SceneTranscript';
18
19
  export * from './SceneView';
20
+ export * from './transcript';
19
21
  export * from './useBalloonRoom';
20
22
  export * from './sceneStage';
21
23
  export * from './AnswerSurfaces';
@@ -15,11 +15,13 @@ export * from './teamConnections.js';
15
15
  export * from './useA2ATeam.js';
16
16
  export * from './usePeerSandbox.js';
17
17
  export * from './A2ATeamGraph.js';
18
+ export * from './backdrops/index.js';
18
19
  export * from './TeamNotebook.js';
19
20
  export * from './NotebookPreview.js';
20
21
  export * from './sceneTranscriptRules.js';
21
22
  export * from './SceneTranscript.js';
22
23
  export * from './SceneView.js';
24
+ export * from './transcript/index.js';
23
25
  export * from './useBalloonRoom.js';
24
26
  export * from './sceneStage.js';
25
27
  export * from './AnswerSurfaces.js';
@@ -9,7 +9,10 @@
9
9
  * tool call an `execute_tool` span (the member and, for an MCP call, the
10
10
  * server it reaches: a connection of the member's), and a request to
11
11
  * another member an `a2a` span (who asked whom, in what words, and what came
12
- * back as its events). Once a run is over, the same lines are read from its
12
+ * back as its events). A coding harness's turn is a `shield.turn` span whose
13
+ * events are the Shield's decisions on each of its steps: one line per step,
14
+ * *Code Explainer → its workspace: Read README.md*, failed when the Shield
15
+ * refused it ({@link shieldStepsOf}). Once a run is over, the same lines are read from its
13
16
  * record (`RecordEntry`: a `turn`, a `tool_call`), the session's entries as
14
17
  * ai-agents kept them.
15
18
  *
@@ -49,9 +52,36 @@ export type SceneTranscriptLine = {
49
52
  open?: boolean;
50
53
  /** It failed: the span's status, the task's state. */
51
54
  failed?: boolean;
55
+ /**
56
+ * When a call or an ask that is over ended, in milliseconds since the
57
+ * epoch: the span's end (SCENES S2-01, the table's durations). None while
58
+ * it is open, and none for a line read from a record.
59
+ */
60
+ ended?: number;
52
61
  };
53
62
  /** The person in the scene, as the transcript names them. */
54
63
  export declare const PERSON = "You";
64
+ /** Where a harness's steps go (agentspecs' `WORKSPACE`): its files, its terminal. */
65
+ export declare const WORKSPACE: string;
66
+ /** The Shield's events that decide a harness's step, each naming the step's call. */
67
+ export declare const SHIELD_STEP_EVENTS: readonly string[];
68
+ /** Whether a call goes to a tool's home (*the page*, *a skill*…) rather than to a connection over MCP. */
69
+ export declare function isToolHome(to: string | undefined): boolean;
70
+ type Attributes = Record<string, unknown>;
71
+ /** Whether a span is a coding harness's turn behind the Shield. */
72
+ export declare const isShieldTurn: (span: OtelSpan) => boolean;
73
+ /** The call a Shield event decides, as a line's key carries it (no `:`). */
74
+ export declare const stepCallOf: (event: {
75
+ attributes?: Attributes;
76
+ }) => string;
77
+ /**
78
+ * A harness's steps in a `shield.turn` span: one line per call the Shield
79
+ * decided, at its first decision, saying its title, ended when the call
80
+ * completed or failed, failed when the Shield refused it (any of its
81
+ * decisions a `deny`). Its key is `<span>/<call>:called`: the drill-down
82
+ * reads the Shield's rule from the span's events by it.
83
+ */
84
+ export declare function shieldStepsOf(span: OtelSpan, member: SceneTranscriptMember): SceneTranscriptLine[];
55
85
  /**
56
86
  * The transcript of a scene from the spans recorded while it works (the
57
87
  * Agent Inspector's, one tracer for every member).
@@ -72,3 +102,4 @@ export declare function lineHeading(line: Pick<SceneTranscriptLine, 'from' | 'to
72
102
  export declare function lineText(line: SceneTranscriptLine): string;
73
103
  /** The transcript as text, one line each with its time: what *Copy as text* copies. */
74
104
  export declare function transcriptText(lines: readonly SceneTranscriptLine[]): string;
105
+ export {};
@@ -11,7 +11,19 @@ const TOOL_HOMES = {
11
11
  skill: 'a skill',
12
12
  codemode: 'codemode',
13
13
  runtime: 'its runtime',
14
+ harness: 'its workspace',
14
15
  };
16
+ /** Where a harness's steps go (agentspecs' `WORKSPACE`): its files, its terminal. */
17
+ export const WORKSPACE = TOOL_HOMES.harness;
18
+ /** The Shield's events that decide a harness's step, each naming the step's call. */
19
+ export const SHIELD_STEP_EVENTS = [
20
+ 'shield.tool_call',
21
+ 'shield.permission',
22
+ ];
23
+ /** Whether a call goes to a tool's home (*the page*, *a skill*…) rather than to a connection over MCP. */
24
+ export function isToolHome(to) {
25
+ return Object.values(TOOL_HOMES).includes(to ?? '');
26
+ }
15
27
  const text = (value) => typeof value === 'string' ? value : '';
16
28
  /** The text of the first message of a GenAI messages attribute (JSON). */
17
29
  function messageText(value) {
@@ -72,12 +84,98 @@ function onceEach(lines) {
72
84
  return true;
73
85
  });
74
86
  }
87
+ /** Whether a span is a coding harness's turn behind the Shield. */
88
+ export const isShieldTurn = (span) => span.span_name === 'shield.turn' ||
89
+ Object.prototype.hasOwnProperty.call(span.attributes ?? {}, 'shield.harness');
90
+ /** The call a Shield event decides, as a line's key carries it (no `:`). */
91
+ export const stepCallOf = (event) => text(event.attributes?.tool_call_id).replace(/:/g, '_');
92
+ /** A step in words: its title (*Read README.md*), else its path, command or kind. */
93
+ function stepText(attributes) {
94
+ for (const name of ['title', 'path', 'command', 'url', 'kind']) {
95
+ const said = text(attributes[name]).trim();
96
+ if (said) {
97
+ return said;
98
+ }
99
+ }
100
+ return 'a step';
101
+ }
102
+ /**
103
+ * A harness's steps in a `shield.turn` span: one line per call the Shield
104
+ * decided, at its first decision, saying its title, ended when the call
105
+ * completed or failed, failed when the Shield refused it (any of its
106
+ * decisions a `deny`). Its key is `<span>/<call>:called`: the drill-down
107
+ * reads the Shield's rule from the span's events by it.
108
+ */
109
+ export function shieldStepsOf(span, member) {
110
+ const start = millis(span.start_time);
111
+ const steps = new Map();
112
+ for (const event of span.events ?? []) {
113
+ if (!SHIELD_STEP_EVENTS.includes(event.name)) {
114
+ continue;
115
+ }
116
+ const call = stepCallOf(event);
117
+ if (!call) {
118
+ continue;
119
+ }
120
+ const attributes = event.attributes ?? {};
121
+ const at = millis(event.timestamp, start);
122
+ let step = steps.get(call);
123
+ if (!step) {
124
+ step = { at, text: '', failed: false };
125
+ steps.set(call, step);
126
+ }
127
+ if (!step.text &&
128
+ ['title', 'path', 'command', 'url'].some(name => text(attributes[name]).trim())) {
129
+ step.text = stepText(attributes);
130
+ }
131
+ if (attributes.decision === 'deny') {
132
+ step.failed = true;
133
+ step.ended = at;
134
+ }
135
+ if (attributes.status === 'completed' || attributes.status === 'failed') {
136
+ step.ended = at;
137
+ step.failed = step.failed || attributes.status === 'failed';
138
+ }
139
+ }
140
+ const lines = [];
141
+ for (const [call, step] of steps) {
142
+ // A step the Shield saw no end of ends with the turn, or is still going.
143
+ const ended = step.ended ??
144
+ (span.in_progress ? undefined : millis(span.end_time, step.at));
145
+ lines.push({
146
+ key: `${span.span_id}/${call}:called`,
147
+ at: step.at,
148
+ from: member.name,
149
+ to: WORKSPACE,
150
+ kind: 'called',
151
+ text: step.text || 'a step',
152
+ ...(ended === undefined ? { open: true } : { ended }),
153
+ ...(step.failed ? { failed: true } : {}),
154
+ });
155
+ }
156
+ return lines;
157
+ }
158
+ /** Whose turn a `shield.turn` span is: the agent of the turn above it, else its service. */
159
+ function turnMember(span, byId, members) {
160
+ let parent = span.parent_span_id ? byId.get(span.parent_span_id) : undefined;
161
+ for (let seen = 0; parent && seen < 32; seen += 1) {
162
+ const name = text(parent.attributes?.['gen_ai.agent.name']);
163
+ if (name) {
164
+ return memberNamed(name, members);
165
+ }
166
+ parent = parent.parent_span_id
167
+ ? byId.get(parent.parent_span_id)
168
+ : undefined;
169
+ }
170
+ return memberNamed(span.service_name, members);
171
+ }
75
172
  /**
76
173
  * The transcript of a scene from the spans recorded while it works (the
77
174
  * Agent Inspector's, one tracer for every member).
78
175
  */
79
176
  export function transcriptOfSpans(spans, members) {
80
177
  const lines = [];
178
+ const byId = new Map(spans.map(span => [span.span_id, span]));
81
179
  // A tool call under which an A2A request sits is the asking itself: the
82
180
  // request says it, with its words.
83
181
  const asking = new Set();
@@ -92,6 +190,10 @@ export function transcriptOfSpans(spans, members) {
92
190
  const end = millis(span.end_time, start);
93
191
  const operation = attributes['gen_ai.operation.name'];
94
192
  const failed = span.status_code === 'ERROR';
193
+ if (isShieldTurn(span)) {
194
+ lines.push(...shieldStepsOf(span, turnMember(span, byId, members)));
195
+ continue;
196
+ }
95
197
  if (operation === 'invoke_agent') {
96
198
  const agent = memberNamed(text(attributes['gen_ai.agent.name']) || span.service_name, members);
97
199
  const asked = messageText(attributes['gen_ai.input.messages']);
@@ -143,7 +245,7 @@ export function transcriptOfSpans(spans, members) {
143
245
  to: calledOf(tool, text(attributes['datalayer.tool.kind']), agent),
144
246
  kind: 'called',
145
247
  text: tool,
146
- ...(span.in_progress ? { open: true } : {}),
248
+ ...(span.in_progress ? { open: true } : { ended: end }),
147
249
  ...(failed ? { failed } : {}),
148
250
  });
149
251
  continue;
@@ -163,7 +265,7 @@ export function transcriptOfSpans(spans, members) {
163
265
  to: peer.name,
164
266
  kind: 'asked',
165
267
  text: request,
166
- ...(span.in_progress ? { open: true } : {}),
268
+ ...(span.in_progress ? { open: true } : { ended: end }),
167
269
  });
168
270
  // What came back: the answer, or the last words of its events.
169
271
  let answer = text(attributes['a2a.answer.text']).trim();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The label a recording plays under: *Recording, taken 29 October 2026*,
3
+ * so that nobody takes it for the scene playing live. Its note, when the
4
+ * recording has one, is the label's title.
5
+ *
6
+ * @module components/teams/transcript/RecordingLabel
7
+ */
8
+ import type { JSX } from 'react';
9
+ export type RecordingLabelProps = {
10
+ /** When it was taken: `2026-10-29`. */
11
+ taken?: string;
12
+ /** What the audience is told of it. */
13
+ note?: string;
14
+ size?: 'small' | 'large';
15
+ };
16
+ export declare function RecordingLabel({ taken, note, size, }: RecordingLabelProps): JSX.Element;
17
+ export default RecordingLabel;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Label } from '@primer/react';
3
+ import { recordingLabel } from './recordingPlayback.js';
4
+ export function RecordingLabel({ taken, note, size = 'small', }) {
5
+ return (_jsx(Label, { variant: "attention", size: size, title: note, "data-scene-recording-label": taken ?? '', children: recordingLabel(taken) }));
6
+ }
7
+ export default RecordingLabel;