@datalayer/agent-runtimes 1.3.86 → 1.3.87
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/lib/components/teams/A2ATeamGraph.d.ts +2 -2
- package/lib/components/teams/A2ATeamGraph.js +39 -22
- package/lib/specs/sceneSchema.d.ts +8 -0
- package/lib/specs/sceneSchema.js +852 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_envvars.py +1 -1
- package/scripts/codegen/generate_scenes.py +43 -1
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* values the rows, top to bottom.
|
|
21
21
|
*
|
|
22
22
|
* A member's connections — the MCP servers it reaches, Odoo for Accounting —
|
|
23
|
-
*
|
|
23
|
+
* stand to its right, each a node half a member's size with the server's mark
|
|
24
24
|
* (`SpecMark`), linked to it by an edge of its own. That edge is still until
|
|
25
25
|
* the member calls one of the connection's tools: then it flows toward the
|
|
26
26
|
* connection for as long as the call runs ({@link A2ATeamCall}), with the
|
|
@@ -59,7 +59,7 @@ export type A2ATeamGraphMember = {
|
|
|
59
59
|
onToggle?: () => void;
|
|
60
60
|
/** It was sent away, or called back. */
|
|
61
61
|
onAway?: (away: boolean) => void;
|
|
62
|
-
/** The MCP servers it reaches, drawn
|
|
62
|
+
/** The MCP servers it reaches, drawn to its right (`teamConnectionsOf`). */
|
|
63
63
|
connections?: A2ATeamConnection[];
|
|
64
64
|
/**
|
|
65
65
|
* How its balloon shows what it says (LOOP T-23): only what it says or
|
|
@@ -25,9 +25,15 @@ const WIDTH = NODE_WIDTH * 2 + GAP;
|
|
|
25
25
|
const ROW_GAP = 48;
|
|
26
26
|
/** A connection's node: half a member's. */
|
|
27
27
|
const CONNECTION_WIDTH = NODE_WIDTH / 2;
|
|
28
|
-
/** Between a member and its connections. */
|
|
28
|
+
/** Between a member and its connections, to its right. */
|
|
29
29
|
const CONNECTION_GAP = 44;
|
|
30
30
|
const CONNECTIONS_APART = 16;
|
|
31
|
+
/** Where a member's connections start: right of it. */
|
|
32
|
+
const CONNECTION_X = NODE_WIDTH + CONNECTION_GAP;
|
|
33
|
+
/** Below the character's middle, so the A2A edge leaving it passes above. */
|
|
34
|
+
const CONNECTION_DROP = 12;
|
|
35
|
+
/** What a member's connections take beside it. */
|
|
36
|
+
const CONNECTIONS_BESIDE = CONNECTION_GAP + CONNECTION_WIDTH;
|
|
31
37
|
/** Where the edge leaves and reaches a member: the middle of its character. */
|
|
32
38
|
const HANDLE = (size) => ({
|
|
33
39
|
opacity: 0,
|
|
@@ -118,7 +124,7 @@ const Moving = createContext({
|
|
|
118
124
|
move: () => undefined,
|
|
119
125
|
reset: () => undefined,
|
|
120
126
|
});
|
|
121
|
-
/** A connection's node id:
|
|
127
|
+
/** A connection's node id: beside its member. */
|
|
122
128
|
const connectionNodeId = (member, connection) => `${member}/${connection}`;
|
|
123
129
|
const MemberNode = memo(function MemberNode({ data, }) {
|
|
124
130
|
const { id, side, size } = data;
|
|
@@ -282,7 +288,7 @@ const MemberNode = memo(function MemberNode({ data, }) {
|
|
|
282
288
|
stayPut: true,
|
|
283
289
|
// Named as the member, not its character: `Sales (in your
|
|
284
290
|
// browser)`; the character describes it.
|
|
285
|
-
agentName: member.name, label: memberLabel(member) })) }), _jsxs(Text, { sx: { fontWeight: 600, textAlign: 'center' }, children: [member.emoji ? `${member.emoji} ` : '', member.name] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', textAlign: 'center' }, "data-member-where": "", children: member.where }), _jsx(Handle, { id: MEMBER_HANDLES.outRight, type: "source", position: Position.Right, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outLeft, type: "source", position: Position.Left, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outBottom, type: "source", position: Position.Bottom, style: { ...HANDLE(size), top: size }, isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outTop, type: "source", position: Position.Top, style: { ...HANDLE(size), top: 0 }, isConnectable: false }), member.connections?.length ? (_jsx(Handle, { id: MEMBER_HANDLES.connections, type: "source", position: Position.
|
|
291
|
+
agentName: member.name, label: memberLabel(member) })) }), _jsxs(Text, { sx: { fontWeight: 600, textAlign: 'center' }, children: [member.emoji ? `${member.emoji} ` : '', member.name] }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', textAlign: 'center' }, "data-member-where": "", children: member.where }), _jsx(Handle, { id: MEMBER_HANDLES.outRight, type: "source", position: Position.Right, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outLeft, type: "source", position: Position.Left, style: HANDLE(size), isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outBottom, type: "source", position: Position.Bottom, style: { ...HANDLE(size), top: size }, isConnectable: false }), _jsx(Handle, { id: MEMBER_HANDLES.outTop, type: "source", position: Position.Top, style: { ...HANDLE(size), top: 0 }, isConnectable: false }), member.connections?.length ? (_jsx(Handle, { id: MEMBER_HANDLES.connections, type: "source", position: Position.Right, style: { ...HANDLE(size), left: 'auto', right: 0 }, isConnectable: false })) : null] }));
|
|
286
292
|
});
|
|
287
293
|
/** A member's accessible name: its name, and where it runs. */
|
|
288
294
|
export function memberLabel(member) {
|
|
@@ -314,7 +320,7 @@ const ConnectionNode = memo(function ConnectionNode({ id, data, }) {
|
|
|
314
320
|
gap: '2px',
|
|
315
321
|
cursor: 'default',
|
|
316
322
|
pointerEvents: 'all',
|
|
317
|
-
}, title: connection.name, "data-team-connection": connection.id, "data-connection-busy": tool ? 'true' : 'false', children: [_jsx(Handle, { type: "target", position: Position.
|
|
323
|
+
}, title: connection.name, "data-team-connection": connection.id, "data-connection-busy": tool ? 'true' : 'false', children: [_jsx(Handle, { type: "target", position: Position.Left, style: { ...HANDLE(size), left: 0 }, isConnectable: false }), _jsx(Box, { as: "button", type: "button", className: tool ? 'a2a-team-connection-busy' : undefined, "aria-label": connectionLabel(connection, owner?.name), "aria-expanded": told, "aria-controls": detailsId, "data-connection-figure": "", onClick: () => setTold(shown => !shown), onKeyDown: (event) => {
|
|
318
324
|
if (event.key === 'Escape' && told) {
|
|
319
325
|
event.stopPropagation();
|
|
320
326
|
setTold(false);
|
|
@@ -419,11 +425,19 @@ export const CallEdge = memo(function CallEdge({ id, sourceX, sourceY, targetX,
|
|
|
419
425
|
});
|
|
420
426
|
const NODE_TYPES = { member: MemberNode, connection: ConnectionNode };
|
|
421
427
|
const EDGE_TYPES = { a2a: LinkEdge, mcp: CallEdge };
|
|
422
|
-
/** Where a member's connections sit:
|
|
423
|
-
function
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
/** Where a member's connections sit: right of it, one under another from its character's middle. */
|
|
429
|
+
function connectionsY(memberY, size, count, connectionHeight) {
|
|
430
|
+
const top = memberY + size / 2 + CONNECTION_DROP;
|
|
431
|
+
return Array.from({ length: count }, (_, at) => top + at * (connectionHeight + CONNECTIONS_APART));
|
|
432
|
+
}
|
|
433
|
+
/** How far down a member's connections reach, from its top; none, 0. */
|
|
434
|
+
function connectionsReach(size, count, connectionHeight) {
|
|
435
|
+
return count
|
|
436
|
+
? size / 2 +
|
|
437
|
+
CONNECTION_DROP +
|
|
438
|
+
count * connectionHeight +
|
|
439
|
+
(count - 1) * CONNECTIONS_APART
|
|
440
|
+
: 0;
|
|
427
441
|
}
|
|
428
442
|
/** What a member's call says, on its edge and to a screen reader. */
|
|
429
443
|
export function callWords(member, connection, tool) {
|
|
@@ -502,7 +516,11 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
502
516
|
// Where the members stand, as columns and rows, and the width that takes.
|
|
503
517
|
const peerIds = peers.map(one => one.id);
|
|
504
518
|
const placing = placeMembers(entry.id, peerIds, positions);
|
|
505
|
-
const
|
|
519
|
+
const hasConnections = members.some(member => member.connections?.length);
|
|
520
|
+
// The last column's connections sit beside it, inside the graph.
|
|
521
|
+
const drawn = Math.max(WIDTH, placing.columns * NODE_WIDTH +
|
|
522
|
+
(placing.columns - 1) * GAP +
|
|
523
|
+
(hasConnections ? CONNECTIONS_BESIDE : 0));
|
|
506
524
|
const viewport = useMemo(() => teamViewport(width || drawn, drawn), [width, drawn]);
|
|
507
525
|
// The members' height: the character, its name and where it runs.
|
|
508
526
|
const memberHeight = size + 56;
|
|
@@ -516,14 +534,13 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
516
534
|
connections: (member.connections ?? []).map(connection => connection.id),
|
|
517
535
|
}));
|
|
518
536
|
const placedKey = JSON.stringify(placed);
|
|
519
|
-
const hasConnections = placed.some(({ connections }) => connections.length);
|
|
520
537
|
// A notebook in a balloon: more room above the members.
|
|
521
538
|
const room = members.some(member => member.persona.notebook)
|
|
522
539
|
? Math.max(NOTEBOOK_BALLOON_ROOM, balloonRoom)
|
|
523
540
|
: balloonRoom;
|
|
524
|
-
//
|
|
525
|
-
const
|
|
526
|
-
const rowHeight = memberHeight
|
|
541
|
+
// Beside a member, its connections may reach below it: the row takes them.
|
|
542
|
+
const reach = Math.max(0, ...placed.map(({ connections }) => connectionsReach(size, connections.length, connectionHeight)));
|
|
543
|
+
const rowHeight = Math.max(memberHeight, reach);
|
|
527
544
|
const total = room + placing.rows * rowHeight + (placing.rows - 1) * ROW_GAP;
|
|
528
545
|
const height = Math.ceil(total * viewport.zoom);
|
|
529
546
|
// Where each member is laid out, before it is moved.
|
|
@@ -537,14 +554,14 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
537
554
|
// Where the members were moved, kept inside the graph's box.
|
|
538
555
|
const boundsOf = (id) => {
|
|
539
556
|
const layout = layoutOf(id);
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
557
|
+
const count = placed.find(({ member }) => member === id)?.connections.length ?? 0;
|
|
558
|
+
const beside = count ? CONNECTIONS_BESIDE : 0;
|
|
559
|
+
const tall = Math.max(memberHeight, connectionsReach(size, count, connectionHeight));
|
|
543
560
|
return {
|
|
544
561
|
minDx: -layout.x,
|
|
545
|
-
maxDx: drawn - NODE_WIDTH - layout.x,
|
|
562
|
+
maxDx: drawn - NODE_WIDTH - beside - layout.x,
|
|
546
563
|
minDy: -layout.y,
|
|
547
|
-
maxDy: total - layout.y -
|
|
564
|
+
maxDy: total - layout.y - tall,
|
|
548
565
|
};
|
|
549
566
|
};
|
|
550
567
|
const [moved, setMoved] = useState(() => Object.fromEntries(members.map(member => [
|
|
@@ -597,13 +614,13 @@ export function A2ATeamGraph({ entry, peer, peers: more = NO_PEERS, flow, flows,
|
|
|
597
614
|
},
|
|
598
615
|
// A member's connections move with it.
|
|
599
616
|
...connections.map((connection, at) => {
|
|
600
|
-
const
|
|
617
|
+
const ys = connectionsY(y, size, connections.length, connectionHeight);
|
|
601
618
|
return {
|
|
602
619
|
id: connectionNodeId(id, connection),
|
|
603
620
|
type: 'connection',
|
|
604
621
|
position: {
|
|
605
|
-
x:
|
|
606
|
-
y:
|
|
622
|
+
x: x + CONNECTION_X,
|
|
623
|
+
y: ys[at],
|
|
607
624
|
},
|
|
608
625
|
width: CONNECTION_WIDTH,
|
|
609
626
|
height: connectionHeight,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scene spec's JSON Schema (`loop.scene/v1`), generated from agentspecs by
|
|
3
|
+
* `scripts/codegen/generate_scenes.py`. Do not edit.
|
|
4
|
+
*
|
|
5
|
+
* @module specs/sceneSchema
|
|
6
|
+
*/
|
|
7
|
+
import type { JsonSchema } from './appspecSchema';
|
|
8
|
+
export declare const SCENE_SCHEMA: JsonSchema;
|
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
export const SCENE_SCHEMA = {
|
|
6
|
+
$defs: {
|
|
7
|
+
ActionClass: {
|
|
8
|
+
description: 'What a tool does to the world.',
|
|
9
|
+
enum: ['read', 'write', 'send', 'buy', 'delete', 'publish'],
|
|
10
|
+
title: 'ActionClass',
|
|
11
|
+
type: 'string',
|
|
12
|
+
},
|
|
13
|
+
AnswerKind: {
|
|
14
|
+
description: 'What kind of answer comes back, and what the page shows.',
|
|
15
|
+
enum: ['words', 'table', 'chart', 'notebook', 'map', 'file', 'image'],
|
|
16
|
+
title: 'AnswerKind',
|
|
17
|
+
type: 'string',
|
|
18
|
+
},
|
|
19
|
+
AppVerified: {
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
description: 'What was verified, and how, each in a sentence a person reads (LOOP E-14).\n\nAn example says it on its card and on its page: what was tried live,\nwhat runs on recorded data instead, and what is not verified yet. Said\nby whoever tried it; nothing here is computed.',
|
|
22
|
+
properties: {
|
|
23
|
+
live: {
|
|
24
|
+
description: 'What was tried live, where and when',
|
|
25
|
+
items: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
},
|
|
28
|
+
title: 'Live',
|
|
29
|
+
type: 'array',
|
|
30
|
+
},
|
|
31
|
+
recorded: {
|
|
32
|
+
description: 'What runs on recorded data, not on live calls',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
},
|
|
36
|
+
title: 'Recorded',
|
|
37
|
+
type: 'array',
|
|
38
|
+
},
|
|
39
|
+
unverified: {
|
|
40
|
+
description: 'What is not verified yet',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
},
|
|
44
|
+
title: 'Unverified',
|
|
45
|
+
type: 'array',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
title: 'AppVerified',
|
|
49
|
+
type: 'object',
|
|
50
|
+
},
|
|
51
|
+
Inspector: {
|
|
52
|
+
description: 'The inspectors a page may offer under a scene.',
|
|
53
|
+
enum: ['agent', 'tools', 'a2a', 'notebook', 'cost'],
|
|
54
|
+
title: 'Inspector',
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
57
|
+
Pace: {
|
|
58
|
+
description: 'How fast a beat plays for the audience.',
|
|
59
|
+
enum: ['quick', 'steady', 'slow'],
|
|
60
|
+
title: 'Pace',
|
|
61
|
+
type: 'string',
|
|
62
|
+
},
|
|
63
|
+
RehearsalBeat: {
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
description: "What a beat's transcript must look like.",
|
|
66
|
+
properties: {
|
|
67
|
+
beat: {
|
|
68
|
+
description: 'The beat, by id',
|
|
69
|
+
title: 'Beat',
|
|
70
|
+
type: 'string',
|
|
71
|
+
},
|
|
72
|
+
lines: {
|
|
73
|
+
description: 'The shape, in order: `Sales → Accounting`, `Accounting → Odoo: odoo_accounting_*`, `Accounting: a table`',
|
|
74
|
+
items: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
},
|
|
77
|
+
title: 'Lines',
|
|
78
|
+
type: 'array',
|
|
79
|
+
},
|
|
80
|
+
must_say: {
|
|
81
|
+
description: 'Words that must appear',
|
|
82
|
+
items: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
},
|
|
85
|
+
title: 'Must Say',
|
|
86
|
+
type: 'array',
|
|
87
|
+
},
|
|
88
|
+
must_not_say: {
|
|
89
|
+
description: 'Words that must not',
|
|
90
|
+
items: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
},
|
|
93
|
+
title: 'Must Not Say',
|
|
94
|
+
type: 'array',
|
|
95
|
+
},
|
|
96
|
+
within: {
|
|
97
|
+
default: '',
|
|
98
|
+
description: 'The time the beat may take',
|
|
99
|
+
pattern: '^(?:[1-9]\\d*(?:ms|s|m|h))?$',
|
|
100
|
+
title: 'Within',
|
|
101
|
+
type: 'string',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ['beat'],
|
|
105
|
+
title: 'RehearsalBeat',
|
|
106
|
+
type: 'object',
|
|
107
|
+
},
|
|
108
|
+
SceneAudience: {
|
|
109
|
+
additionalProperties: false,
|
|
110
|
+
description: 'Who may watch and ask, and what an ask may cost.',
|
|
111
|
+
properties: {
|
|
112
|
+
who: {
|
|
113
|
+
$ref: '#/$defs/Watchers',
|
|
114
|
+
default: 'signed-in',
|
|
115
|
+
description: '`visitors`, `signed-in`, or `nobody`',
|
|
116
|
+
},
|
|
117
|
+
ceiling_per_ask: {
|
|
118
|
+
default: 0,
|
|
119
|
+
description: "What one ask may cost, in USD; the site's when 0",
|
|
120
|
+
minimum: 0,
|
|
121
|
+
title: 'Ceiling Per Ask',
|
|
122
|
+
type: 'number',
|
|
123
|
+
},
|
|
124
|
+
asks_a_day: {
|
|
125
|
+
default: 0,
|
|
126
|
+
description: "How many asks a visitor gets a day; the site's when 0",
|
|
127
|
+
minimum: 0,
|
|
128
|
+
title: 'Asks A Day',
|
|
129
|
+
type: 'integer',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
title: 'SceneAudience',
|
|
133
|
+
type: 'object',
|
|
134
|
+
},
|
|
135
|
+
SceneBeat: {
|
|
136
|
+
additionalProperties: false,
|
|
137
|
+
description: 'One beat of the script.',
|
|
138
|
+
properties: {
|
|
139
|
+
id: {
|
|
140
|
+
description: 'Its id: what the rehearsal and a branch name',
|
|
141
|
+
title: 'Id',
|
|
142
|
+
type: 'string',
|
|
143
|
+
},
|
|
144
|
+
cue: {
|
|
145
|
+
$ref: '#/$defs/SceneCue',
|
|
146
|
+
description: 'What starts it',
|
|
147
|
+
},
|
|
148
|
+
narration: {
|
|
149
|
+
default: '',
|
|
150
|
+
description: 'One line for the audience, read in the transcript',
|
|
151
|
+
title: 'Narration',
|
|
152
|
+
type: 'string',
|
|
153
|
+
},
|
|
154
|
+
moves: {
|
|
155
|
+
description: 'Who asks whom over what, in order',
|
|
156
|
+
items: {
|
|
157
|
+
$ref: '#/$defs/SceneMove',
|
|
158
|
+
},
|
|
159
|
+
title: 'Moves',
|
|
160
|
+
type: 'array',
|
|
161
|
+
},
|
|
162
|
+
expect: {
|
|
163
|
+
description: 'What should happen, in words',
|
|
164
|
+
title: 'Expect',
|
|
165
|
+
type: 'string',
|
|
166
|
+
},
|
|
167
|
+
shows: {
|
|
168
|
+
description: 'What the page shows: the answers to the audience when unsaid',
|
|
169
|
+
items: {
|
|
170
|
+
$ref: '#/$defs/AnswerKind',
|
|
171
|
+
},
|
|
172
|
+
title: 'Shows',
|
|
173
|
+
type: 'array',
|
|
174
|
+
},
|
|
175
|
+
pace: {
|
|
176
|
+
anyOf: [
|
|
177
|
+
{
|
|
178
|
+
$ref: '#/$defs/Pace',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: 'null',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
default: null,
|
|
185
|
+
description: "Its pace; the stage's when unsaid",
|
|
186
|
+
},
|
|
187
|
+
branch: {
|
|
188
|
+
description: 'What it does instead, on a decision',
|
|
189
|
+
items: {
|
|
190
|
+
$ref: '#/$defs/SceneBranch',
|
|
191
|
+
},
|
|
192
|
+
title: 'Branch',
|
|
193
|
+
type: 'array',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ['id', 'cue', 'expect'],
|
|
197
|
+
title: 'SceneBeat',
|
|
198
|
+
type: 'object',
|
|
199
|
+
},
|
|
200
|
+
SceneBranch: {
|
|
201
|
+
additionalProperties: false,
|
|
202
|
+
description: 'What a beat does instead when a decision holds.',
|
|
203
|
+
properties: {
|
|
204
|
+
decision: {
|
|
205
|
+
description: 'When, in words: *the books hold no open invoice*',
|
|
206
|
+
title: 'Decision',
|
|
207
|
+
type: 'string',
|
|
208
|
+
},
|
|
209
|
+
expect: {
|
|
210
|
+
default: '',
|
|
211
|
+
description: 'What happens then, in words',
|
|
212
|
+
title: 'Expect',
|
|
213
|
+
type: 'string',
|
|
214
|
+
},
|
|
215
|
+
moves: {
|
|
216
|
+
description: 'The moves then, when they differ',
|
|
217
|
+
items: {
|
|
218
|
+
$ref: '#/$defs/SceneMove',
|
|
219
|
+
},
|
|
220
|
+
title: 'Moves',
|
|
221
|
+
type: 'array',
|
|
222
|
+
},
|
|
223
|
+
then: {
|
|
224
|
+
default: '',
|
|
225
|
+
description: 'The beat that follows, by id, when the script goes on elsewhere',
|
|
226
|
+
title: 'Then',
|
|
227
|
+
type: 'string',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
required: ['decision'],
|
|
231
|
+
title: 'SceneBranch',
|
|
232
|
+
type: 'object',
|
|
233
|
+
},
|
|
234
|
+
SceneCastMember: {
|
|
235
|
+
additionalProperties: false,
|
|
236
|
+
description: 'A member of the cast.\n\nOn a staged team, it names a member of the team (`member`) and says only\nhow it appears here (`persona`) and what it is for in this scene\n(`brief`). Written inline — a scene with no `team` — it is the member:\nwhat it is (`app`, `ref` or `server`), its `role`, where it `runs_in`\nand whom it `talks_to`, as a team member says them.',
|
|
237
|
+
properties: {
|
|
238
|
+
member: {
|
|
239
|
+
description: 'Its id: in the team, or in this cast',
|
|
240
|
+
title: 'Member',
|
|
241
|
+
type: 'string',
|
|
242
|
+
},
|
|
243
|
+
app: {
|
|
244
|
+
default: '',
|
|
245
|
+
description: 'Inline: the application it is, `id` or `id:version`',
|
|
246
|
+
title: 'App',
|
|
247
|
+
type: 'string',
|
|
248
|
+
},
|
|
249
|
+
ref: {
|
|
250
|
+
default: '',
|
|
251
|
+
description: 'Inline: the agent it is, `id` or `id:version`',
|
|
252
|
+
title: 'Ref',
|
|
253
|
+
type: 'string',
|
|
254
|
+
},
|
|
255
|
+
server: {
|
|
256
|
+
default: '',
|
|
257
|
+
description: 'Inline: the MCP server it is, a system of the scene',
|
|
258
|
+
title: 'Server',
|
|
259
|
+
type: 'string',
|
|
260
|
+
},
|
|
261
|
+
role: {
|
|
262
|
+
anyOf: [
|
|
263
|
+
{
|
|
264
|
+
$ref: '#/$defs/TeamRole',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: 'null',
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
default: null,
|
|
271
|
+
description: 'Inline: what it is for, structurally',
|
|
272
|
+
},
|
|
273
|
+
runs_in: {
|
|
274
|
+
anyOf: [
|
|
275
|
+
{
|
|
276
|
+
$ref: '#/$defs/TeamPlace',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: 'null',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
default: null,
|
|
283
|
+
description: 'Inline: `browser` or `runtime`',
|
|
284
|
+
},
|
|
285
|
+
talks_to: {
|
|
286
|
+
description: 'Inline: whom it asks, and over what',
|
|
287
|
+
items: {
|
|
288
|
+
$ref: '#/$defs/TeamLink',
|
|
289
|
+
},
|
|
290
|
+
title: 'Talks To',
|
|
291
|
+
type: 'array',
|
|
292
|
+
},
|
|
293
|
+
persona: {
|
|
294
|
+
$ref: '#/$defs/ScenePersona',
|
|
295
|
+
description: 'How it appears on stage',
|
|
296
|
+
},
|
|
297
|
+
brief: {
|
|
298
|
+
default: '',
|
|
299
|
+
description: "What it is for in this scene, over its application's instructions",
|
|
300
|
+
title: 'Brief',
|
|
301
|
+
type: 'string',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
required: ['member'],
|
|
305
|
+
title: 'SceneCastMember',
|
|
306
|
+
type: 'object',
|
|
307
|
+
},
|
|
308
|
+
SceneCue: {
|
|
309
|
+
additionalProperties: false,
|
|
310
|
+
description: 'What starts a beat: something the audience says, a schedule, or an event.',
|
|
311
|
+
properties: {
|
|
312
|
+
say: {
|
|
313
|
+
default: '',
|
|
314
|
+
description: "An opener the audience may say: the entry's suggestion on the page",
|
|
315
|
+
title: 'Say',
|
|
316
|
+
type: 'string',
|
|
317
|
+
},
|
|
318
|
+
schedule: {
|
|
319
|
+
default: '',
|
|
320
|
+
description: 'A schedule, when the beat starts by itself',
|
|
321
|
+
title: 'Schedule',
|
|
322
|
+
type: 'string',
|
|
323
|
+
},
|
|
324
|
+
event: {
|
|
325
|
+
default: '',
|
|
326
|
+
description: 'An event, when something happening starts it',
|
|
327
|
+
title: 'Event',
|
|
328
|
+
type: 'string',
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
title: 'SceneCue',
|
|
332
|
+
type: 'object',
|
|
333
|
+
},
|
|
334
|
+
SceneDeployment: {
|
|
335
|
+
additionalProperties: false,
|
|
336
|
+
description: 'Where the scene plays.',
|
|
337
|
+
properties: {
|
|
338
|
+
account: {
|
|
339
|
+
default: '',
|
|
340
|
+
description: 'Under which account the scene plays: `demo`',
|
|
341
|
+
title: 'Account',
|
|
342
|
+
type: 'string',
|
|
343
|
+
},
|
|
344
|
+
page: {
|
|
345
|
+
default: '',
|
|
346
|
+
description: "The page it plays on: the entry's address",
|
|
347
|
+
title: 'Page',
|
|
348
|
+
type: 'string',
|
|
349
|
+
},
|
|
350
|
+
addresses: {
|
|
351
|
+
additionalProperties: {
|
|
352
|
+
type: 'string',
|
|
353
|
+
},
|
|
354
|
+
description: 'For each member on a runtime, the variable its address is read from at build',
|
|
355
|
+
title: 'Addresses',
|
|
356
|
+
type: 'object',
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
title: 'SceneDeployment',
|
|
360
|
+
type: 'object',
|
|
361
|
+
},
|
|
362
|
+
SceneMove: {
|
|
363
|
+
additionalProperties: false,
|
|
364
|
+
description: 'One member asking another over a protocol, or answering the audience.',
|
|
365
|
+
properties: {
|
|
366
|
+
who: {
|
|
367
|
+
description: 'The member moving, by its id in the cast',
|
|
368
|
+
title: 'Who',
|
|
369
|
+
type: 'string',
|
|
370
|
+
},
|
|
371
|
+
asks: {
|
|
372
|
+
default: '',
|
|
373
|
+
description: 'Whom: a member or a system; nobody when it answers the audience',
|
|
374
|
+
title: 'Asks',
|
|
375
|
+
type: 'string',
|
|
376
|
+
},
|
|
377
|
+
over: {
|
|
378
|
+
anyOf: [
|
|
379
|
+
{
|
|
380
|
+
$ref: '#/$defs/TeamProtocol',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
type: 'null',
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
default: null,
|
|
387
|
+
description: '`a2a` to a member, `mcp` to a system',
|
|
388
|
+
},
|
|
389
|
+
what: {
|
|
390
|
+
default: '',
|
|
391
|
+
description: 'What it asks for, or what it answers, in words',
|
|
392
|
+
title: 'What',
|
|
393
|
+
type: 'string',
|
|
394
|
+
},
|
|
395
|
+
tool: {
|
|
396
|
+
default: '',
|
|
397
|
+
description: 'Over `mcp`: the tool, by name or pattern (`odoo_accounting_*`)',
|
|
398
|
+
title: 'Tool',
|
|
399
|
+
type: 'string',
|
|
400
|
+
},
|
|
401
|
+
does: {
|
|
402
|
+
anyOf: [
|
|
403
|
+
{
|
|
404
|
+
$ref: '#/$defs/ActionClass',
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
type: 'null',
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
default: null,
|
|
411
|
+
description: 'The kind of tool: `read`, `write`, …',
|
|
412
|
+
},
|
|
413
|
+
answers: {
|
|
414
|
+
anyOf: [
|
|
415
|
+
{
|
|
416
|
+
$ref: '#/$defs/AnswerKind',
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
type: 'null',
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
default: null,
|
|
423
|
+
description: 'What kind of answer comes back',
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
required: ['who'],
|
|
427
|
+
title: 'SceneMove',
|
|
428
|
+
type: 'object',
|
|
429
|
+
},
|
|
430
|
+
ScenePersona: {
|
|
431
|
+
additionalProperties: false,
|
|
432
|
+
description: 'How a member appears to the audience, in this scene.',
|
|
433
|
+
properties: {
|
|
434
|
+
name: {
|
|
435
|
+
default: '',
|
|
436
|
+
description: "The name the audience sees; its application's when unsaid",
|
|
437
|
+
title: 'Name',
|
|
438
|
+
type: 'string',
|
|
439
|
+
},
|
|
440
|
+
face: {
|
|
441
|
+
default: '',
|
|
442
|
+
description: "One emoji; its application's when unsaid",
|
|
443
|
+
title: 'Face',
|
|
444
|
+
type: 'string',
|
|
445
|
+
},
|
|
446
|
+
line: {
|
|
447
|
+
default: '',
|
|
448
|
+
description: 'One line of *who I am here*',
|
|
449
|
+
title: 'Line',
|
|
450
|
+
type: 'string',
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
title: 'ScenePersona',
|
|
454
|
+
type: 'object',
|
|
455
|
+
},
|
|
456
|
+
SceneRecording: {
|
|
457
|
+
additionalProperties: false,
|
|
458
|
+
description: 'A transcript recorded once, played when the scene cannot play live (LOOP H-08).',
|
|
459
|
+
properties: {
|
|
460
|
+
path: {
|
|
461
|
+
description: 'Its file, beside the scenes: `sales-and-accounting/recording.json`',
|
|
462
|
+
title: 'Path',
|
|
463
|
+
type: 'string',
|
|
464
|
+
},
|
|
465
|
+
taken: {
|
|
466
|
+
default: '',
|
|
467
|
+
description: 'When it was recorded',
|
|
468
|
+
title: 'Taken',
|
|
469
|
+
type: 'string',
|
|
470
|
+
},
|
|
471
|
+
note: {
|
|
472
|
+
default: '',
|
|
473
|
+
description: 'What the audience is told: *recorded on 2026-10-07*',
|
|
474
|
+
title: 'Note',
|
|
475
|
+
type: 'string',
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
required: ['path'],
|
|
479
|
+
title: 'SceneRecording',
|
|
480
|
+
type: 'object',
|
|
481
|
+
},
|
|
482
|
+
SceneRehearsal: {
|
|
483
|
+
additionalProperties: false,
|
|
484
|
+
description: "The scene's tests: a passing rehearsal is the gallery's *Live*.",
|
|
485
|
+
properties: {
|
|
486
|
+
beats: {
|
|
487
|
+
description: "Each beat's expected shape",
|
|
488
|
+
items: {
|
|
489
|
+
$ref: '#/$defs/RehearsalBeat',
|
|
490
|
+
},
|
|
491
|
+
title: 'Beats',
|
|
492
|
+
type: 'array',
|
|
493
|
+
},
|
|
494
|
+
within: {
|
|
495
|
+
default: '',
|
|
496
|
+
description: 'The time the whole scene may take',
|
|
497
|
+
pattern: '^(?:[1-9]\\d*(?:ms|s|m|h))?$',
|
|
498
|
+
title: 'Within',
|
|
499
|
+
type: 'string',
|
|
500
|
+
},
|
|
501
|
+
recording: {
|
|
502
|
+
anyOf: [
|
|
503
|
+
{
|
|
504
|
+
$ref: '#/$defs/SceneRecording',
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
type: 'null',
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
default: null,
|
|
511
|
+
description: 'A recording, for when it cannot play live',
|
|
512
|
+
},
|
|
513
|
+
verified: {
|
|
514
|
+
$ref: '#/$defs/AppVerified',
|
|
515
|
+
description: 'What was verified live, what runs on recorded data, and what is not verified yet',
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
title: 'SceneRehearsal',
|
|
519
|
+
type: 'object',
|
|
520
|
+
},
|
|
521
|
+
SceneSetting: {
|
|
522
|
+
additionalProperties: false,
|
|
523
|
+
description: 'The stage: what is on it, and what the audience is told.',
|
|
524
|
+
properties: {
|
|
525
|
+
systems: {
|
|
526
|
+
description: 'The systems the cast reaches',
|
|
527
|
+
items: {
|
|
528
|
+
$ref: '#/$defs/SceneSystem',
|
|
529
|
+
},
|
|
530
|
+
title: 'Systems',
|
|
531
|
+
type: 'array',
|
|
532
|
+
},
|
|
533
|
+
frames: {
|
|
534
|
+
description: "The Frames the scene plays under, over the team's",
|
|
535
|
+
items: {
|
|
536
|
+
type: 'string',
|
|
537
|
+
},
|
|
538
|
+
title: 'Frames',
|
|
539
|
+
type: 'array',
|
|
540
|
+
},
|
|
541
|
+
contents: {
|
|
542
|
+
description: 'The data in play: documents and datasets',
|
|
543
|
+
items: {
|
|
544
|
+
type: 'string',
|
|
545
|
+
},
|
|
546
|
+
title: 'Contents',
|
|
547
|
+
type: 'array',
|
|
548
|
+
},
|
|
549
|
+
period: {
|
|
550
|
+
default: '',
|
|
551
|
+
description: 'When the scene plays, in words: *March 2026*',
|
|
552
|
+
title: 'Period',
|
|
553
|
+
type: 'string',
|
|
554
|
+
},
|
|
555
|
+
language: {
|
|
556
|
+
default: '',
|
|
557
|
+
description: 'The language it plays in, `en`',
|
|
558
|
+
pattern: '^(?:[a-z]{2,3}(?:-[A-Z]{2})?)?$',
|
|
559
|
+
title: 'Language',
|
|
560
|
+
type: 'string',
|
|
561
|
+
},
|
|
562
|
+
assumes: {
|
|
563
|
+
default: '',
|
|
564
|
+
description: 'What the audience is told before it starts, one line',
|
|
565
|
+
title: 'Assumes',
|
|
566
|
+
type: 'string',
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
title: 'SceneSetting',
|
|
570
|
+
type: 'object',
|
|
571
|
+
},
|
|
572
|
+
SceneStage: {
|
|
573
|
+
additionalProperties: false,
|
|
574
|
+
description: 'Directions for the page.',
|
|
575
|
+
properties: {
|
|
576
|
+
positions: {
|
|
577
|
+
additionalProperties: {
|
|
578
|
+
$ref: '#/$defs/StagePosition',
|
|
579
|
+
},
|
|
580
|
+
description: 'Where each member stands',
|
|
581
|
+
title: 'Positions',
|
|
582
|
+
type: 'object',
|
|
583
|
+
},
|
|
584
|
+
opens_first: {
|
|
585
|
+
default: '',
|
|
586
|
+
description: "Whose balloon opens first; the entry's when unsaid",
|
|
587
|
+
title: 'Opens First',
|
|
588
|
+
type: 'string',
|
|
589
|
+
},
|
|
590
|
+
transcript: {
|
|
591
|
+
$ref: '#/$defs/SceneTranscript',
|
|
592
|
+
description: 'What the transcript shows',
|
|
593
|
+
},
|
|
594
|
+
inspectors: {
|
|
595
|
+
description: 'The inspectors offered',
|
|
596
|
+
items: {
|
|
597
|
+
$ref: '#/$defs/Inspector',
|
|
598
|
+
},
|
|
599
|
+
title: 'Inspectors',
|
|
600
|
+
type: 'array',
|
|
601
|
+
},
|
|
602
|
+
rests_after: {
|
|
603
|
+
default: '',
|
|
604
|
+
description: 'How long the scene plays before it rests',
|
|
605
|
+
pattern: '^(?:[1-9]\\d*(?:ms|s|m|h))?$',
|
|
606
|
+
title: 'Rests After',
|
|
607
|
+
type: 'string',
|
|
608
|
+
},
|
|
609
|
+
pace: {
|
|
610
|
+
$ref: '#/$defs/Pace',
|
|
611
|
+
default: 'steady',
|
|
612
|
+
description: 'The pace of the beats that say none',
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
title: 'SceneStage',
|
|
616
|
+
type: 'object',
|
|
617
|
+
},
|
|
618
|
+
SceneSystem: {
|
|
619
|
+
additionalProperties: false,
|
|
620
|
+
description: 'A system on stage: an MCP server of the catalogue the cast reaches.',
|
|
621
|
+
properties: {
|
|
622
|
+
server: {
|
|
623
|
+
description: 'An MCP server of the catalogue, `id` or `id:version`',
|
|
624
|
+
title: 'Server',
|
|
625
|
+
type: 'string',
|
|
626
|
+
},
|
|
627
|
+
as: {
|
|
628
|
+
default: '',
|
|
629
|
+
description: 'The name the audience reads on the graph and in the transcript',
|
|
630
|
+
title: 'As',
|
|
631
|
+
type: 'string',
|
|
632
|
+
},
|
|
633
|
+
holds: {
|
|
634
|
+
default: '',
|
|
635
|
+
description: "What it holds in this scene: *Datalayer's own books, March 2026*",
|
|
636
|
+
title: 'Holds',
|
|
637
|
+
type: 'string',
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
required: ['server'],
|
|
641
|
+
title: 'SceneSystem',
|
|
642
|
+
type: 'object',
|
|
643
|
+
},
|
|
644
|
+
SceneTranscript: {
|
|
645
|
+
additionalProperties: false,
|
|
646
|
+
description: 'What the transcript shows.',
|
|
647
|
+
properties: {
|
|
648
|
+
tools: {
|
|
649
|
+
default: true,
|
|
650
|
+
description: 'Whether tool lines are shown',
|
|
651
|
+
title: 'Tools',
|
|
652
|
+
type: 'boolean',
|
|
653
|
+
},
|
|
654
|
+
narration: {
|
|
655
|
+
default: true,
|
|
656
|
+
description: "Whether the beats' narration is read in it",
|
|
657
|
+
title: 'Narration',
|
|
658
|
+
type: 'boolean',
|
|
659
|
+
},
|
|
660
|
+
withhold: {
|
|
661
|
+
description: 'Words withheld: credentials, ids, …',
|
|
662
|
+
items: {
|
|
663
|
+
$ref: '#/$defs/Withheld',
|
|
664
|
+
},
|
|
665
|
+
title: 'Withhold',
|
|
666
|
+
type: 'array',
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
title: 'SceneTranscript',
|
|
670
|
+
type: 'object',
|
|
671
|
+
},
|
|
672
|
+
StagePosition: {
|
|
673
|
+
additionalProperties: false,
|
|
674
|
+
description: 'Where a member stands: fractions of the box, left to right and top to bottom.',
|
|
675
|
+
properties: {
|
|
676
|
+
x: {
|
|
677
|
+
maximum: 1,
|
|
678
|
+
minimum: 0,
|
|
679
|
+
title: 'X',
|
|
680
|
+
type: 'number',
|
|
681
|
+
},
|
|
682
|
+
y: {
|
|
683
|
+
maximum: 1,
|
|
684
|
+
minimum: 0,
|
|
685
|
+
title: 'Y',
|
|
686
|
+
type: 'number',
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
required: ['x', 'y'],
|
|
690
|
+
title: 'StagePosition',
|
|
691
|
+
type: 'object',
|
|
692
|
+
},
|
|
693
|
+
TeamLink: {
|
|
694
|
+
description: 'One member asking another, directly, over a protocol.\n\nDistinct from `depends_on`, which orders work: a link says who may ask\nwhom while the work runs, in either order and as often as it needs.',
|
|
695
|
+
properties: {
|
|
696
|
+
member: {
|
|
697
|
+
description: 'The member asked, by its id in this team',
|
|
698
|
+
title: 'Member',
|
|
699
|
+
type: 'string',
|
|
700
|
+
},
|
|
701
|
+
over: {
|
|
702
|
+
$ref: '#/$defs/TeamProtocol',
|
|
703
|
+
default: 'a2a',
|
|
704
|
+
description: 'The protocol it is asked over: `a2a` to an agent or an application, `mcp` to a server',
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
required: ['member'],
|
|
708
|
+
title: 'TeamLink',
|
|
709
|
+
type: 'object',
|
|
710
|
+
},
|
|
711
|
+
TeamPlace: {
|
|
712
|
+
description: "Where a member's loop turns.\n\nA team of applications can be split between the person's page and a\nruntime: what reads the person's screen and talks to them turns in their\nbrowser, what reaches a company's systems turns on a runtime, where its\nconnections and its credentials are.",
|
|
713
|
+
enum: ['browser', 'runtime'],
|
|
714
|
+
title: 'TeamPlace',
|
|
715
|
+
type: 'string',
|
|
716
|
+
},
|
|
717
|
+
TeamProtocol: {
|
|
718
|
+
description: 'How one member talks to another.',
|
|
719
|
+
enum: ['a2a', 'mcp'],
|
|
720
|
+
title: 'TeamProtocol',
|
|
721
|
+
type: 'string',
|
|
722
|
+
},
|
|
723
|
+
TeamRole: {
|
|
724
|
+
description: 'What a member is *for*, structurally.\n\nDeliberately about position in the work rather than about subject matter: a\nteam of analysts and a team of reviewers have the same shape, and a runtime\nscheduling them cares which member opens the work and which closes it, not\nwhat the work is about.',
|
|
725
|
+
enum: [
|
|
726
|
+
'coordinator',
|
|
727
|
+
'initiator',
|
|
728
|
+
'contributor',
|
|
729
|
+
'reviewer',
|
|
730
|
+
'finalizer',
|
|
731
|
+
],
|
|
732
|
+
title: 'TeamRole',
|
|
733
|
+
type: 'string',
|
|
734
|
+
},
|
|
735
|
+
Watchers: {
|
|
736
|
+
description: 'Who may watch and ask.',
|
|
737
|
+
enum: ['visitors', 'signed-in', 'nobody'],
|
|
738
|
+
title: 'Watchers',
|
|
739
|
+
type: 'string',
|
|
740
|
+
},
|
|
741
|
+
Withheld: {
|
|
742
|
+
description: 'What the transcript does not show.',
|
|
743
|
+
enum: ['credentials', 'ids', 'addresses', 'amounts'],
|
|
744
|
+
title: 'Withheld',
|
|
745
|
+
type: 'string',
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
additionalProperties: false,
|
|
749
|
+
description: 'Specification for a scene: a team, staged.',
|
|
750
|
+
properties: {
|
|
751
|
+
schema: {
|
|
752
|
+
description: 'The version of the spec itself',
|
|
753
|
+
title: 'Schema',
|
|
754
|
+
const: 'loop.scene/v1',
|
|
755
|
+
default: 'loop.scene/v1',
|
|
756
|
+
},
|
|
757
|
+
id: {
|
|
758
|
+
description: 'Unique scene identifier',
|
|
759
|
+
title: 'Id',
|
|
760
|
+
type: 'string',
|
|
761
|
+
},
|
|
762
|
+
version: {
|
|
763
|
+
default: '0.0.1',
|
|
764
|
+
description: 'Scene version',
|
|
765
|
+
title: 'Version',
|
|
766
|
+
type: 'string',
|
|
767
|
+
},
|
|
768
|
+
name: {
|
|
769
|
+
description: "Display name: the tab's",
|
|
770
|
+
title: 'Name',
|
|
771
|
+
type: 'string',
|
|
772
|
+
},
|
|
773
|
+
description: {
|
|
774
|
+
default: '',
|
|
775
|
+
description: 'What happens in it, in a sentence',
|
|
776
|
+
title: 'Description',
|
|
777
|
+
type: 'string',
|
|
778
|
+
},
|
|
779
|
+
tags: {
|
|
780
|
+
items: {
|
|
781
|
+
type: 'string',
|
|
782
|
+
},
|
|
783
|
+
title: 'Tags',
|
|
784
|
+
type: 'array',
|
|
785
|
+
},
|
|
786
|
+
icon: {
|
|
787
|
+
default: 'people',
|
|
788
|
+
description: 'Icon identifier',
|
|
789
|
+
title: 'Icon',
|
|
790
|
+
type: 'string',
|
|
791
|
+
},
|
|
792
|
+
emoji: {
|
|
793
|
+
default: '👀',
|
|
794
|
+
description: 'Its face: one emoji, drawn before its name',
|
|
795
|
+
title: 'Emoji',
|
|
796
|
+
type: 'string',
|
|
797
|
+
},
|
|
798
|
+
team: {
|
|
799
|
+
default: '',
|
|
800
|
+
description: 'The team it stages, `id` or `id:version`',
|
|
801
|
+
title: 'Team',
|
|
802
|
+
type: 'string',
|
|
803
|
+
},
|
|
804
|
+
entry: {
|
|
805
|
+
default: '',
|
|
806
|
+
description: "The member the audience talks to; the team's entry when unsaid",
|
|
807
|
+
title: 'Entry',
|
|
808
|
+
type: 'string',
|
|
809
|
+
},
|
|
810
|
+
cast: {
|
|
811
|
+
description: 'The cast: personas and briefs, or the members',
|
|
812
|
+
items: {
|
|
813
|
+
$ref: '#/$defs/SceneCastMember',
|
|
814
|
+
},
|
|
815
|
+
title: 'Cast',
|
|
816
|
+
type: 'array',
|
|
817
|
+
},
|
|
818
|
+
setting: {
|
|
819
|
+
$ref: '#/$defs/SceneSetting',
|
|
820
|
+
description: 'The stage',
|
|
821
|
+
},
|
|
822
|
+
script: {
|
|
823
|
+
description: 'The beats, in order',
|
|
824
|
+
items: {
|
|
825
|
+
$ref: '#/$defs/SceneBeat',
|
|
826
|
+
},
|
|
827
|
+
title: 'Script',
|
|
828
|
+
type: 'array',
|
|
829
|
+
},
|
|
830
|
+
stage: {
|
|
831
|
+
$ref: '#/$defs/SceneStage',
|
|
832
|
+
description: 'Directions for the page',
|
|
833
|
+
},
|
|
834
|
+
audience: {
|
|
835
|
+
$ref: '#/$defs/SceneAudience',
|
|
836
|
+
description: 'Who may watch and ask',
|
|
837
|
+
},
|
|
838
|
+
rehearsal: {
|
|
839
|
+
$ref: '#/$defs/SceneRehearsal',
|
|
840
|
+
description: 'Its tests',
|
|
841
|
+
},
|
|
842
|
+
deployment: {
|
|
843
|
+
$ref: '#/$defs/SceneDeployment',
|
|
844
|
+
description: 'Where it plays',
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
required: ['id', 'name'],
|
|
848
|
+
title: 'Scene spec',
|
|
849
|
+
type: 'object',
|
|
850
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
851
|
+
$id: 'https://agentspecs.datalayer.tech/schemas/loop.scene/v1.json',
|
|
852
|
+
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ Generate Python and TypeScript code from YAML environment variable specification
|
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
9
|
python generate_envvars.py \\
|
|
10
|
-
--specs-dir specs/
|
|
10
|
+
--specs-dir specs/env-vars \\
|
|
11
11
|
--python-output agent_runtimes/config/envvars.py \\
|
|
12
12
|
--typescript-output src/config/envvars.ts
|
|
13
13
|
"""
|
|
@@ -16,6 +16,11 @@ filled from its application (``cast_of``) — its ``entry`` said, what each
|
|
|
16
16
|
beat ``shows`` said, and what it names that is not enabled (``setup``).
|
|
17
17
|
Empty optional lists are dropped, as ``generate_apps.py`` drops them.
|
|
18
18
|
|
|
19
|
+
The scene spec's JSON Schema is written beside the TypeScript catalogue as
|
|
20
|
+
``sceneSchema.ts`` (``agentspecs.scenes.json_schema``), the way
|
|
21
|
+
``generate_apps.py`` writes ``appspecSchema.ts``: what an editor of the text
|
|
22
|
+
completes and explains is what the spec accepts (LOOP S-01, A-03).
|
|
23
|
+
|
|
19
24
|
Usage:
|
|
20
25
|
python generate_scenes.py \\
|
|
21
26
|
--specs-dir agentspecs/agentspecs/scenes \\
|
|
@@ -273,8 +278,41 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
|
|
|
273
278
|
return "\n".join(lines)
|
|
274
279
|
|
|
275
280
|
|
|
281
|
+
def generate_schema_typescript_code(specs_dir: Path) -> str:
|
|
282
|
+
"""The scene spec's JSON Schema, for the editor of its text (LOOP S-01, A-03).
|
|
283
|
+
|
|
284
|
+
Taken from the agentspecs the catalogue is generated from, so that what
|
|
285
|
+
the editor completes and explains is what the spec accepts. The
|
|
286
|
+
``JsonSchema`` type is ``appspecSchema.ts``'s.
|
|
287
|
+
"""
|
|
288
|
+
scenes_module = import_from_clone(specs_dir, "scenes")
|
|
289
|
+
schema = json.dumps(
|
|
290
|
+
scenes_module.json_schema(), indent=2, ensure_ascii=False, sort_keys=False
|
|
291
|
+
)
|
|
292
|
+
return "\n".join(
|
|
293
|
+
[
|
|
294
|
+
"/*",
|
|
295
|
+
" * Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
296
|
+
" * Distributed under the terms of the Modified BSD License.",
|
|
297
|
+
" */",
|
|
298
|
+
"",
|
|
299
|
+
"/**",
|
|
300
|
+
" * The scene spec's JSON Schema (`loop.scene/v1`), generated from agentspecs by",
|
|
301
|
+
" * `scripts/codegen/generate_scenes.py`. Do not edit.",
|
|
302
|
+
" *",
|
|
303
|
+
" * @module specs/sceneSchema",
|
|
304
|
+
" */",
|
|
305
|
+
"",
|
|
306
|
+
"import type { JsonSchema } from './appspecSchema';",
|
|
307
|
+
"",
|
|
308
|
+
f"export const SCENE_SCHEMA: JsonSchema = {schema};",
|
|
309
|
+
"",
|
|
310
|
+
]
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
276
314
|
def main() -> None:
|
|
277
|
-
"""Generate the scene catalogue."""
|
|
315
|
+
"""Generate the scene catalogue, and the scene spec's JSON Schema."""
|
|
278
316
|
parser = argparse.ArgumentParser(
|
|
279
317
|
description="Generate Python and TypeScript code from YAML scene specifications"
|
|
280
318
|
)
|
|
@@ -290,6 +328,10 @@ def main() -> None:
|
|
|
290
328
|
for path, text in (
|
|
291
329
|
(args.python_output, generate_python_code(specs)),
|
|
292
330
|
(args.typescript_output, generate_typescript_code(specs)),
|
|
331
|
+
(
|
|
332
|
+
args.typescript_output.with_name("sceneSchema.ts"),
|
|
333
|
+
generate_schema_typescript_code(args.specs_dir),
|
|
334
|
+
),
|
|
293
335
|
):
|
|
294
336
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
295
337
|
path.write_text(text)
|