@datalayer/agent-runtimes 1.5.90 → 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.
- package/README.md +29 -297
- package/dist-embed/datalayer-app-main.js +2 -2
- package/lib/components/teams/A2ATeamGraph.d.ts +40 -155
- package/lib/components/teams/A2ATeamGraph.js +122 -400
- package/lib/components/teams/SceneTranscript.d.ts +5 -1
- package/lib/components/teams/SceneTranscript.js +2 -2
- package/lib/components/teams/SceneView.d.ts +17 -5
- package/lib/components/teams/SceneView.js +7 -3
- package/lib/components/teams/backdrops/catalogue.d.ts +35 -0
- package/lib/components/teams/backdrops/catalogue.js +34 -0
- package/lib/components/teams/backdrops/edges.d.ts +20 -0
- package/lib/components/teams/backdrops/edges.js +29 -0
- package/lib/components/teams/backdrops/faces.d.ts +24 -0
- package/lib/components/teams/backdrops/faces.js +39 -0
- package/lib/components/teams/backdrops/halo.d.ts +14 -0
- package/lib/components/teams/backdrops/halo.js +5 -0
- package/lib/components/teams/backdrops/hero.d.ts +14 -0
- package/lib/components/teams/backdrops/hero.js +178 -0
- package/lib/components/teams/backdrops/index.d.ts +22 -0
- package/lib/components/teams/backdrops/index.js +38 -0
- package/lib/components/teams/backdrops/nodes.d.ts +123 -0
- package/lib/components/teams/backdrops/nodes.js +265 -0
- package/lib/components/teams/backdrops/plain.d.ts +14 -0
- package/lib/components/teams/backdrops/plain.js +116 -0
- package/lib/components/teams/backdrops/studio.d.ts +17 -0
- package/lib/components/teams/backdrops/studio.js +122 -0
- package/lib/components/teams/backdrops/theme.d.ts +14 -0
- package/lib/components/teams/backdrops/theme.js +19 -0
- package/lib/components/teams/backdrops/types.d.ts +214 -0
- package/lib/components/teams/backdrops/types.js +5 -0
- package/lib/components/teams/index.d.ts +2 -0
- package/lib/components/teams/index.js +2 -0
- package/lib/components/teams/sceneTranscriptRules.d.ts +32 -1
- package/lib/components/teams/sceneTranscriptRules.js +104 -2
- package/lib/components/teams/transcript/RecordingLabel.d.ts +17 -0
- package/lib/components/teams/transcript/RecordingLabel.js +7 -0
- package/lib/components/teams/transcript/SequenceDiagram.d.ts +39 -0
- package/lib/components/teams/transcript/SequenceDiagram.js +190 -0
- package/lib/components/teams/transcript/TranscriptDetail.d.ts +27 -0
- package/lib/components/teams/transcript/TranscriptDetail.js +48 -0
- package/lib/components/teams/transcript/TranscriptTable.d.ts +23 -0
- package/lib/components/teams/transcript/TranscriptTable.js +112 -0
- package/lib/components/teams/transcript/TranscriptViews.d.ts +66 -0
- package/lib/components/teams/transcript/TranscriptViews.js +135 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.d.ts +13 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.js +5 -0
- package/lib/components/teams/transcript/index.d.ts +23 -0
- package/lib/components/teams/transcript/index.js +27 -0
- package/lib/components/teams/transcript/recordingPlayback.d.ts +52 -0
- package/lib/components/teams/transcript/recordingPlayback.js +146 -0
- package/lib/components/teams/transcript/selection.d.ts +59 -0
- package/lib/components/teams/transcript/selection.js +79 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.d.ts +35 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.js +158 -0
- package/lib/components/teams/transcript/transcriptDetailRules.d.ts +80 -0
- package/lib/components/teams/transcript/transcriptDetailRules.js +208 -0
- package/lib/components/teams/transcript/transcriptExport.d.ts +31 -0
- package/lib/components/teams/transcript/transcriptExport.js +82 -0
- package/lib/components/teams/transcript/transcriptRows.d.ts +76 -0
- package/lib/components/teams/transcript/transcriptRows.js +187 -0
- package/lib/components/teams/transcript/useSceneRecording.d.ts +45 -0
- package/lib/components/teams/transcript/useSceneRecording.js +96 -0
- package/lib/examples/ScenesExample.js +16 -3
- package/lib/specs/actions.js +2 -0
- package/lib/specs/apps.d.ts +1 -0
- package/lib/specs/apps.js +219 -0
- package/lib/specs/sceneRecordings.d.ts +14 -0
- package/lib/specs/sceneRecordings.js +12 -0
- package/lib/specs/sceneSchema.js +11 -0
- package/lib/specs/scenes.d.ts +1 -0
- package/lib/specs/scenes.js +159 -0
- package/lib/types/scenes.d.ts +54 -0
- package/package.json +2 -1
- 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'>;
|
|
@@ -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).
|
|
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;
|