@theoremjs/studio 0.3.4-next.474.gdb1d5d7 → 0.3.4-next.482.g7e608f2
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 +1 -1
- package/draft.js +1 -1
- package/package.json +4 -4
- package/page/assets/{Banner-TdHtbVjU.js → Banner-CrUhb26_.js} +2 -2
- package/page/assets/{ConsoleFrame-DgjR7mxh.js → ConsoleFrame-6cEEvO4i.js} +1 -1
- package/page/assets/{ContextMenu-D-6K0Fs2.js → ContextMenu-WnsgC14_.js} +2 -2
- package/page/assets/{ToolResultCharts-0ddlwgFH.js → ToolResultCharts-BEhuo0Z4.js} +1 -1
- package/page/assets/{TraceInspector-egLHbVIj.js → TraceInspector-k1w1q0XO.js} +1 -1
- package/page/assets/{TraceOverview-CRTN7VoT.js → TraceOverview-Crxd-sTS.js} +1 -1
- package/page/assets/{_virtual_studio-type-sources-DdSjKnMh.js → _virtual_studio-type-sources-DAabvYbE.js} +16 -6
- package/page/assets/{browser-engine-tXDx3yU4.js → browser-engine-C78edv3x.js} +7 -7
- package/page/assets/{browser-transport-ffGDz5MD.js → browser-transport-mGxdvtBL.js} +4 -4
- package/page/assets/{crypto-C5mfRMPv.js → crypto-iT_LZ8Et.js} +1 -1
- package/page/assets/encode-files-8AFMw7Er.js +1 -0
- package/page/assets/{export-agent-Y58EdieU.js → export-agent-BedkRp13.js} +1 -1
- package/page/assets/{guardrail-tester-CDawxhDM.js → guardrail-tester-XwnZWNsg.js} +1 -1
- package/page/assets/{index-DtLCmecg.js → index-BVHT-9mR.js} +9 -9
- package/page/assets/{live-messages-CcUq850L.js → live-messages-B65rUlNM.js} +1 -1
- package/page/assets/{reader-DkhgU7-Z.js → reader-XebgyCzF.js} +4 -4
- package/page/assets/{shaped-data-Hq2l-bCM.js → shaped-data-Bc6u4pow.js} +2 -2
- package/page/assets/{stub-9noS56tk.js → stub-DG5wxM-A.js} +1 -1
- package/page/assets/{studio-code-pane-CTGeGFc3.js → studio-code-pane-p4fjtQhL.js} +3 -3
- package/page/assets/{studio-console-bsXe3dk3.js → studio-console-BtZY4aUM.js} +1 -1
- package/page/assets/{studio-decision-CEcqOCMb.js → studio-decision-BovTBIwV.js} +1 -1
- package/page/assets/{studio-embedding-C1KeE3S4.js → studio-embedding-BY6UQk0k.js} +1 -1
- package/page/assets/{studio-host-console-mg-gOPJs.js → studio-host-console-D9NFWXxV.js} +2 -2
- package/page/assets/{studio-labels-DyUKoK3A.js → studio-labels-C3yQoEd0.js} +1 -1
- package/page/assets/{studio-live-D4qKxLHi.js → studio-live-B_cVDiqs.js} +1 -1
- package/page/assets/{studio-map-CsQoRSdx.js → studio-map-iN6-MOzK.js} +1 -1
- package/page/assets/studio-trace-replay-B3k21TSg.js +2 -0
- package/page/assets/{studio-type-checker-Ch0YO2TO.js → studio-type-checker-Bljo3amv.js} +1 -1
- package/page/assets/{tool-schema-DUAd6AXZ.js → tool-schema-BmScISma.js} +8 -8
- package/page/assets/{trace-record-Dfx7WS9n.js → trace-record-D4xAUHLZ.js} +1 -1
- package/page/assets/{tree-q4zfZBm3.js → tree-BiZScKs7.js} +2 -2
- package/page/assets/{turn-client-BHueOGtF.js → turn-client-CBWNoFgo.js} +2 -2
- package/page/assets/types-zRlFhC9U.js +1 -0
- package/page/assets/{use-one-call-BF7eEAPL.js → use-one-call-rh-5EjBB.js} +1 -1
- package/page/assets/{workspace-D8wB8WWL.js → workspace-kAEf4Zn4.js} +1 -1
- package/page/index.html +15 -15
- package/policy.js +2 -2
- package/replay.js +2 -3
- package/server/draft-decide.d.ts +4 -1
- package/server/draft-decide.js +1 -1
- package/ui/lib/tree-map-layout.d.ts +38 -14
- package/ui/lib/tree-map-layout.js +210 -142
- package/ui/tree-map.d.ts +10 -9
- package/ui/tree-map.js +60 -40
- package/page/assets/encode-files-CZXrfYAp.js +0 -1
- package/page/assets/studio-trace-replay-aP7TzJuO.js +0 -2
- package/page/assets/types-CbxtWiCN.js +0 -1
|
@@ -7,27 +7,24 @@
|
|
|
7
7
|
* stands where it is.
|
|
8
8
|
*/
|
|
9
9
|
import { workspaceTree } from "../../mod.js";
|
|
10
|
-
/** A row's height closed, and
|
|
10
|
+
/** A row's height closed; grown, the least and the most it stretches to, to fill the box it fits. */
|
|
11
11
|
const ROW = 32;
|
|
12
12
|
const GROWN_ROW = 28;
|
|
13
|
+
const TALLEST_ROW = 40;
|
|
13
14
|
/** How far in each level of the tree starts. */
|
|
14
15
|
export const INDENT = 28;
|
|
15
16
|
/** A token's half height. */
|
|
16
17
|
export const HALF = 11;
|
|
17
18
|
export const PAD = 16;
|
|
18
|
-
/**
|
|
19
|
+
/** Closed, between the tree's widest row and the feeds; grown, the least and most between columns. */
|
|
19
20
|
const GAP = 72;
|
|
20
|
-
|
|
21
|
-
const
|
|
21
|
+
const LEAST_GAP = 56;
|
|
22
|
+
const MOST_GAP = 160;
|
|
23
|
+
/** The tools beside a Tools row: the first of each type. */
|
|
22
24
|
const TOOL_TYPES = ['function', 'http', 'mcp', 'agent'];
|
|
23
|
-
/** Grown,
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const STEPS_ACROSS = 3;
|
|
27
|
-
const STEP_SLOT = 160;
|
|
28
|
-
/** Grown, the registry's tools in columns this tall. */
|
|
29
|
-
const TOOLS_DOWN = 8;
|
|
30
|
-
const TOOL_SLOT = 210;
|
|
25
|
+
/** Grown, the runner's steps two across, down one column and then the next. */
|
|
26
|
+
const STEPS_DOWN = 4;
|
|
27
|
+
const STEP_GAP = 24;
|
|
31
28
|
/** By how far grown every node has reached its zone; the rest is for drawing in. */
|
|
32
29
|
const MOVE_TO = 0.7;
|
|
33
30
|
/** Over what stretch of growing the added nodes draw, and how long each takes of it. */
|
|
@@ -38,25 +35,33 @@ const DRAW_SPAN = 0.1;
|
|
|
38
35
|
const SAMPLES = 100;
|
|
39
36
|
/** Space kept clear round a token. */
|
|
40
37
|
const CLEAR = 2;
|
|
41
|
-
export const
|
|
38
|
+
export const RUNNER = 'runner';
|
|
39
|
+
export const TOOL_RUNNER = 'tool-runner';
|
|
42
40
|
/**
|
|
43
|
-
* What
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* What the runner does in a turn, in order: the steps of the kernel contract's turn lifecycle
|
|
42
|
+
* (docs/contracts/kernel.md, "Turn lifecycle") that do work.
|
|
43
|
+
* why: The canary bind runs only while a canary detector is on, and pre_turn, before_end and
|
|
44
|
+
* post_turn are hooks for the host's stages, so the map leaves those four out.
|
|
46
45
|
*/
|
|
47
46
|
export const TURN_STEPS = [
|
|
48
47
|
{ id: 'resolve', label: 'Resolve' },
|
|
49
48
|
{ id: 'sanitize', label: 'Sanitize' },
|
|
50
49
|
{ id: 'compaction', label: 'Compaction' },
|
|
51
|
-
{ id: 'canary', label: 'Canary bind' },
|
|
52
|
-
{ id: 'pre_turn', label: 'pre_turn' },
|
|
53
50
|
{ id: 'stream', label: 'Provider stream' },
|
|
54
51
|
{ id: 'tools', label: 'Tool loop' },
|
|
55
|
-
{ id: 'before_end', label: 'before_end' },
|
|
56
52
|
{ id: 'validation', label: 'Validation' },
|
|
57
53
|
{ id: 'egress', label: 'Egress' },
|
|
58
54
|
{ id: 'trace', label: 'Trace' },
|
|
59
|
-
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* What the tool runner checks of every call, in order (`executeRegisteredTool`): its permission,
|
|
58
|
+
* its sign-in, the network a remote call reaches, and the guard on its result.
|
|
59
|
+
*/
|
|
60
|
+
export const TOOL_CHECKS = [
|
|
61
|
+
{ id: 'permission', label: 'Permission gate' },
|
|
62
|
+
{ id: 'sign-in', label: 'Sign-in' },
|
|
63
|
+
{ id: 'network', label: 'Network check' },
|
|
64
|
+
{ id: 'result', label: 'Result guard' },
|
|
60
65
|
];
|
|
61
66
|
/** A row's token id: the tree's ids repeat from one agent to the next. */
|
|
62
67
|
export function rowId(row) {
|
|
@@ -84,144 +89,195 @@ function rowsOf(workspace, only) {
|
|
|
84
89
|
return rows;
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (type === 'decision')
|
|
94
|
-
return 'Decision runner';
|
|
95
|
-
if (type === 'live')
|
|
96
|
-
return 'Session runner';
|
|
97
|
-
if (type === 'embedding')
|
|
98
|
-
return 'Embedding runner';
|
|
99
|
-
return 'Turn runner';
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Each agent's runner (`runTurn`, or `runDecision` for a decision), beside its name; its providers,
|
|
103
|
-
* beside its models; and the workspace's tool registry, beside its Tools row, with the tool runner
|
|
104
|
-
* under it. A feed sits level with the middle of the rows it joins, or under the feed before it when
|
|
105
|
-
* that is lower. With `detail`, every tool and each turn runner's steps.
|
|
92
|
+
* Beside the tree, shared by every agent drawn: the providers, each with the protocols the models
|
|
93
|
+
* it serves speak, and a line into each of those models; the first tool of each type the agents
|
|
94
|
+
* allow, with a line into each Tools row that allows it, an MCP tool's sessions under it and an
|
|
95
|
+
* agent tool's agent under it, joined to the runner; and the one runner, with a line into each
|
|
96
|
+
* agent but a host (whose calls the tool runner runs) and its steps under it. Past them, the tool
|
|
97
|
+
* runner the tool loop calls, its checks under it.
|
|
106
98
|
*/
|
|
107
|
-
function feedsOf(workspace, rows
|
|
99
|
+
function feedsOf(workspace, rows) {
|
|
100
|
+
const drawn = workspace.agents.filter((agent) => rows.some((row) => row.agent === agent.key));
|
|
101
|
+
const rowOf = (agent, facet, key) => rows.find((row) => row.agent === agent &&
|
|
102
|
+
row.node.ref.facet === facet &&
|
|
103
|
+
(key === undefined || ('key' in row.node.ref && row.node.ref.key === key)));
|
|
108
104
|
const feeds = [];
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
const middle = into.length ? into.reduce((sum, row) => sum + row.y, 0) / into.length : below;
|
|
112
|
-
feeds.push({ ...feed, y: Math.max(middle, below), into: into.map(rowId), depth });
|
|
113
|
-
};
|
|
114
|
-
for (const agent of workspace.agents) {
|
|
115
|
-
const mine = rows.filter((row) => row.agent === agent.key);
|
|
116
|
-
const name = mine.find((row) => row.node.ref.facet === 'identity');
|
|
117
|
-
if (!name)
|
|
118
|
-
continue;
|
|
119
|
-
const runner = runnerLabel(agent.identity.profileType);
|
|
120
|
-
if (runner) {
|
|
121
|
-
const id = `${agent.key}/runner`;
|
|
122
|
-
add({ id, label: runner, kind: 'runner', agent: agent.key }, [name]);
|
|
123
|
-
const steps = detail && runner === 'Turn runner' ? TURN_STEPS : [];
|
|
124
|
-
for (const step of steps) {
|
|
125
|
-
const of = step.id;
|
|
126
|
-
add({ id: `${id}/${of}`, label: step.label, kind: 'step', agent: agent.key, of, under: id }, [], 1);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const providers = new Map();
|
|
105
|
+
const providers = new Map();
|
|
106
|
+
for (const agent of drawn) {
|
|
130
107
|
for (const binding of agent.modelBindings) {
|
|
131
|
-
const row =
|
|
132
|
-
if (row)
|
|
133
|
-
|
|
108
|
+
const row = rowOf(agent.key, 'modelBinding', binding.key);
|
|
109
|
+
if (!row)
|
|
110
|
+
continue;
|
|
111
|
+
const provider = providers.get(binding.provider) ?? { into: [], protocols: new Set() };
|
|
112
|
+
provider.into.push(rowId(row));
|
|
113
|
+
provider.protocols.add(binding.protocol);
|
|
114
|
+
providers.set(binding.provider, provider);
|
|
134
115
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
}
|
|
117
|
+
for (const [provider, { into, protocols }] of providers) {
|
|
118
|
+
const id = `provider:${provider}`;
|
|
119
|
+
feeds.push({ id, label: provider, kind: 'provider', into, column: 1, group: 'providers' });
|
|
120
|
+
for (const protocol of protocols) {
|
|
121
|
+
const nested = { id: `${id}/${protocol}`, label: protocol, kind: 'protocol' };
|
|
122
|
+
feeds.push({ ...nested, into: [], under: id, column: 1, group: 'providers' });
|
|
138
123
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
124
|
+
}
|
|
125
|
+
const allowing = (key) => drawn.flatMap((agent) => {
|
|
126
|
+
const row = agent.tools.allow.includes(key) ? rowOf(agent.key, 'tools') : undefined;
|
|
127
|
+
return row ? [rowId(row)] : [];
|
|
128
|
+
});
|
|
129
|
+
for (const type of TOOL_TYPES) {
|
|
130
|
+
const tool = workspace.toolSpecs.find((each) => each.toolType === type && allowing(each.key).length > 0);
|
|
131
|
+
if (!tool)
|
|
132
|
+
continue;
|
|
133
|
+
const id = `tool:${tool.key}`;
|
|
134
|
+
const into = allowing(tool.key);
|
|
135
|
+
feeds.push({
|
|
136
|
+
id,
|
|
137
|
+
label: tool.toolName,
|
|
138
|
+
kind: 'tool',
|
|
139
|
+
of: type,
|
|
140
|
+
into,
|
|
141
|
+
column: 1,
|
|
142
|
+
group: 'tools',
|
|
143
|
+
});
|
|
144
|
+
const nested = { into: [], under: id, column: 1, group: 'tools' };
|
|
145
|
+
if (type === 'mcp') {
|
|
146
|
+
feeds.push({ ...nested, id: `${id}/sessions`, label: 'MCP sessions', kind: 'branch' });
|
|
147
|
+
}
|
|
148
|
+
const agent = workspace.agents.find((each) => each.key === tool.agentKey);
|
|
149
|
+
if (type === 'agent' && agent) {
|
|
150
|
+
const label = agent.identity.agentId || agent.identity.handle;
|
|
151
|
+
feeds.push({
|
|
152
|
+
...nested,
|
|
153
|
+
id: `${id}/agent`,
|
|
154
|
+
label,
|
|
155
|
+
kind: 'subagent',
|
|
156
|
+
of: agent.identity.profileType,
|
|
157
|
+
joins: RUNNER,
|
|
158
|
+
});
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
* them, or with `detail` every one.
|
|
161
|
-
*/
|
|
162
|
-
function listedTools(tools, detail) {
|
|
163
|
-
const firsts = TOOL_TYPES.flatMap((type) => {
|
|
164
|
-
const tool = tools.find((each) => each.toolType === type);
|
|
165
|
-
return tool ? [tool] : [];
|
|
161
|
+
const runs = drawn.filter((agent) => agent.identity.profileType !== 'host');
|
|
162
|
+
const into = runs.flatMap((agent) => {
|
|
163
|
+
const row = rowOf(agent.key, 'identity');
|
|
164
|
+
return row ? [rowId(row)] : [];
|
|
166
165
|
});
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
166
|
+
feeds.push({ id: RUNNER, label: 'Runner', kind: 'runner', into, column: 1, group: 'runner' });
|
|
167
|
+
for (const step of TURN_STEPS) {
|
|
168
|
+
feeds.push({
|
|
169
|
+
id: `${RUNNER}/${step.id}`,
|
|
170
|
+
label: step.label,
|
|
171
|
+
kind: 'step',
|
|
172
|
+
of: step.id,
|
|
173
|
+
into: [],
|
|
174
|
+
under: RUNNER,
|
|
175
|
+
column: 1,
|
|
176
|
+
group: 'runner',
|
|
177
|
+
...(step.id === 'tools' ? { joins: TOOL_RUNNER } : {}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const runner = { id: TOOL_RUNNER, label: 'Tool runner', kind: 'tool-runner' };
|
|
181
|
+
feeds.push({ ...runner, into: [], column: 2, group: 'tool-runner' });
|
|
182
|
+
for (const check of TOOL_CHECKS) {
|
|
183
|
+
const nested = { id: `${TOOL_RUNNER}/${check.id}`, label: check.label, of: check.id };
|
|
184
|
+
feeds.push({
|
|
185
|
+
...nested,
|
|
186
|
+
kind: 'check',
|
|
187
|
+
into: [],
|
|
188
|
+
under: TOOL_RUNNER,
|
|
189
|
+
column: 2,
|
|
190
|
+
group: 'tool-runner',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return feeds;
|
|
170
194
|
}
|
|
171
195
|
const clamp = (value) => Math.min(Math.max(value, 0), 1);
|
|
172
196
|
const between = (from, to, at) => from + (to - from) * at;
|
|
173
|
-
/**
|
|
174
|
-
function
|
|
175
|
-
return Array.from({ length: Math.max(first.length, second.length) }, (_, index) => [first[index], second[index]].filter((each) => each !== undefined)).flat();
|
|
176
|
-
}
|
|
177
|
-
/** A step under its runner in rows so many across; a tool under the registry in columns. */
|
|
178
|
-
function slotsOf(feeds) {
|
|
197
|
+
/** A step under the runner two across, down one column and then the next; any other a level in. */
|
|
198
|
+
function slotsOf(feeds, width) {
|
|
179
199
|
const slots = new Map();
|
|
200
|
+
const steps = feeds.filter((feed) => feed.kind === 'step');
|
|
201
|
+
const stepColumn = Math.max(0, ...steps.map((step) => width(step.id))) + STEP_GAP;
|
|
180
202
|
const counts = new Map();
|
|
181
203
|
for (const feed of feeds) {
|
|
182
|
-
if (feed.under === undefined
|
|
204
|
+
if (feed.under === undefined)
|
|
183
205
|
continue;
|
|
184
206
|
const index = counts.get(feed.under) ?? 0;
|
|
185
207
|
counts.set(feed.under, index + 1);
|
|
186
|
-
const slot = feed.kind === '
|
|
187
|
-
? { across: Math.floor(index /
|
|
188
|
-
: {
|
|
189
|
-
across: (index % STEPS_ACROSS) * STEP_SLOT,
|
|
190
|
-
down: Math.floor(index / STEPS_ACROSS) + 1,
|
|
191
|
-
};
|
|
208
|
+
const slot = feed.kind === 'step'
|
|
209
|
+
? { across: Math.floor(index / STEPS_DOWN) * stepColumn, down: (index % STEPS_DOWN) + 1 }
|
|
210
|
+
: { across: 0, down: index + 1 };
|
|
192
211
|
slots.set(feed.id, { holder: feed.under, across: INDENT + slot.across, down: slot.down });
|
|
193
212
|
}
|
|
194
213
|
return slots;
|
|
195
214
|
}
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
* each level with its rows or under the room the one before it holds for its own.
|
|
199
|
-
*/
|
|
200
|
-
function grownColumn(rows, feeds, slots, feedX) {
|
|
201
|
-
const places = new Map();
|
|
215
|
+
/** Each column's groups of heads, and how many rows each group holds with what is under its heads. */
|
|
216
|
+
function stacksOf(rows, feeds, slots) {
|
|
202
217
|
const held = new Map();
|
|
203
218
|
for (const slot of slots.values()) {
|
|
204
219
|
held.set(slot.holder, Math.max(held.get(slot.holder) ?? 0, slot.down));
|
|
205
220
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
221
|
+
const stack = (groups) => {
|
|
222
|
+
const ids = [...groups.values()];
|
|
223
|
+
const count = ids.flat().reduce((sum, id) => sum + 1 + (held.get(id) ?? 0), 0);
|
|
224
|
+
return { ids, rows: count };
|
|
225
|
+
};
|
|
226
|
+
const tree = new Map();
|
|
227
|
+
for (const row of rows)
|
|
228
|
+
tree.set(row.agent, [...(tree.get(row.agent) ?? []), rowId(row)]);
|
|
229
|
+
const columns = [1, 2].map((column) => {
|
|
230
|
+
const groups = new Map();
|
|
213
231
|
for (const feed of feeds) {
|
|
214
|
-
if (feed.
|
|
232
|
+
if (feed.column !== column || feed.under !== undefined)
|
|
215
233
|
continue;
|
|
216
|
-
|
|
217
|
-
const middle = level.length ? level.reduce((sum, y) => sum + y, 0) / level.length : cursor;
|
|
218
|
-
const y = Math.max(middle, cursor);
|
|
219
|
-
places.set(feed.id, { x: feedX, y });
|
|
220
|
-
cursor = y + GROWN_ROW * (1 + (held.get(feed.id) ?? 0));
|
|
234
|
+
groups.set(feed.group, [...(groups.get(feed.group) ?? []), feed.id]);
|
|
221
235
|
}
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
return
|
|
236
|
+
return stack(groups);
|
|
237
|
+
});
|
|
238
|
+
return { held, columns: [stack(tree), ...columns] };
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The map grown: the tree, each agent a zone of its own; beside it the providers, the tools and the
|
|
242
|
+
* runner; past them the tool runner. Rows stretch, up to a most, and the gaps between columns and
|
|
243
|
+
* between groups widen, so the map fills `box`; without one, rows and gaps are at their least.
|
|
244
|
+
*/
|
|
245
|
+
function grownPlaces(rows, feeds, slots, width, box) {
|
|
246
|
+
const { held, columns } = stacksOf(rows, feeds, slots);
|
|
247
|
+
// In rows, from the top node's middle to the bottom one's, each gap at its least of a row.
|
|
248
|
+
const span = Math.max(...columns.map((each) => each.rows + each.ids.length - 2));
|
|
249
|
+
const room = box ? box.height - 2 * (PAD + HALF) : 0;
|
|
250
|
+
const row = Math.min(Math.max(room / Math.max(span, 1), GROWN_ROW), TALLEST_ROW);
|
|
251
|
+
const height = Math.max(room, span * row);
|
|
252
|
+
const right = (id, x) => x + width(id);
|
|
253
|
+
const treeRight = Math.max(0, ...rows.map((each) => right(rowId(each), each.x)));
|
|
254
|
+
const columnWidth = (column) => Math.max(0, ...feeds
|
|
255
|
+
.filter((feed) => feed.column === column)
|
|
256
|
+
.map((feed) => {
|
|
257
|
+
const slot = slots.get(feed.id);
|
|
258
|
+
return right(feed.id, slot ? slot.across : 0);
|
|
259
|
+
}));
|
|
260
|
+
const [first, second] = [columnWidth(1), columnWidth(2)];
|
|
261
|
+
const spare = box ? (box.width - 2 * PAD - (treeRight - PAD) - first - second) / 2 : LEAST_GAP;
|
|
262
|
+
const gap = Math.min(Math.max(spare, LEAST_GAP), MOST_GAP);
|
|
263
|
+
const lefts = [0, treeRight + gap, treeRight + gap + first + gap];
|
|
264
|
+
const places = new Map();
|
|
265
|
+
const tree = new Map(rows.map((each) => [rowId(each), each]));
|
|
266
|
+
columns.forEach((stack, column) => {
|
|
267
|
+
const free = height - (stack.rows - 1) * row;
|
|
268
|
+
// Past a group, the next stands a row and its share of what is free further down.
|
|
269
|
+
const extra = stack.ids.length > 1 ? free / (stack.ids.length - 1) : 0;
|
|
270
|
+
// why: A column of one group has no gap to widen, so it stands in the middle of the map.
|
|
271
|
+
let y = PAD + HALF + (stack.ids.length > 1 ? 0 : free / 2);
|
|
272
|
+
for (const group of stack.ids) {
|
|
273
|
+
for (const id of group) {
|
|
274
|
+
places.set(id, { x: tree.get(id)?.x ?? lefts[column] ?? 0, y });
|
|
275
|
+
y += row * (1 + (held.get(id) ?? 0));
|
|
276
|
+
}
|
|
277
|
+
y += extra;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return { places, row };
|
|
225
281
|
}
|
|
226
282
|
/** Where a node stands this far grown: a node under a feed keeps its slot under it on the way. */
|
|
227
283
|
export function placeAt(layout, id, grow) {
|
|
@@ -229,7 +285,8 @@ export function placeAt(layout, id, grow) {
|
|
|
229
285
|
const slot = layout.slots.get(id);
|
|
230
286
|
if (slot) {
|
|
231
287
|
const holder = placeAt(layout, slot.holder, grow);
|
|
232
|
-
|
|
288
|
+
const row = between(ROW, layout.row, moved);
|
|
289
|
+
return { x: holder.x + slot.across, y: holder.y + slot.down * row };
|
|
233
290
|
}
|
|
234
291
|
const to = layout.grown.get(id) ?? { x: 0, y: 0 };
|
|
235
292
|
const from = layout.closed.get(id) ?? to;
|
|
@@ -257,29 +314,40 @@ function clearFrom(layout, id, ids, width) {
|
|
|
257
314
|
}
|
|
258
315
|
return 0;
|
|
259
316
|
}
|
|
260
|
-
/**
|
|
261
|
-
|
|
317
|
+
/**
|
|
318
|
+
* The tree and what feeds it, closed and grown, for tokens this wide: grown, it fills `box` when
|
|
319
|
+
* one is given.
|
|
320
|
+
*/
|
|
321
|
+
export function treeMapLayout(workspace, agents, width, box) {
|
|
262
322
|
const rows = rowsOf(workspace, agents);
|
|
263
|
-
const
|
|
323
|
+
const feeds = feedsOf(workspace, rows);
|
|
324
|
+
// Closed, the first column's heads stand beside the tree, each level with the first row it
|
|
325
|
+
// joins or under the one before.
|
|
326
|
+
const fewFeeds = feeds.filter((feed) => feed.column === 1 && feed.under === undefined);
|
|
264
327
|
const few = new Set(fewFeeds.map((feed) => feed.id));
|
|
265
|
-
const feeds = feedsOf(workspace, rows, true);
|
|
266
328
|
const feedX = Math.max(0, ...rows.map((row) => row.x + width(rowId(row)))) + GAP;
|
|
267
329
|
const closed = new Map(rows.map((row) => [rowId(row), row]));
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
330
|
+
const rowY = new Map(rows.map((row) => [rowId(row), row.y]));
|
|
331
|
+
let below = Number.NEGATIVE_INFINITY;
|
|
332
|
+
for (const feed of fewFeeds) {
|
|
333
|
+
const y = Math.max(rowY.get(feed.into[0] ?? '') ?? below, below);
|
|
334
|
+
closed.set(feed.id, { x: feedX, y });
|
|
335
|
+
below = y + ROW;
|
|
336
|
+
}
|
|
337
|
+
const slots = slotsOf(feeds, width);
|
|
338
|
+
const { places, row } = grownPlaces(rows, feeds, slots, width, box);
|
|
271
339
|
const layout = {
|
|
272
340
|
rows,
|
|
273
341
|
feeds,
|
|
274
342
|
few,
|
|
275
343
|
closed,
|
|
276
|
-
grown:
|
|
344
|
+
grown: places,
|
|
345
|
+
row,
|
|
277
346
|
slots,
|
|
278
347
|
starts: new Map(),
|
|
279
348
|
};
|
|
280
|
-
// What growing adds draws in
|
|
281
|
-
const
|
|
282
|
-
const order = byTurns(added.filter((feed) => feed.under !== REGISTRY), added.filter((feed) => feed.under === REGISTRY));
|
|
349
|
+
// What growing adds draws in its columns' order, each once it stands clear.
|
|
350
|
+
const order = feeds.filter((feed) => !few.has(feed.id));
|
|
283
351
|
const ids = [...rows.map(rowId), ...feeds.map((feed) => feed.id)];
|
|
284
352
|
order.forEach((feed, index) => {
|
|
285
353
|
const turn = DRAW_FROM + ((DRAW_TO - DRAW_SPAN - DRAW_FROM) * index) / Math.max(order.length - 1, 1);
|
package/ui/tree-map.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { type StudioWorkspace } from '../mod.ts';
|
|
2
|
+
import { type Box } from './lib/tree-map-layout.ts';
|
|
2
3
|
export interface StudioTreeMapProps {
|
|
3
4
|
workspace: StudioWorkspace;
|
|
4
5
|
/** The keys of the agents to draw, in the workspace's order; every agent when unset. */
|
|
5
6
|
agents?: readonly string[] | undefined;
|
|
6
7
|
/**
|
|
7
|
-
* How far the tree has grown, from 0 to 1. As it grows each agent drifts into a zone of its own
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* How far the tree has grown, from 0 to 1. As it grows each agent drifts into a zone of its own
|
|
9
|
+
* and what feeds the tree spreads into its columns. What growing adds draws in as its room opens,
|
|
10
|
+
* its line and then its token: the protocols under each provider, what branches off a tool, the
|
|
11
|
+
* runner's steps, and the tool runner with its checks. Shrinking takes it all back the same way.
|
|
11
12
|
*/
|
|
12
13
|
grow?: number | undefined;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The box the map fills grown: its rows and gaps stretch to fill it, and it stands in the middle
|
|
16
|
+
* of it. When the map cannot fit, it eases smaller as it grows until all of it does.
|
|
17
|
+
*/
|
|
18
|
+
fit?: Box | undefined;
|
|
18
19
|
/** A token dragged moves to where it is dropped, its lines with it; the field dragged moves the whole map. */
|
|
19
20
|
draggable?: boolean | undefined;
|
|
20
21
|
}
|