@openclawcity/become 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,767 @@
1
+ // src/dashboard/theme.ts
2
+ var STAGE_COLORS = {
3
+ novice: "#64748b",
4
+ beginner: "#22d3ee",
5
+ competent: "#34d399",
6
+ proficient: "#a78bfa",
7
+ expert: "#fbbf24"
8
+ };
9
+ var STAGE_LABELS = {
10
+ novice: "Novice",
11
+ beginner: "Beginner",
12
+ competent: "Competent",
13
+ proficient: "Proficient",
14
+ expert: "Expert"
15
+ };
16
+ var BACKGROUND = {
17
+ panel: "rgba(10, 12, 20, 0.94)",
18
+ card: "rgba(255, 255, 255, 0.03)",
19
+ hover: "rgba(255, 255, 255, 0.06)"
20
+ };
21
+ var BORDER = {
22
+ subtle: "rgba(0, 212, 255, 0.15)",
23
+ accent: "rgba(0, 212, 255, 0.3)"
24
+ };
25
+ var ACCENT = "#00d4ff";
26
+ var GOLD = "rgba(255, 215, 0, 0.8)";
27
+ var CELEBRATION_CONFIG = {
28
+ micro: { particles: 0, spread: 0, duration: 0 },
29
+ small: { particles: 20, spread: 50, duration: 1e3 },
30
+ medium: { particles: 60, spread: 70, duration: 1500 },
31
+ large: { particles: 80, spread: 90, duration: 2e3 },
32
+ epic: { particles: 120, spread: 120, duration: 4e3 }
33
+ };
34
+
35
+ // src/dashboard/components/SkillRing.tsx
36
+ import { jsx, jsxs } from "react/jsx-runtime";
37
+ function SkillRing({
38
+ skill,
39
+ score,
40
+ stage,
41
+ size = 80,
42
+ showLabel = true,
43
+ className
44
+ }) {
45
+ const safeSize = Math.max(24, size);
46
+ const strokeWidth = Math.max(4, safeSize * 0.08);
47
+ const radius = (safeSize - strokeWidth) / 2;
48
+ const circumference = 2 * Math.PI * radius;
49
+ const progress = Math.min(100, Math.max(0, score)) / 100;
50
+ const offset = circumference * (1 - progress);
51
+ const color = STAGE_COLORS[stage];
52
+ const center = safeSize / 2;
53
+ return /* @__PURE__ */ jsxs(
54
+ "div",
55
+ {
56
+ className,
57
+ style: {
58
+ display: "inline-flex",
59
+ flexDirection: "column",
60
+ alignItems: "center",
61
+ gap: 4
62
+ },
63
+ children: [
64
+ /* @__PURE__ */ jsxs(
65
+ "svg",
66
+ {
67
+ width: safeSize,
68
+ height: safeSize,
69
+ viewBox: `0 0 ${safeSize} ${safeSize}`,
70
+ role: "img",
71
+ "aria-label": `${skill}: ${score}/100, ${STAGE_LABELS[stage]}`,
72
+ children: [
73
+ /* @__PURE__ */ jsx(
74
+ "circle",
75
+ {
76
+ cx: center,
77
+ cy: center,
78
+ r: radius,
79
+ fill: "none",
80
+ stroke: "rgba(255,255,255,0.08)",
81
+ strokeWidth
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(
85
+ "circle",
86
+ {
87
+ cx: center,
88
+ cy: center,
89
+ r: radius,
90
+ fill: "none",
91
+ stroke: color,
92
+ strokeWidth,
93
+ strokeDasharray: circumference,
94
+ strokeDashoffset: offset,
95
+ strokeLinecap: "round",
96
+ transform: `rotate(-90 ${center} ${center})`,
97
+ style: { transition: "stroke-dashoffset 0.6s ease" }
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx(
101
+ "text",
102
+ {
103
+ x: center,
104
+ y: center,
105
+ textAnchor: "middle",
106
+ dominantBaseline: "central",
107
+ fill: "white",
108
+ fontSize: safeSize * 0.28,
109
+ fontWeight: "bold",
110
+ fontFamily: "system-ui, sans-serif",
111
+ children: score
112
+ }
113
+ )
114
+ ]
115
+ }
116
+ ),
117
+ showLabel && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
118
+ /* @__PURE__ */ jsx(
119
+ "div",
120
+ {
121
+ style: {
122
+ color: "rgba(255,255,255,0.9)",
123
+ fontSize: Math.max(10, safeSize * 0.14),
124
+ fontWeight: 500,
125
+ fontFamily: "system-ui, sans-serif"
126
+ },
127
+ children: skill
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsx(
131
+ "div",
132
+ {
133
+ style: {
134
+ color,
135
+ fontSize: Math.max(9, safeSize * 0.12),
136
+ fontFamily: "system-ui, sans-serif",
137
+ textTransform: "uppercase",
138
+ letterSpacing: "0.05em"
139
+ },
140
+ children: STAGE_LABELS[stage]
141
+ }
142
+ )
143
+ ] })
144
+ ]
145
+ }
146
+ );
147
+ }
148
+
149
+ // src/dashboard/components/Sparkline.tsx
150
+ import { useId } from "react";
151
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
152
+ function Sparkline({
153
+ data,
154
+ width = 300,
155
+ height = 40,
156
+ color = "#22d3ee",
157
+ showDots = false,
158
+ className
159
+ }) {
160
+ if (data.length < 2) {
161
+ return /* @__PURE__ */ jsx2("svg", { width, height, className, role: "img", "aria-label": "Insufficient data", children: /* @__PURE__ */ jsx2("text", { x: width / 2, y: height / 2, textAnchor: "middle", dominantBaseline: "central", fill: "rgba(255,255,255,0.3)", fontSize: 10, children: "Not enough data" }) });
162
+ }
163
+ const strokeColor = Object.prototype.hasOwnProperty.call(STAGE_COLORS, color) ? STAGE_COLORS[color] : color;
164
+ const padding = 4;
165
+ const chartWidth = width - padding * 2;
166
+ const chartHeight = height - padding * 2;
167
+ const scores = data.map((d) => d.score);
168
+ let minScore = scores[0];
169
+ let maxScore = scores[0];
170
+ for (let i = 1; i < scores.length; i++) {
171
+ if (scores[i] < minScore) minScore = scores[i];
172
+ if (scores[i] > maxScore) maxScore = scores[i];
173
+ }
174
+ const range = maxScore - minScore || 1;
175
+ const points = data.map((d, i) => {
176
+ const x = padding + i / (data.length - 1) * chartWidth;
177
+ const y = padding + chartHeight - (d.score - minScore) / range * chartHeight;
178
+ return { x, y };
179
+ });
180
+ const polyline = points.map((p) => `${p.x},${p.y}`).join(" ");
181
+ const fillPoints = [
182
+ ...points.map((p) => `${p.x},${p.y}`),
183
+ `${points[points.length - 1].x},${height - padding}`,
184
+ `${points[0].x},${height - padding}`
185
+ ].join(" ");
186
+ const reactId = useId();
187
+ const gradientId = `sparkline-grad-${reactId.replace(/:/g, "")}`;
188
+ return /* @__PURE__ */ jsxs2(
189
+ "svg",
190
+ {
191
+ width,
192
+ height,
193
+ viewBox: `0 0 ${width} ${height}`,
194
+ className,
195
+ role: "img",
196
+ "aria-label": `Trend: ${scores[0]} to ${scores[scores.length - 1]}`,
197
+ children: [
198
+ /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsxs2("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "0%", y2: "100%", children: [
199
+ /* @__PURE__ */ jsx2("stop", { offset: "0%", stopColor: strokeColor, stopOpacity: "0.2" }),
200
+ /* @__PURE__ */ jsx2("stop", { offset: "100%", stopColor: strokeColor, stopOpacity: "0" })
201
+ ] }) }),
202
+ /* @__PURE__ */ jsx2("polygon", { points: fillPoints, fill: `url(#${gradientId})` }),
203
+ /* @__PURE__ */ jsx2(
204
+ "polyline",
205
+ {
206
+ points: polyline,
207
+ fill: "none",
208
+ stroke: strokeColor,
209
+ strokeWidth: 1.5,
210
+ strokeLinejoin: "round",
211
+ strokeLinecap: "round"
212
+ }
213
+ ),
214
+ showDots && points.map((p, i) => /* @__PURE__ */ jsx2(
215
+ "circle",
216
+ {
217
+ cx: p.x,
218
+ cy: p.y,
219
+ r: 2,
220
+ fill: strokeColor
221
+ },
222
+ i
223
+ )),
224
+ /* @__PURE__ */ jsx2(
225
+ "circle",
226
+ {
227
+ cx: points[points.length - 1].x,
228
+ cy: points[points.length - 1].y,
229
+ r: 2.5,
230
+ fill: strokeColor
231
+ }
232
+ )
233
+ ]
234
+ }
235
+ );
236
+ }
237
+
238
+ // src/core/validation.ts
239
+ var MAX_AGENT_ID_LENGTH = 200;
240
+ var AGENT_ID_REGEX = /^[a-zA-Z0-9_.:@/-]+$/;
241
+ function validateAgentId(agentId) {
242
+ if (!agentId || typeof agentId !== "string") {
243
+ throw new Error("agentId is required and must be a non-empty string");
244
+ }
245
+ if (agentId.length > MAX_AGENT_ID_LENGTH) {
246
+ throw new Error(`agentId too long (max ${MAX_AGENT_ID_LENGTH} chars)`);
247
+ }
248
+ if (!AGENT_ID_REGEX.test(agentId)) {
249
+ throw new Error("agentId contains invalid characters (allowed: alphanumeric, _ . : @ / -)");
250
+ }
251
+ }
252
+
253
+ // src/core/milestones.ts
254
+ var BUILT_IN = {
255
+ skill_discovered: { threshold: 1, description: "First score for a skill" },
256
+ skill_competent: { threshold: 36, description: "Reached competent stage" },
257
+ skill_proficient: { threshold: 56, description: "Reached proficient stage" },
258
+ skill_expert: { threshold: 76, description: "Reached expert stage" },
259
+ first_artifact: { threshold: 1, description: "Created first output" },
260
+ ten_artifacts: { threshold: 10, description: "Created 10 outputs" },
261
+ first_collab: { threshold: 1, description: "Completed first collaboration" },
262
+ first_teaching: { threshold: 1, description: "Taught another agent for the first time" },
263
+ first_peer_review: { threshold: 1, description: "Gave first peer review" },
264
+ identity_shift: { threshold: 1, description: "Agent evolved its identity" },
265
+ norm_setter: { threshold: 1, description: "Started a cultural norm adopted by 3+ agents" }
266
+ };
267
+ var MilestoneDetector = class {
268
+ constructor(adapter) {
269
+ this.adapter = adapter;
270
+ }
271
+ custom = {};
272
+ register(type, config) {
273
+ this.custom[type] = config;
274
+ }
275
+ async check(agentId, scores) {
276
+ validateAgentId(agentId);
277
+ const awarded = [];
278
+ const now = (/* @__PURE__ */ new Date()).toISOString();
279
+ const globalChecked = /* @__PURE__ */ new Set();
280
+ for (const score of scores) {
281
+ if (score.score > 0) {
282
+ const type = `skill_discovered:${score.skill}`;
283
+ if (await this.tryAward(agentId, type, 1, score.skill, now)) {
284
+ awarded.push({ agent_id: agentId, milestone_type: type, threshold: 1, skill: score.skill, achieved_at: now });
285
+ }
286
+ }
287
+ const stageChecks = [
288
+ ["skill_competent", 36],
289
+ ["skill_proficient", 56],
290
+ ["skill_expert", 76]
291
+ ];
292
+ for (const [prefix, threshold] of stageChecks) {
293
+ if (score.score >= threshold) {
294
+ const type = `${prefix}:${score.skill}`;
295
+ if (await this.tryAward(agentId, type, threshold, score.skill, now)) {
296
+ awarded.push({ agent_id: agentId, milestone_type: type, threshold, skill: score.skill, achieved_at: now });
297
+ }
298
+ }
299
+ }
300
+ const globalMilestones = [
301
+ ["first_artifact", 1, score.evidence.artifact_count >= 1],
302
+ ["ten_artifacts", 10, score.evidence.artifact_count >= 10],
303
+ ["first_collab", 1, score.evidence.collab_count >= 1],
304
+ ["first_teaching", 1, score.evidence.teaching_events >= 1],
305
+ ["first_peer_review", 1, score.evidence.peer_reviews_given >= 1]
306
+ ];
307
+ for (const [type, threshold, eligible] of globalMilestones) {
308
+ if (eligible && !globalChecked.has(type)) {
309
+ globalChecked.add(type);
310
+ if (await this.tryAward(agentId, type, threshold, void 0, now)) {
311
+ awarded.push({ agent_id: agentId, milestone_type: type, threshold, achieved_at: now });
312
+ }
313
+ }
314
+ }
315
+ }
316
+ return awarded;
317
+ }
318
+ async tryAward(agentId, milestoneType, threshold, skill, now) {
319
+ const exists = await this.adapter.hasMilestone(agentId, milestoneType, skill);
320
+ if (exists) return false;
321
+ return this.adapter.saveMilestone({
322
+ agent_id: agentId,
323
+ milestone_type: milestoneType,
324
+ threshold,
325
+ skill,
326
+ achieved_at: now
327
+ });
328
+ }
329
+ static celebrationTier(milestoneType, threshold) {
330
+ if (milestoneType.startsWith("skill_expert")) return "epic";
331
+ if (milestoneType.startsWith("skill_proficient")) return "large";
332
+ if (milestoneType.startsWith("skill_competent")) return "medium";
333
+ if (milestoneType.startsWith("skill_discovered")) return "small";
334
+ if (threshold !== void 0) {
335
+ if (threshold >= 50) return "large";
336
+ if (threshold >= 10) return "medium";
337
+ }
338
+ return "small";
339
+ }
340
+ static getBuiltInMilestones() {
341
+ return { ...BUILT_IN };
342
+ }
343
+ };
344
+
345
+ // src/dashboard/components/MilestoneTimeline.tsx
346
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
347
+ function formatMilestoneLabel(type) {
348
+ if (type.includes(":")) {
349
+ const [prefix, skill] = type.split(":");
350
+ const stage = prefix.replace("skill_", "");
351
+ return `${stage.charAt(0).toUpperCase() + stage.slice(1)}: ${skill}`;
352
+ }
353
+ return type.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
354
+ }
355
+ function tierColor(tier) {
356
+ switch (tier) {
357
+ case "epic":
358
+ return GOLD;
359
+ case "large":
360
+ return STAGE_COLORS.proficient;
361
+ case "medium":
362
+ return STAGE_COLORS.competent;
363
+ case "small":
364
+ return STAGE_COLORS.beginner;
365
+ default:
366
+ return "rgba(255,255,255,0.3)";
367
+ }
368
+ }
369
+ function formatDate(iso) {
370
+ try {
371
+ const d = new Date(iso);
372
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
373
+ } catch {
374
+ return "";
375
+ }
376
+ }
377
+ function MilestoneTimeline({
378
+ milestones,
379
+ limit = 10,
380
+ className
381
+ }) {
382
+ const sorted = [...milestones].sort((a, b) => b.achieved_at.localeCompare(a.achieved_at)).slice(0, limit);
383
+ if (sorted.length === 0) {
384
+ return /* @__PURE__ */ jsx3("div", { className, style: { color: "rgba(255,255,255,0.4)", fontSize: 13, fontFamily: "system-ui, sans-serif", padding: 16 }, children: "No milestones yet" });
385
+ }
386
+ return /* @__PURE__ */ jsxs3("div", { className, style: { position: "relative", paddingLeft: 20 }, role: "list", "aria-label": "Milestone timeline", children: [
387
+ /* @__PURE__ */ jsx3(
388
+ "div",
389
+ {
390
+ style: {
391
+ position: "absolute",
392
+ left: 7,
393
+ top: 4,
394
+ bottom: 4,
395
+ width: 2,
396
+ background: "rgba(255,255,255,0.1)",
397
+ borderRadius: 1
398
+ }
399
+ }
400
+ ),
401
+ sorted.map((m, i) => {
402
+ const tier = MilestoneDetector.celebrationTier(m.milestone_type, m.threshold);
403
+ const color = tierColor(tier);
404
+ return /* @__PURE__ */ jsxs3(
405
+ "div",
406
+ {
407
+ role: "listitem",
408
+ style: {
409
+ display: "flex",
410
+ alignItems: "flex-start",
411
+ gap: 10,
412
+ marginBottom: 12,
413
+ position: "relative"
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ jsx3(
417
+ "div",
418
+ {
419
+ style: {
420
+ position: "absolute",
421
+ left: -16,
422
+ top: 4,
423
+ width: 10,
424
+ height: 10,
425
+ borderRadius: "50%",
426
+ background: color,
427
+ border: `2px solid ${color}`,
428
+ boxShadow: tier === "epic" ? `0 0 8px ${color}` : void 0
429
+ }
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
433
+ /* @__PURE__ */ jsx3(
434
+ "div",
435
+ {
436
+ style: {
437
+ color: "rgba(255,255,255,0.9)",
438
+ fontSize: 13,
439
+ fontWeight: 500,
440
+ fontFamily: "system-ui, sans-serif"
441
+ },
442
+ children: formatMilestoneLabel(m.milestone_type)
443
+ }
444
+ ),
445
+ /* @__PURE__ */ jsx3(
446
+ "div",
447
+ {
448
+ style: {
449
+ color: "rgba(255,255,255,0.4)",
450
+ fontSize: 11,
451
+ fontFamily: "system-ui, sans-serif",
452
+ marginTop: 2
453
+ },
454
+ children: formatDate(m.achieved_at)
455
+ }
456
+ )
457
+ ] })
458
+ ]
459
+ },
460
+ `${m.milestone_type}-${m.achieved_at}-${i}`
461
+ );
462
+ })
463
+ ] });
464
+ }
465
+
466
+ // src/dashboard/components/GrowthCard.tsx
467
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
468
+ function GrowthCard({
469
+ agentId,
470
+ agentName,
471
+ scores,
472
+ scoreHistory,
473
+ milestones,
474
+ className
475
+ }) {
476
+ const sortedScores = [...scores].sort((a, b) => b.score - a.score);
477
+ const avgScore = scores.length > 0 ? Math.round(scores.reduce((sum, s) => sum + s.score, 0) / scores.length) : 0;
478
+ return /* @__PURE__ */ jsxs4(
479
+ "div",
480
+ {
481
+ className,
482
+ style: {
483
+ background: BACKGROUND.panel,
484
+ border: `1px solid ${BORDER.subtle}`,
485
+ borderRadius: 12,
486
+ padding: 20,
487
+ fontFamily: "system-ui, sans-serif",
488
+ color: "white",
489
+ maxWidth: 380,
490
+ backdropFilter: "blur(20px) saturate(1.5)"
491
+ },
492
+ children: [
493
+ /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 16 }, children: [
494
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 16, fontWeight: 600, color: "rgba(255,255,255,0.95)" }, children: agentName ?? agentId }),
495
+ /* @__PURE__ */ jsxs4("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.4)", marginTop: 2 }, children: [
496
+ scores.length,
497
+ " skill",
498
+ scores.length !== 1 ? "s" : "",
499
+ " \xB7 avg score ",
500
+ avgScore
501
+ ] })
502
+ ] }),
503
+ sortedScores.length > 0 && /* @__PURE__ */ jsx4(
504
+ "div",
505
+ {
506
+ style: {
507
+ display: "flex",
508
+ flexWrap: "wrap",
509
+ gap: 16,
510
+ justifyContent: "center",
511
+ marginBottom: 20,
512
+ padding: "12px 0",
513
+ borderTop: `1px solid ${BORDER.subtle}`,
514
+ borderBottom: `1px solid ${BORDER.subtle}`
515
+ },
516
+ children: sortedScores.slice(0, 5).map((s) => /* @__PURE__ */ jsx4(
517
+ SkillRing,
518
+ {
519
+ skill: s.skill,
520
+ score: s.score,
521
+ stage: s.dreyfus_stage,
522
+ size: 64
523
+ },
524
+ s.skill
525
+ ))
526
+ }
527
+ ),
528
+ scoreHistory && sortedScores.length > 0 && /* @__PURE__ */ jsx4("div", { style: { marginBottom: 16 }, children: sortedScores.slice(0, 3).map((s) => {
529
+ const history = scoreHistory.get(s.skill);
530
+ if (!history || history.length < 2) return null;
531
+ return /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 8 }, children: [
532
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 11, color: "rgba(255,255,255,0.5)", marginBottom: 2 }, children: s.skill }),
533
+ /* @__PURE__ */ jsx4(
534
+ Sparkline,
535
+ {
536
+ data: history,
537
+ width: 340,
538
+ height: 32,
539
+ color: s.dreyfus_stage
540
+ }
541
+ )
542
+ ] }, s.skill);
543
+ }) }),
544
+ milestones && milestones.length > 0 && /* @__PURE__ */ jsxs4("div", { children: [
545
+ /* @__PURE__ */ jsx4("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.5)", marginBottom: 8, fontWeight: 500 }, children: "Recent Milestones" }),
546
+ /* @__PURE__ */ jsx4(MilestoneTimeline, { milestones, limit: 5 })
547
+ ] }),
548
+ scores.length === 0 && /* @__PURE__ */ jsx4("div", { style: { textAlign: "center", padding: 24, color: "rgba(255,255,255,0.3)", fontSize: 13 }, children: "No skills scored yet" })
549
+ ]
550
+ }
551
+ );
552
+ }
553
+
554
+ // src/dashboard/components/PeerGraph.tsx
555
+ import { useMemo } from "react";
556
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
557
+ function PeerGraph({
558
+ nodes,
559
+ edges,
560
+ width = 400,
561
+ height = 300,
562
+ className
563
+ }) {
564
+ const layout = useMemo(() => {
565
+ const cx = width / 2;
566
+ const cy = height / 2;
567
+ const radius = Math.min(cx, cy) - 40;
568
+ return nodes.map((node, i) => {
569
+ const angle = 2 * Math.PI * i / nodes.length - Math.PI / 2;
570
+ return {
571
+ ...node,
572
+ x: cx + radius * Math.cos(angle),
573
+ y: cy + radius * Math.sin(angle)
574
+ };
575
+ });
576
+ }, [nodes, width, height]);
577
+ const nodeMap = useMemo(() => {
578
+ const map = /* @__PURE__ */ new Map();
579
+ for (const n of layout) map.set(n.id, n);
580
+ return map;
581
+ }, [layout]);
582
+ const { edgeCounts, dedupedEdges, nodeEdgeCounts } = useMemo(() => {
583
+ const counts = /* @__PURE__ */ new Map();
584
+ const deduped = [];
585
+ const seen = /* @__PURE__ */ new Set();
586
+ const nodeCounts = /* @__PURE__ */ new Map();
587
+ for (const e of edges) {
588
+ const key = [e.from_agent, e.to_agent].sort().join("\u2194");
589
+ counts.set(key, (counts.get(key) ?? 0) + 1);
590
+ nodeCounts.set(e.from_agent, (nodeCounts.get(e.from_agent) ?? 0) + 1);
591
+ nodeCounts.set(e.to_agent, (nodeCounts.get(e.to_agent) ?? 0) + 1);
592
+ }
593
+ for (const e of edges) {
594
+ const key = [e.from_agent, e.to_agent].sort().join("\u2194");
595
+ if (!seen.has(key)) {
596
+ seen.add(key);
597
+ deduped.push({ key, edge: e, count: counts.get(key) ?? 1 });
598
+ }
599
+ }
600
+ return { edgeCounts: counts, dedupedEdges: deduped, nodeEdgeCounts: nodeCounts };
601
+ }, [edges]);
602
+ if (nodes.length === 0) {
603
+ return /* @__PURE__ */ jsx5("svg", { width, height, className, children: /* @__PURE__ */ jsx5("text", { x: width / 2, y: height / 2, textAnchor: "middle", fill: "rgba(255,255,255,0.3)", fontSize: 13, children: "No agents to display" }) });
604
+ }
605
+ return /* @__PURE__ */ jsxs5(
606
+ "svg",
607
+ {
608
+ width,
609
+ height,
610
+ viewBox: `0 0 ${width} ${height}`,
611
+ className,
612
+ role: "img",
613
+ "aria-label": `Learning network: ${nodes.length} agents, ${edges.length} connections`,
614
+ children: [
615
+ dedupedEdges.map(({ key, edge, count }) => {
616
+ const from = nodeMap.get(edge.from_agent);
617
+ const to = nodeMap.get(edge.to_agent);
618
+ if (!from || !to) return null;
619
+ const strokeWidth = Math.min(4, 0.5 + count * 0.5);
620
+ return /* @__PURE__ */ jsx5(
621
+ "line",
622
+ {
623
+ x1: from.x,
624
+ y1: from.y,
625
+ x2: to.x,
626
+ y2: to.y,
627
+ stroke: ACCENT,
628
+ strokeWidth,
629
+ strokeOpacity: 0.3 + Math.min(0.4, count * 0.1)
630
+ },
631
+ `edge-${key}`
632
+ );
633
+ }),
634
+ layout.map((node) => {
635
+ const color = node.stage ? STAGE_COLORS[node.stage] : ACCENT;
636
+ const edgeCount = nodeEdgeCounts.get(node.id) ?? 0;
637
+ const radius = Math.min(20, 8 + edgeCount * 1.5);
638
+ return /* @__PURE__ */ jsxs5("g", { children: [
639
+ /* @__PURE__ */ jsx5("circle", { cx: node.x, cy: node.y, r: radius + 4, fill: color, opacity: 0.1 }),
640
+ /* @__PURE__ */ jsx5("circle", { cx: node.x, cy: node.y, r: radius, fill: color, opacity: 0.8 }),
641
+ /* @__PURE__ */ jsx5(
642
+ "text",
643
+ {
644
+ x: node.x,
645
+ y: node.y + radius + 14,
646
+ textAnchor: "middle",
647
+ fill: "rgba(255,255,255,0.7)",
648
+ fontSize: 10,
649
+ fontFamily: "system-ui, sans-serif",
650
+ children: node.label ?? node.id
651
+ }
652
+ )
653
+ ] }, node.id);
654
+ })
655
+ ]
656
+ }
657
+ );
658
+ }
659
+
660
+ // src/dashboard/components/PopulationView.tsx
661
+ import { useMemo as useMemo2 } from "react";
662
+ import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
663
+ function PopulationView({ agents, className }) {
664
+ const { allScores, avgScore, stageDist, totalSkills, topSkills } = useMemo2(() => {
665
+ const scores = agents.flatMap((a) => a.scores);
666
+ const avg = scores.length > 0 ? Math.round(scores.reduce((s, sc) => s + sc.score, 0) / scores.length) : 0;
667
+ const dist = {
668
+ novice: 0,
669
+ beginner: 0,
670
+ competent: 0,
671
+ proficient: 0,
672
+ expert: 0
673
+ };
674
+ for (const s of scores) {
675
+ dist[s.dreyfus_stage]++;
676
+ }
677
+ const skillCounts = /* @__PURE__ */ new Map();
678
+ for (const s of scores) {
679
+ skillCounts.set(s.skill, (skillCounts.get(s.skill) ?? 0) + 1);
680
+ }
681
+ const top = [...skillCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
682
+ return { allScores: scores, avgScore: avg, stageDist: dist, totalSkills: scores.length, topSkills: top };
683
+ }, [agents]);
684
+ return /* @__PURE__ */ jsxs6(
685
+ "div",
686
+ {
687
+ className,
688
+ style: {
689
+ background: BACKGROUND.panel,
690
+ border: `1px solid ${BORDER.subtle}`,
691
+ borderRadius: 12,
692
+ padding: 20,
693
+ fontFamily: "system-ui, sans-serif",
694
+ color: "white",
695
+ maxWidth: 500,
696
+ backdropFilter: "blur(20px) saturate(1.5)"
697
+ },
698
+ children: [
699
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 16, fontWeight: 600, marginBottom: 16 }, children: "Population Overview" }),
700
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 24, marginBottom: 20 }, children: [
701
+ /* @__PURE__ */ jsx6(Stat, { label: "Agents", value: agents.length }),
702
+ /* @__PURE__ */ jsx6(Stat, { label: "Skills tracked", value: totalSkills }),
703
+ /* @__PURE__ */ jsx6(Stat, { label: "Avg score", value: avgScore })
704
+ ] }),
705
+ /* @__PURE__ */ jsxs6("div", { style: { marginBottom: 20 }, children: [
706
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.5)", marginBottom: 8 }, children: "Stage Distribution" }),
707
+ totalSkills > 0 ? /* @__PURE__ */ jsxs6(Fragment, { children: [
708
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", height: 20, borderRadius: 4, overflow: "hidden" }, children: Object.entries(stageDist).filter(([, count]) => count > 0).map(([stage, count]) => /* @__PURE__ */ jsx6(
709
+ "div",
710
+ {
711
+ style: {
712
+ width: `${count / totalSkills * 100}%`,
713
+ background: STAGE_COLORS[stage],
714
+ minWidth: 2
715
+ },
716
+ title: `${STAGE_LABELS[stage]}: ${count}`
717
+ },
718
+ stage
719
+ )) }),
720
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 12, marginTop: 6, flexWrap: "wrap" }, children: Object.entries(stageDist).filter(([, count]) => count > 0).map(([stage, count]) => /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
721
+ /* @__PURE__ */ jsx6("div", { style: { width: 8, height: 8, borderRadius: 2, background: STAGE_COLORS[stage] } }),
722
+ /* @__PURE__ */ jsxs6("span", { style: { fontSize: 11, color: "rgba(255,255,255,0.6)" }, children: [
723
+ STAGE_LABELS[stage],
724
+ " (",
725
+ count,
726
+ ")"
727
+ ] })
728
+ ] }, stage)) })
729
+ ] }) : /* @__PURE__ */ jsx6("div", { style: { color: "rgba(255,255,255,0.3)", fontSize: 12 }, children: "No data" })
730
+ ] }),
731
+ topSkills.length > 0 && /* @__PURE__ */ jsxs6("div", { children: [
732
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 12, color: "rgba(255,255,255,0.5)", marginBottom: 8 }, children: "Most Popular Skills" }),
733
+ topSkills.map(([skill, count]) => {
734
+ const pct = totalSkills > 0 ? count / totalSkills * 100 : 0;
735
+ return /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [
736
+ /* @__PURE__ */ jsx6("div", { style: { width: 80, fontSize: 12, color: "rgba(255,255,255,0.7)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: skill }),
737
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1, height: 6, background: "rgba(255,255,255,0.06)", borderRadius: 3 }, children: /* @__PURE__ */ jsx6("div", { style: { width: `${pct}%`, height: "100%", background: "#22d3ee", borderRadius: 3, minWidth: 2 } }) }),
738
+ /* @__PURE__ */ jsx6("div", { style: { width: 24, fontSize: 11, color: "rgba(255,255,255,0.4)", textAlign: "right" }, children: count })
739
+ ] }, skill);
740
+ })
741
+ ] })
742
+ ]
743
+ }
744
+ );
745
+ }
746
+ function Stat({ label, value }) {
747
+ return /* @__PURE__ */ jsxs6("div", { children: [
748
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 22, fontWeight: 700, color: "rgba(255,255,255,0.95)" }, children: value }),
749
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 11, color: "rgba(255,255,255,0.4)" }, children: label })
750
+ ] });
751
+ }
752
+ export {
753
+ ACCENT,
754
+ BACKGROUND,
755
+ BORDER,
756
+ CELEBRATION_CONFIG,
757
+ GOLD,
758
+ GrowthCard,
759
+ MilestoneTimeline,
760
+ PeerGraph,
761
+ PopulationView,
762
+ STAGE_COLORS,
763
+ STAGE_LABELS,
764
+ SkillRing,
765
+ Sparkline
766
+ };
767
+ //# sourceMappingURL=dashboard.js.map