htmlgraph 0.24.1__py3-none-any.whl → 0.25.0__py3-none-any.whl
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.
- htmlgraph/__init__.py +20 -1
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/analytics/cross_session.py +4 -3
- htmlgraph/analytics/work_type.py +52 -16
- htmlgraph/analytics_index.py +51 -19
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/main.py +2115 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +812 -0
- htmlgraph/api/templates/dashboard.html +783 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +570 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +509 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +163 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/builders/base.py +55 -1
- htmlgraph/builders/bug.py +17 -2
- htmlgraph/builders/chore.py +17 -2
- htmlgraph/builders/epic.py +17 -2
- htmlgraph/builders/feature.py +25 -2
- htmlgraph/builders/phase.py +17 -2
- htmlgraph/builders/spike.py +27 -2
- htmlgraph/builders/track.py +14 -0
- htmlgraph/cigs/__init__.py +4 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cli.py +1427 -401
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +21 -0
- htmlgraph/collections/session.py +189 -0
- htmlgraph/collections/spike.py +7 -1
- htmlgraph/collections/task_delegation.py +236 -0
- htmlgraph/collections/traces.py +482 -0
- htmlgraph/config.py +113 -0
- htmlgraph/converter.py +41 -0
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +3315 -492
- htmlgraph-0.24.1.data/data/htmlgraph/dashboard.html → htmlgraph/dashboard.html.backup +2246 -248
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1334 -0
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +710 -0
- htmlgraph/docs/README.md +533 -0
- htmlgraph/docs/version_check.py +3 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +2 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/context.py +271 -0
- htmlgraph/hooks/drift_handler.py +521 -0
- htmlgraph/hooks/event_tracker.py +405 -15
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/pretooluse.py +476 -6
- htmlgraph/hooks/prompt_analyzer.py +648 -0
- htmlgraph/hooks/session_handler.py +583 -0
- htmlgraph/hooks/state_manager.py +501 -0
- htmlgraph/hooks/subagent_stop.py +309 -0
- htmlgraph/hooks/task_enforcer.py +39 -0
- htmlgraph/models.py +111 -15
- htmlgraph/operations/fastapi_server.py +230 -0
- htmlgraph/orchestration/headless_spawner.py +22 -14
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/repo_hash.py +511 -0
- htmlgraph/sdk.py +348 -10
- htmlgraph/server.py +194 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +131 -1
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/system_prompts.py +449 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +19 -0
- htmlgraph/validation.py +115 -0
- htmlgraph-0.25.0.data/data/htmlgraph/dashboard.html +7417 -0
- {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/METADATA +91 -64
- {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/RECORD +103 -42
- {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.24.1.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/WHEEL +0 -0
- {htmlgraph-0.24.1.dist-info → htmlgraph-0.25.0.dist-info}/entry_points.txt +0 -0
htmlgraph/dashboard.html
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>HtmlGraph Dashboard</title>
|
|
7
|
-
<!--
|
|
8
|
-
<script src="https://
|
|
9
|
-
<
|
|
10
|
-
<script src="https://d3js.org/d3-timer.v3.min.js"></script>
|
|
11
|
-
<script src="https://d3js.org/d3-force.v3.min.js"></script>
|
|
7
|
+
<!-- Vis.js for graph visualization -->
|
|
8
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
9
|
+
<link href="https://unpkg.com/vis-network/styles/vis-network.min.css" rel="stylesheet" type="text/css" />
|
|
12
10
|
<!-- Typography: JetBrains Mono + Outfit -->
|
|
13
11
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
12
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -106,9 +104,11 @@
|
|
|
106
104
|
background: var(--bg-primary);
|
|
107
105
|
color: var(--text-primary);
|
|
108
106
|
line-height: 1.5;
|
|
109
|
-
|
|
107
|
+
height: 100vh;
|
|
110
108
|
position: relative;
|
|
111
|
-
overflow
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/* Subtle grain texture overlay */
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
max-width: 1440px;
|
|
127
127
|
margin: 0 auto;
|
|
128
128
|
padding: 2rem;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
flex: 1;
|
|
132
|
+
min-height: 0;
|
|
133
|
+
width: 100%;
|
|
134
|
+
overflow: hidden;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/* ================================================================
|
|
@@ -211,6 +217,7 @@
|
|
|
211
217
|
margin-bottom: 1rem;
|
|
212
218
|
border: 2px solid var(--border-strong);
|
|
213
219
|
width: fit-content;
|
|
220
|
+
flex-shrink: 0;
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
.view-btn {
|
|
@@ -252,8 +259,14 @@
|
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
.kanban.active {
|
|
255
|
-
display:
|
|
262
|
+
display: grid;
|
|
256
263
|
width: 100%;
|
|
264
|
+
height: 100%;
|
|
265
|
+
flex: 1;
|
|
266
|
+
min-height: 0;
|
|
267
|
+
overflow: auto;
|
|
268
|
+
grid-auto-rows: minmax(0, 1fr);
|
|
269
|
+
grid-auto-flow: dense;
|
|
257
270
|
}
|
|
258
271
|
|
|
259
272
|
/* Dynamic grid: expanded columns grow, collapsed stay fixed */
|
|
@@ -524,6 +537,7 @@
|
|
|
524
537
|
gap: 1rem;
|
|
525
538
|
width: 100%;
|
|
526
539
|
max-width: 100%;
|
|
540
|
+
grid-column: 1 / -1;
|
|
527
541
|
}
|
|
528
542
|
|
|
529
543
|
.track-section {
|
|
@@ -732,6 +746,11 @@
|
|
|
732
746
|
border-radius: 8px;
|
|
733
747
|
overflow: hidden;
|
|
734
748
|
box-shadow: var(--shadow-md);
|
|
749
|
+
display: flex;
|
|
750
|
+
flex-direction: column;
|
|
751
|
+
grid-column: 1 / -1;
|
|
752
|
+
align-self: stretch;
|
|
753
|
+
min-height: 0;
|
|
735
754
|
}
|
|
736
755
|
|
|
737
756
|
.untracked-header {
|
|
@@ -777,6 +796,7 @@
|
|
|
777
796
|
max-height: 0;
|
|
778
797
|
overflow: hidden;
|
|
779
798
|
transition: max-height 0.4s var(--ease-out-expo);
|
|
799
|
+
flex-grow: 0;
|
|
780
800
|
}
|
|
781
801
|
|
|
782
802
|
.untracked-content.collapsed {
|
|
@@ -784,7 +804,9 @@
|
|
|
784
804
|
}
|
|
785
805
|
|
|
786
806
|
.untracked-section.expanded .untracked-content {
|
|
787
|
-
max-height:
|
|
807
|
+
max-height: none;
|
|
808
|
+
flex-grow: 1;
|
|
809
|
+
overflow-y: auto;
|
|
788
810
|
padding: 1rem;
|
|
789
811
|
}
|
|
790
812
|
|
|
@@ -871,6 +893,80 @@
|
|
|
871
893
|
.badge.priority-high { background: var(--priority-high); color: white; border-color: var(--priority-high); }
|
|
872
894
|
.badge.type { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
|
|
873
895
|
|
|
896
|
+
/* Agent attribution badges - color-coded by agent type */
|
|
897
|
+
.badge.agent {
|
|
898
|
+
padding: 0.375rem 0.625rem;
|
|
899
|
+
font-weight: 600;
|
|
900
|
+
display: inline-flex;
|
|
901
|
+
align-items: center;
|
|
902
|
+
gap: 0.3rem;
|
|
903
|
+
position: relative;
|
|
904
|
+
transition: all 0.2s ease;
|
|
905
|
+
cursor: help;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.badge.agent::before {
|
|
909
|
+
content: '';
|
|
910
|
+
display: inline-block;
|
|
911
|
+
width: 0.5rem;
|
|
912
|
+
height: 0.5rem;
|
|
913
|
+
border-radius: 50%;
|
|
914
|
+
background: currentColor;
|
|
915
|
+
opacity: 0.8;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.badge.agent:hover {
|
|
919
|
+
transform: translateY(-2px);
|
|
920
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/* Primary agents - Requested color system */
|
|
924
|
+
.badge.agent-claude { background: #2979FF; color: white; border-color: #2979FF; }
|
|
925
|
+
.badge.agent-codex { background: #00C853; color: white; border-color: #00C853; }
|
|
926
|
+
.badge.agent-orchestrator { background: #7C4DFF; color: white; border-color: #7C4DFF; }
|
|
927
|
+
.badge.agent-gemini { background: #FBC02D; color: #000; border-color: #FBC02D; }
|
|
928
|
+
.badge.agent-gemini-2 { background: #FF9100; color: white; border-color: #FF9100; }
|
|
929
|
+
|
|
930
|
+
/* Secondary agents - Backward compatibility */
|
|
931
|
+
.badge.agent-analyst { background: #7C3AED; color: white; border-color: #7C3AED; }
|
|
932
|
+
.badge.agent-developer { background: #00C853; color: white; border-color: #00C853; }
|
|
933
|
+
.badge.agent-researcher { background: #FF6D00; color: white; border-color: #FF6D00; }
|
|
934
|
+
.badge.agent-debugger { background: #E91E63; color: white; border-color: #E91E63; }
|
|
935
|
+
.badge.agent-default { background: #78909C; color: white; border-color: #78909C; }
|
|
936
|
+
|
|
937
|
+
/* Delegation badges */
|
|
938
|
+
.badge.delegation { padding: 0.25rem 0.5rem; font-size: 0.55rem; }
|
|
939
|
+
.badge.delegation-external { background: #00C853; color: white; }
|
|
940
|
+
.badge.delegation-fallback { background: #FF9100; color: white; }
|
|
941
|
+
.badge.delegation-direct { background: #2979FF; color: white; }
|
|
942
|
+
|
|
943
|
+
/* Event source badges - Color-coded by data source */
|
|
944
|
+
.badge.source { padding: 0.25rem 0.5rem; font-size: 0.55rem; font-weight: 600; }
|
|
945
|
+
.badge.source-hook { background: #2979FF; color: white; border-color: #2979FF; }
|
|
946
|
+
.badge.source-subagent { background: #7C4DFF; color: white; border-color: #7C4DFF; }
|
|
947
|
+
.badge.source-spike { background: #00C853; color: white; border-color: #00C853; }
|
|
948
|
+
.badge.source-delegation { background: #FF9100; color: white; border-color: #FF9100; }
|
|
949
|
+
|
|
950
|
+
/* Event type indicators */
|
|
951
|
+
.event-source-indicator {
|
|
952
|
+
display: inline-flex;
|
|
953
|
+
align-items: center;
|
|
954
|
+
gap: 0.25rem;
|
|
955
|
+
font-size: 0.65rem;
|
|
956
|
+
color: var(--text-muted);
|
|
957
|
+
}
|
|
958
|
+
.event-source-indicator::before {
|
|
959
|
+
content: '';
|
|
960
|
+
display: inline-block;
|
|
961
|
+
width: 6px;
|
|
962
|
+
height: 6px;
|
|
963
|
+
border-radius: 50%;
|
|
964
|
+
}
|
|
965
|
+
.event-source-indicator.hook::before { background: #2979FF; }
|
|
966
|
+
.event-source-indicator.subagent::before { background: #7C4DFF; }
|
|
967
|
+
.event-source-indicator.spike::before { background: #00C853; }
|
|
968
|
+
.event-source-indicator.delegation::before { background: #FF9100; }
|
|
969
|
+
|
|
874
970
|
.card-path {
|
|
875
971
|
font-family: 'JetBrains Mono', monospace;
|
|
876
972
|
font-size: 0.625rem;
|
|
@@ -887,97 +983,6 @@
|
|
|
887
983
|
margin: 1rem;
|
|
888
984
|
}
|
|
889
985
|
|
|
890
|
-
/* ================================================================
|
|
891
|
-
GRAPH VIEW
|
|
892
|
-
================================================================ */
|
|
893
|
-
.graph-container {
|
|
894
|
-
display: none;
|
|
895
|
-
background: var(--bg-secondary);
|
|
896
|
-
border: 2px solid var(--border-strong);
|
|
897
|
-
box-shadow: var(--shadow-md);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
.graph-container.active {
|
|
901
|
-
display: block;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
.graph-svg {
|
|
905
|
-
width: 100%;
|
|
906
|
-
height: 600px;
|
|
907
|
-
display: block;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
.graph-node {
|
|
911
|
-
cursor: pointer;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
.graph-node circle {
|
|
915
|
-
stroke: var(--border-strong);
|
|
916
|
-
stroke-width: 2;
|
|
917
|
-
transition: all 0.2s var(--ease-out-expo);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
.graph-node:hover circle {
|
|
921
|
-
stroke-width: 4;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
.graph-node text {
|
|
925
|
-
font-family: 'JetBrains Mono', monospace;
|
|
926
|
-
font-size: 8px;
|
|
927
|
-
font-weight: 500;
|
|
928
|
-
fill: white;
|
|
929
|
-
text-anchor: middle;
|
|
930
|
-
pointer-events: none;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
.graph-edge {
|
|
934
|
-
stroke: var(--border);
|
|
935
|
-
stroke-width: 2;
|
|
936
|
-
fill: none;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
.graph-edge.blocked_by {
|
|
940
|
-
stroke: var(--status-blocked);
|
|
941
|
-
stroke-dasharray: 6, 4;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
.graph-edge.related {
|
|
945
|
-
stroke: var(--status-active);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
.graph-arrowhead {
|
|
949
|
-
fill: var(--border);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
.graph-legend {
|
|
953
|
-
display: flex;
|
|
954
|
-
gap: 2rem;
|
|
955
|
-
justify-content: center;
|
|
956
|
-
padding: 1rem;
|
|
957
|
-
border-top: 2px solid var(--border-strong);
|
|
958
|
-
background: var(--bg-tertiary);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
.graph-legend-item {
|
|
962
|
-
display: flex;
|
|
963
|
-
align-items: center;
|
|
964
|
-
gap: 0.5rem;
|
|
965
|
-
font-family: 'JetBrains Mono', monospace;
|
|
966
|
-
font-size: 0.6875rem;
|
|
967
|
-
text-transform: uppercase;
|
|
968
|
-
letter-spacing: 0.1em;
|
|
969
|
-
color: var(--text-muted);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
.graph-legend-item span {
|
|
973
|
-
width: 24px;
|
|
974
|
-
height: 3px;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
.legend-blocked { background: var(--status-blocked); }
|
|
978
|
-
.legend-related { background: var(--status-active); }
|
|
979
|
-
.legend-default { background: var(--border); }
|
|
980
|
-
|
|
981
986
|
/* ================================================================
|
|
982
987
|
ANALYTICS VIEW
|
|
983
988
|
================================================================ */
|
|
@@ -987,7 +992,11 @@
|
|
|
987
992
|
}
|
|
988
993
|
|
|
989
994
|
.analytics.active {
|
|
990
|
-
display:
|
|
995
|
+
display: flex;
|
|
996
|
+
flex-direction: column;
|
|
997
|
+
flex: 1;
|
|
998
|
+
min-height: 0;
|
|
999
|
+
overflow: auto;
|
|
991
1000
|
}
|
|
992
1001
|
|
|
993
1002
|
/* ================================================================
|
|
@@ -999,171 +1008,1127 @@
|
|
|
999
1008
|
}
|
|
1000
1009
|
|
|
1001
1010
|
.sessions.active {
|
|
1002
|
-
display:
|
|
1011
|
+
display: flex;
|
|
1012
|
+
flex-direction: column;
|
|
1013
|
+
flex: 1;
|
|
1014
|
+
min-height: 0;
|
|
1015
|
+
overflow: auto;
|
|
1003
1016
|
}
|
|
1004
1017
|
|
|
1005
1018
|
/* ================================================================
|
|
1006
|
-
|
|
1019
|
+
AGENTS VIEW - Multi-Agent Work Attribution
|
|
1007
1020
|
================================================================ */
|
|
1008
|
-
.
|
|
1021
|
+
.agents {
|
|
1009
1022
|
display: none;
|
|
1023
|
+
margin-top: 1.5rem;
|
|
1024
|
+
padding: 0 1.5rem 1.5rem 1.5rem;
|
|
1010
1025
|
}
|
|
1011
1026
|
|
|
1012
|
-
.
|
|
1013
|
-
display:
|
|
1027
|
+
.agents.active {
|
|
1028
|
+
display: flex;
|
|
1029
|
+
flex-direction: column;
|
|
1030
|
+
flex: 1;
|
|
1031
|
+
min-height: 0;
|
|
1032
|
+
overflow: auto;
|
|
1014
1033
|
}
|
|
1015
1034
|
|
|
1016
|
-
.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
background: var(--bg-secondary);
|
|
1022
|
-
border: 2px solid var(--border-strong);
|
|
1023
|
-
box-shadow: var(--shadow-md);
|
|
1035
|
+
.agent-stats-grid {
|
|
1036
|
+
display: grid;
|
|
1037
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1038
|
+
gap: 1rem;
|
|
1039
|
+
margin: 1rem 0;
|
|
1024
1040
|
}
|
|
1025
1041
|
|
|
1026
|
-
.
|
|
1027
|
-
|
|
1028
|
-
border
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1042
|
+
.agent-stat-card {
|
|
1043
|
+
background: var(--bg-secondary);
|
|
1044
|
+
border: 2px solid var(--border);
|
|
1045
|
+
border-radius: 8px;
|
|
1046
|
+
padding: 1.5rem;
|
|
1047
|
+
text-align: center;
|
|
1048
|
+
box-shadow: var(--shadow-sm);
|
|
1032
1049
|
}
|
|
1033
1050
|
|
|
1034
|
-
.
|
|
1051
|
+
.agent-stat-card h4 {
|
|
1035
1052
|
color: var(--text-muted);
|
|
1036
|
-
font-
|
|
1053
|
+
font-size: 0.75rem;
|
|
1037
1054
|
text-transform: uppercase;
|
|
1038
1055
|
letter-spacing: 0.08em;
|
|
1039
|
-
font-size: 0.625rem;
|
|
1040
|
-
background: var(--bg-tertiary);
|
|
1041
|
-
border-bottom: 2px solid var(--border-strong);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
.sessions-table tr:hover td {
|
|
1045
|
-
background: var(--bg-tertiary);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
.sessions-table .session-id {
|
|
1049
|
-
font-weight: 600;
|
|
1050
|
-
color: var(--status-active);
|
|
1051
|
-
cursor: pointer;
|
|
1052
|
-
text-decoration: underline;
|
|
1053
|
-
text-underline-offset: 2px;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
.sessions-table .session-id:hover {
|
|
1057
|
-
color: var(--accent);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/* Session Filters */
|
|
1061
|
-
.session-filters {
|
|
1062
|
-
display: flex;
|
|
1063
|
-
gap: 1rem;
|
|
1064
|
-
padding: 1rem 1.5rem;
|
|
1065
|
-
background: var(--bg-secondary);
|
|
1066
|
-
border: 2px solid var(--border-strong);
|
|
1067
|
-
box-shadow: var(--shadow-md);
|
|
1068
|
-
margin-bottom: 1rem;
|
|
1069
|
-
flex-wrap: wrap;
|
|
1070
|
-
align-items: flex-end;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
.filter-group {
|
|
1074
|
-
display: flex;
|
|
1075
|
-
flex-direction: column;
|
|
1076
|
-
gap: 0.375rem;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
.filter-group label {
|
|
1080
|
-
font-size: 0.75rem;
|
|
1081
1056
|
font-weight: 600;
|
|
1082
|
-
|
|
1083
|
-
text-transform: uppercase;
|
|
1084
|
-
letter-spacing: 0.05em;
|
|
1057
|
+
margin-bottom: 0.5rem;
|
|
1085
1058
|
}
|
|
1086
1059
|
|
|
1087
|
-
.
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
border: 1px solid var(--border);
|
|
1091
|
-
background: var(--bg-primary);
|
|
1060
|
+
.agent-stat-value {
|
|
1061
|
+
font-size: 1.75rem;
|
|
1062
|
+
font-weight: 700;
|
|
1092
1063
|
color: var(--text-primary);
|
|
1093
|
-
border-radius: 4px;
|
|
1094
|
-
font-size: 0.875rem;
|
|
1095
1064
|
font-family: 'JetBrains Mono', monospace;
|
|
1096
|
-
min-width: 150px;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
.filter-select:focus,
|
|
1100
|
-
.filter-input:focus {
|
|
1101
|
-
outline: none;
|
|
1102
|
-
border-color: var(--accent);
|
|
1103
|
-
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
|
|
1104
1065
|
}
|
|
1105
1066
|
|
|
1106
|
-
.
|
|
1067
|
+
.agent-stat-unit {
|
|
1068
|
+
font-size: 0.75rem;
|
|
1107
1069
|
color: var(--text-muted);
|
|
1108
|
-
|
|
1070
|
+
margin-top: 0.25rem;
|
|
1109
1071
|
}
|
|
1110
1072
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
gap: 1rem;
|
|
1116
|
-
padding: 1.25rem 1.5rem;
|
|
1073
|
+
/* ================================================================
|
|
1074
|
+
WORKLOAD DISTRIBUTION CHART
|
|
1075
|
+
================================================================ */
|
|
1076
|
+
.workload-chart-container {
|
|
1117
1077
|
background: var(--bg-secondary);
|
|
1118
|
-
border: 2px solid var(--border
|
|
1119
|
-
|
|
1120
|
-
|
|
1078
|
+
border: 2px solid var(--border);
|
|
1079
|
+
border-radius: 8px;
|
|
1080
|
+
padding: 1.5rem;
|
|
1081
|
+
box-shadow: var(--shadow-sm);
|
|
1082
|
+
margin-top: 1rem;
|
|
1121
1083
|
}
|
|
1122
1084
|
|
|
1123
|
-
.
|
|
1085
|
+
.workload-chart-header {
|
|
1086
|
+
margin-bottom: 1.5rem;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.workload-chart-header h3 {
|
|
1124
1090
|
font-family: 'JetBrains Mono', monospace;
|
|
1125
|
-
font-size:
|
|
1091
|
+
font-size: 0.875rem;
|
|
1126
1092
|
text-transform: uppercase;
|
|
1127
1093
|
letter-spacing: 0.08em;
|
|
1094
|
+
color: var(--text-muted);
|
|
1128
1095
|
margin-bottom: 0.25rem;
|
|
1096
|
+
font-weight: 600;
|
|
1129
1097
|
}
|
|
1130
1098
|
|
|
1131
|
-
.
|
|
1132
|
-
color: var(--text-
|
|
1099
|
+
.workload-chart-header p {
|
|
1100
|
+
color: var(--text-secondary);
|
|
1133
1101
|
font-size: 0.875rem;
|
|
1134
1102
|
margin: 0;
|
|
1135
1103
|
}
|
|
1136
1104
|
|
|
1137
|
-
.
|
|
1138
|
-
display:
|
|
1139
|
-
|
|
1105
|
+
.workload-bars {
|
|
1106
|
+
display: flex;
|
|
1107
|
+
flex-direction: column;
|
|
1140
1108
|
gap: 1rem;
|
|
1109
|
+
max-height: 600px;
|
|
1110
|
+
overflow-y: auto;
|
|
1141
1111
|
}
|
|
1142
1112
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
.analytics-card {
|
|
1150
|
-
background: var(--bg-secondary);
|
|
1151
|
-
border: 2px solid var(--border-strong);
|
|
1152
|
-
box-shadow: var(--shadow-md);
|
|
1153
|
-
padding: 1rem 1.25rem;
|
|
1113
|
+
.workload-bar-group {
|
|
1114
|
+
display: flex;
|
|
1115
|
+
flex-direction: column;
|
|
1116
|
+
gap: 0.375rem;
|
|
1154
1117
|
}
|
|
1155
1118
|
|
|
1156
|
-
.
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1119
|
+
.workload-bar-label {
|
|
1120
|
+
display: flex;
|
|
1121
|
+
justify-content: space-between;
|
|
1122
|
+
align-items: center;
|
|
1123
|
+
font-size: 0.875rem;
|
|
1124
|
+
font-weight: 500;
|
|
1125
|
+
color: var(--text-primary);
|
|
1126
|
+
margin-bottom: 0.25rem;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.workload-bar-label-name {
|
|
1130
|
+
display: flex;
|
|
1131
|
+
align-items: center;
|
|
1132
|
+
gap: 0.5rem;
|
|
1133
|
+
flex: 1;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.workload-agent-badge {
|
|
1137
|
+
display: inline-flex;
|
|
1138
|
+
align-items: center;
|
|
1139
|
+
justify-content: center;
|
|
1140
|
+
width: 24px;
|
|
1141
|
+
height: 24px;
|
|
1142
|
+
border-radius: 50%;
|
|
1143
|
+
font-size: 0.7rem;
|
|
1144
|
+
font-weight: 600;
|
|
1145
|
+
color: white;
|
|
1146
|
+
flex-shrink: 0;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.workload-bar-value {
|
|
1150
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1151
|
+
font-size: 0.8rem;
|
|
1152
|
+
color: var(--text-muted);
|
|
1153
|
+
white-space: nowrap;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.workload-bar-container {
|
|
1157
|
+
position: relative;
|
|
1158
|
+
width: 100%;
|
|
1159
|
+
height: 32px;
|
|
1160
|
+
background: var(--bg-tertiary);
|
|
1161
|
+
border: 1px solid var(--border);
|
|
1162
|
+
border-radius: 4px;
|
|
1163
|
+
overflow: hidden;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.workload-bar-fill {
|
|
1167
|
+
height: 100%;
|
|
1168
|
+
display: flex;
|
|
1169
|
+
align-items: center;
|
|
1170
|
+
padding: 0 0.75rem;
|
|
1171
|
+
transition: all 0.3s var(--ease-out-expo);
|
|
1172
|
+
position: relative;
|
|
1173
|
+
justify-content: flex-start;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.workload-bar-fill::after {
|
|
1177
|
+
content: '';
|
|
1178
|
+
position: absolute;
|
|
1179
|
+
inset: 0;
|
|
1180
|
+
background: linear-gradient(90deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 100%);
|
|
1181
|
+
pointer-events: none;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.workload-bar-text {
|
|
1185
|
+
position: absolute;
|
|
1186
|
+
right: 0.75rem;
|
|
1187
|
+
top: 50%;
|
|
1188
|
+
transform: translateY(-50%);
|
|
1189
|
+
color: white;
|
|
1190
|
+
font-size: 0.75rem;
|
|
1191
|
+
font-weight: 600;
|
|
1192
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1193
|
+
white-space: nowrap;
|
|
1194
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
1195
|
+
pointer-events: none;
|
|
1196
|
+
z-index: 2;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
.workload-bar-hover-info {
|
|
1200
|
+
position: absolute;
|
|
1201
|
+
bottom: 100%;
|
|
1202
|
+
left: 50%;
|
|
1203
|
+
transform: translateX(-50%);
|
|
1204
|
+
background: var(--bg-tertiary);
|
|
1205
|
+
border: 1px solid var(--border-strong);
|
|
1206
|
+
border-radius: 4px;
|
|
1207
|
+
padding: 0.75rem;
|
|
1208
|
+
font-size: 0.75rem;
|
|
1209
|
+
white-space: nowrap;
|
|
1210
|
+
pointer-events: none;
|
|
1211
|
+
opacity: 0;
|
|
1212
|
+
visibility: hidden;
|
|
1213
|
+
transition: all 0.2s;
|
|
1214
|
+
z-index: 100;
|
|
1215
|
+
margin-bottom: 0.5rem;
|
|
1216
|
+
box-shadow: var(--shadow-md);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.workload-bar-container:hover .workload-bar-hover-info {
|
|
1220
|
+
opacity: 1;
|
|
1221
|
+
visibility: visible;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.workload-bar-hover-info::after {
|
|
1225
|
+
content: '';
|
|
1226
|
+
position: absolute;
|
|
1227
|
+
top: 100%;
|
|
1228
|
+
left: 50%;
|
|
1229
|
+
transform: translateX(-50%);
|
|
1230
|
+
border: 6px solid transparent;
|
|
1231
|
+
border-top-color: var(--border-strong);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
.workload-chart-legend {
|
|
1235
|
+
display: flex;
|
|
1236
|
+
flex-wrap: wrap;
|
|
1237
|
+
gap: 1.5rem;
|
|
1238
|
+
margin-top: 1.5rem;
|
|
1239
|
+
padding-top: 1.5rem;
|
|
1240
|
+
border-top: 1px solid var(--border);
|
|
1241
|
+
font-size: 0.875rem;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.workload-legend-item {
|
|
1245
|
+
display: flex;
|
|
1246
|
+
align-items: center;
|
|
1247
|
+
gap: 0.5rem;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.workload-legend-color {
|
|
1251
|
+
width: 16px;
|
|
1252
|
+
height: 16px;
|
|
1253
|
+
border-radius: 3px;
|
|
1254
|
+
flex-shrink: 0;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.workload-legend-label {
|
|
1258
|
+
color: var(--text-secondary);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/* Agent Color System */
|
|
1262
|
+
.agent-claude { background: linear-gradient(135deg, #6366f1, #818cf8); }
|
|
1263
|
+
.agent-codex { background: linear-gradient(135deg, #10b981, #34d399); }
|
|
1264
|
+
.agent-orchestrator { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
|
|
1265
|
+
.agent-gemini-2 { background: linear-gradient(135deg, #8b5cf6, #a78bfa); }
|
|
1266
|
+
.agent-gemini { background: linear-gradient(135deg, #ec4899, #f472b6); }
|
|
1267
|
+
.agent-analyst { background: linear-gradient(135deg, #0ea5e9, #38bdf8); }
|
|
1268
|
+
.agent-developer { background: linear-gradient(135deg, #06b6d4, #22d3ee); }
|
|
1269
|
+
|
|
1270
|
+
/* ================================================================
|
|
1271
|
+
AGENT COST VISUALIZATION
|
|
1272
|
+
================================================================ */
|
|
1273
|
+
.cost-breakdown-container {
|
|
1274
|
+
background: var(--bg-secondary);
|
|
1275
|
+
border: 2px solid var(--border);
|
|
1276
|
+
border-radius: 8px;
|
|
1277
|
+
padding: 1.5rem;
|
|
1278
|
+
box-shadow: var(--shadow-sm);
|
|
1279
|
+
margin-top: 1rem;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.cost-breakdown-header {
|
|
1283
|
+
margin-bottom: 1.5rem;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
.cost-breakdown-header h3 {
|
|
1287
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1288
|
+
font-size: 0.875rem;
|
|
1289
|
+
text-transform: uppercase;
|
|
1290
|
+
letter-spacing: 0.08em;
|
|
1291
|
+
color: var(--text-muted);
|
|
1292
|
+
margin-bottom: 0.25rem;
|
|
1293
|
+
font-weight: 600;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.cost-breakdown-header p {
|
|
1297
|
+
color: var(--text-secondary);
|
|
1298
|
+
font-size: 0.875rem;
|
|
1299
|
+
margin: 0;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.cost-summary-metrics {
|
|
1303
|
+
display: grid;
|
|
1304
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
1305
|
+
gap: 1rem;
|
|
1306
|
+
margin-bottom: 1.5rem;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.cost-metric {
|
|
1310
|
+
background: var(--bg-tertiary);
|
|
1311
|
+
border: 1px solid var(--border);
|
|
1312
|
+
border-radius: 4px;
|
|
1313
|
+
padding: 1rem;
|
|
1314
|
+
text-align: center;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
.cost-metric-label {
|
|
1318
|
+
font-size: 0.7rem;
|
|
1319
|
+
text-transform: uppercase;
|
|
1320
|
+
letter-spacing: 0.08em;
|
|
1321
|
+
color: var(--text-muted);
|
|
1322
|
+
margin-bottom: 0.5rem;
|
|
1323
|
+
font-weight: 600;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.cost-metric-value {
|
|
1327
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1328
|
+
font-size: 1.5rem;
|
|
1329
|
+
font-weight: 700;
|
|
1330
|
+
color: var(--text-primary);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.cost-metric-unit {
|
|
1334
|
+
font-size: 0.65rem;
|
|
1335
|
+
color: var(--text-muted);
|
|
1336
|
+
margin-top: 0.25rem;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.cost-bars {
|
|
1340
|
+
display: flex;
|
|
1341
|
+
flex-direction: column;
|
|
1342
|
+
gap: 1.25rem;
|
|
1343
|
+
max-height: 600px;
|
|
1344
|
+
overflow-y: auto;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.cost-bar-group {
|
|
1348
|
+
display: flex;
|
|
1349
|
+
flex-direction: column;
|
|
1350
|
+
gap: 0.375rem;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.cost-bar-label {
|
|
1354
|
+
display: flex;
|
|
1355
|
+
justify-content: space-between;
|
|
1356
|
+
align-items: center;
|
|
1357
|
+
font-size: 0.875rem;
|
|
1358
|
+
font-weight: 500;
|
|
1359
|
+
color: var(--text-primary);
|
|
1360
|
+
margin-bottom: 0.375rem;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
.cost-bar-label-name {
|
|
1364
|
+
display: flex;
|
|
1365
|
+
align-items: center;
|
|
1366
|
+
gap: 0.5rem;
|
|
1367
|
+
flex: 1;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.cost-agent-badge {
|
|
1371
|
+
display: inline-flex;
|
|
1372
|
+
align-items: center;
|
|
1373
|
+
justify-content: center;
|
|
1374
|
+
width: 24px;
|
|
1375
|
+
height: 24px;
|
|
1376
|
+
border-radius: 50%;
|
|
1377
|
+
font-size: 0.7rem;
|
|
1378
|
+
font-weight: 600;
|
|
1379
|
+
color: white;
|
|
1380
|
+
flex-shrink: 0;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.cost-bar-stats {
|
|
1384
|
+
display: flex;
|
|
1385
|
+
gap: 1rem;
|
|
1386
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1387
|
+
font-size: 0.75rem;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
.cost-bar-stat {
|
|
1391
|
+
display: flex;
|
|
1392
|
+
flex-direction: column;
|
|
1393
|
+
gap: 0.125rem;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
.cost-bar-stat-label {
|
|
1397
|
+
color: var(--text-muted);
|
|
1398
|
+
font-size: 0.65rem;
|
|
1399
|
+
text-transform: uppercase;
|
|
1400
|
+
letter-spacing: 0.05em;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.cost-bar-stat-value {
|
|
1404
|
+
color: var(--text-primary);
|
|
1405
|
+
font-weight: 600;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
.cost-bar-container {
|
|
1409
|
+
position: relative;
|
|
1410
|
+
width: 100%;
|
|
1411
|
+
height: 40px;
|
|
1412
|
+
background: var(--bg-tertiary);
|
|
1413
|
+
border: 1px solid var(--border);
|
|
1414
|
+
border-radius: 4px;
|
|
1415
|
+
overflow: hidden;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.cost-bar-stacked {
|
|
1419
|
+
display: flex;
|
|
1420
|
+
height: 100%;
|
|
1421
|
+
width: 100%;
|
|
1422
|
+
position: relative;
|
|
1423
|
+
overflow: hidden;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
.cost-bar-segment {
|
|
1427
|
+
height: 100%;
|
|
1428
|
+
display: flex;
|
|
1429
|
+
align-items: center;
|
|
1430
|
+
justify-content: center;
|
|
1431
|
+
position: relative;
|
|
1432
|
+
transition: all 0.3s var(--ease-out-expo);
|
|
1433
|
+
flex-grow: 1;
|
|
1434
|
+
min-width: 2px;
|
|
1435
|
+
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
.cost-bar-segment:last-child {
|
|
1439
|
+
border-right: none;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.cost-bar-segment::after {
|
|
1443
|
+
content: '';
|
|
1444
|
+
position: absolute;
|
|
1445
|
+
inset: 0;
|
|
1446
|
+
background: linear-gradient(90deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 100%);
|
|
1447
|
+
pointer-events: none;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
.cost-bar-segment-label {
|
|
1451
|
+
position: relative;
|
|
1452
|
+
z-index: 2;
|
|
1453
|
+
font-size: 0.65rem;
|
|
1454
|
+
font-weight: 600;
|
|
1455
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1456
|
+
color: white;
|
|
1457
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
1458
|
+
white-space: nowrap;
|
|
1459
|
+
padding: 0 0.3rem;
|
|
1460
|
+
pointer-events: none;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.cost-bar-tooltip {
|
|
1464
|
+
position: absolute;
|
|
1465
|
+
bottom: 100%;
|
|
1466
|
+
left: 50%;
|
|
1467
|
+
transform: translateX(-50%);
|
|
1468
|
+
background: var(--bg-tertiary);
|
|
1469
|
+
border: 1px solid var(--border-strong);
|
|
1470
|
+
border-radius: 4px;
|
|
1471
|
+
padding: 0.75rem;
|
|
1472
|
+
font-size: 0.75rem;
|
|
1473
|
+
pointer-events: none;
|
|
1474
|
+
opacity: 0;
|
|
1475
|
+
visibility: hidden;
|
|
1476
|
+
transition: all 0.2s;
|
|
1477
|
+
z-index: 100;
|
|
1478
|
+
margin-bottom: 0.5rem;
|
|
1479
|
+
box-shadow: var(--shadow-md);
|
|
1480
|
+
white-space: nowrap;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
.cost-bar-segment:hover ~ .cost-bar-tooltip,
|
|
1484
|
+
.cost-bar-container:hover .cost-bar-tooltip {
|
|
1485
|
+
opacity: 1;
|
|
1486
|
+
visibility: visible;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
.cost-bar-tooltip::after {
|
|
1490
|
+
content: '';
|
|
1491
|
+
position: absolute;
|
|
1492
|
+
top: 100%;
|
|
1493
|
+
left: 50%;
|
|
1494
|
+
transform: translateX(-50%);
|
|
1495
|
+
border: 6px solid transparent;
|
|
1496
|
+
border-top-color: var(--border-strong);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.cost-range-indicator {
|
|
1500
|
+
display: flex;
|
|
1501
|
+
align-items: center;
|
|
1502
|
+
gap: 0.5rem;
|
|
1503
|
+
font-size: 0.7rem;
|
|
1504
|
+
color: var(--text-muted);
|
|
1505
|
+
margin-top: 0.25rem;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.cost-range-dot {
|
|
1509
|
+
width: 8px;
|
|
1510
|
+
height: 8px;
|
|
1511
|
+
border-radius: 50%;
|
|
1512
|
+
flex-shrink: 0;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.cost-range-dot.low { background: #10b981; }
|
|
1516
|
+
.cost-range-dot.medium { background: #f59e0b; }
|
|
1517
|
+
.cost-range-dot.high { background: #ef4444; }
|
|
1518
|
+
|
|
1519
|
+
.cost-breakdown-legend {
|
|
1520
|
+
display: flex;
|
|
1521
|
+
flex-wrap: wrap;
|
|
1522
|
+
gap: 1.5rem;
|
|
1523
|
+
margin-top: 1.5rem;
|
|
1524
|
+
padding-top: 1.5rem;
|
|
1525
|
+
border-top: 1px solid var(--border);
|
|
1526
|
+
font-size: 0.875rem;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
.cost-legend-item {
|
|
1530
|
+
display: flex;
|
|
1531
|
+
align-items: center;
|
|
1532
|
+
gap: 0.5rem;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.cost-legend-color {
|
|
1536
|
+
width: 16px;
|
|
1537
|
+
height: 16px;
|
|
1538
|
+
border-radius: 3px;
|
|
1539
|
+
flex-shrink: 0;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.cost-legend-label {
|
|
1543
|
+
color: var(--text-secondary);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/* Agent cost colors - match agent system */
|
|
1547
|
+
.cost-claude { background: #2979FF; }
|
|
1548
|
+
.cost-codex { background: #00C853; }
|
|
1549
|
+
.cost-orchestrator { background: #7C4DFF; }
|
|
1550
|
+
.cost-gemini { background: #FBC02D; }
|
|
1551
|
+
.cost-gemini-2 { background: #FF9100; }
|
|
1552
|
+
.agent-researcher { background: linear-gradient(135deg, #d946ef, #e879f9); }
|
|
1553
|
+
.agent-debugger { background: linear-gradient(135deg, #ef4444, #f87171); }
|
|
1554
|
+
.agent-default { background: linear-gradient(135deg, #6b7280, #9ca3af); }
|
|
1555
|
+
|
|
1556
|
+
.workload-empty-state {
|
|
1557
|
+
text-align: center;
|
|
1558
|
+
padding: 2rem;
|
|
1559
|
+
color: var(--text-muted);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
.workload-empty-state svg {
|
|
1563
|
+
width: 48px;
|
|
1564
|
+
height: 48px;
|
|
1565
|
+
margin: 0 auto 1rem;
|
|
1566
|
+
opacity: 0.5;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
.workload-chart-responsive {
|
|
1570
|
+
max-height: 500px;
|
|
1571
|
+
overflow-y: auto;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
@media (max-width: 768px) {
|
|
1575
|
+
.workload-bar-group {
|
|
1576
|
+
gap: 0.25rem;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.workload-bar-label {
|
|
1580
|
+
font-size: 0.8rem;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.workload-bar-text {
|
|
1584
|
+
font-size: 0.65rem;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.workload-chart-legend {
|
|
1588
|
+
gap: 1rem;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
.workload-bars {
|
|
1592
|
+
max-height: 400px;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/* Skills Matrix Styles */
|
|
1597
|
+
.skills-matrix-container {
|
|
1598
|
+
background: var(--bg-secondary);
|
|
1599
|
+
border: 2px solid var(--border);
|
|
1600
|
+
border-radius: 8px;
|
|
1601
|
+
padding: 1.5rem;
|
|
1602
|
+
box-shadow: var(--shadow-sm);
|
|
1603
|
+
overflow-x: auto;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.skills-matrix {
|
|
1607
|
+
display: grid;
|
|
1608
|
+
grid-template-columns: 150px repeat(auto-fit, minmax(100px, 1fr));
|
|
1609
|
+
gap: 0;
|
|
1610
|
+
min-width: 600px;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
.skills-matrix-cell {
|
|
1614
|
+
display: flex;
|
|
1615
|
+
align-items: center;
|
|
1616
|
+
justify-content: center;
|
|
1617
|
+
padding: 0.75rem;
|
|
1618
|
+
border: 1px solid var(--border);
|
|
1619
|
+
font-size: 0.85rem;
|
|
1620
|
+
min-height: 50px;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.skills-matrix-header-row {
|
|
1624
|
+
position: sticky;
|
|
1625
|
+
top: 0;
|
|
1626
|
+
background: var(--bg-tertiary);
|
|
1627
|
+
font-weight: 600;
|
|
1628
|
+
text-transform: uppercase;
|
|
1629
|
+
letter-spacing: 0.05em;
|
|
1630
|
+
color: var(--text-muted);
|
|
1631
|
+
font-size: 0.75rem;
|
|
1632
|
+
z-index: 10;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.skills-matrix-agent-name {
|
|
1636
|
+
position: sticky;
|
|
1637
|
+
left: 0;
|
|
1638
|
+
background: var(--bg-tertiary);
|
|
1639
|
+
font-weight: 600;
|
|
1640
|
+
color: var(--text-primary);
|
|
1641
|
+
text-align: left;
|
|
1642
|
+
z-index: 11;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
.skills-matrix-skill-label {
|
|
1646
|
+
writing-mode: horizontal-tb;
|
|
1647
|
+
white-space: nowrap;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.proficiency-dot {
|
|
1651
|
+
display: inline-flex;
|
|
1652
|
+
align-items: center;
|
|
1653
|
+
justify-content: center;
|
|
1654
|
+
width: 28px;
|
|
1655
|
+
height: 28px;
|
|
1656
|
+
border-radius: 50%;
|
|
1657
|
+
font-size: 0.7rem;
|
|
1658
|
+
font-weight: 600;
|
|
1659
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
1660
|
+
cursor: help;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
.proficiency-dot:hover {
|
|
1664
|
+
transform: scale(1.15);
|
|
1665
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.proficiency-1 {
|
|
1669
|
+
background: #fee5e5;
|
|
1670
|
+
color: #8b0000;
|
|
1671
|
+
border: 1px solid #d4a5a5;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
.proficiency-2 {
|
|
1675
|
+
background: #ffcccb;
|
|
1676
|
+
color: #660000;
|
|
1677
|
+
border: 1px solid #c97c7c;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
.proficiency-3 {
|
|
1681
|
+
background: #ffb366;
|
|
1682
|
+
color: #5a3a00;
|
|
1683
|
+
border: 1px solid #cc8844;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.proficiency-4 {
|
|
1687
|
+
background: #99ff99;
|
|
1688
|
+
color: #1a4d1a;
|
|
1689
|
+
border: 1px solid #66cc66;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
.proficiency-5 {
|
|
1693
|
+
background: #00cc00;
|
|
1694
|
+
color: #ffffff;
|
|
1695
|
+
border: 1px solid #009900;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
.skill-category-legend {
|
|
1699
|
+
display: flex;
|
|
1700
|
+
gap: 1.5rem;
|
|
1701
|
+
flex-wrap: wrap;
|
|
1702
|
+
margin-top: 1.5rem;
|
|
1703
|
+
padding-top: 1.5rem;
|
|
1704
|
+
border-top: 1px solid var(--border);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
.skill-category-item {
|
|
1708
|
+
display: flex;
|
|
1709
|
+
align-items: center;
|
|
1710
|
+
gap: 0.5rem;
|
|
1711
|
+
font-size: 0.875rem;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.skill-category-icon {
|
|
1715
|
+
display: inline-block;
|
|
1716
|
+
width: 16px;
|
|
1717
|
+
height: 16px;
|
|
1718
|
+
border-radius: 3px;
|
|
1719
|
+
background: var(--accent);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
.skill-tooltip {
|
|
1723
|
+
position: absolute;
|
|
1724
|
+
background: var(--bg-tertiary);
|
|
1725
|
+
border: 1px solid var(--border-strong);
|
|
1726
|
+
border-radius: 4px;
|
|
1727
|
+
padding: 0.5rem 0.75rem;
|
|
1728
|
+
font-size: 0.75rem;
|
|
1729
|
+
white-space: nowrap;
|
|
1730
|
+
pointer-events: none;
|
|
1731
|
+
z-index: 1000;
|
|
1732
|
+
box-shadow: var(--shadow-md);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/* ================================================================
|
|
1736
|
+
TRACKS VIEW
|
|
1737
|
+
================================================================ */
|
|
1738
|
+
.tracks {
|
|
1739
|
+
display: none;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.tracks.active {
|
|
1743
|
+
display: block;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.sessions-table {
|
|
1747
|
+
width: 100%;
|
|
1748
|
+
border-collapse: collapse;
|
|
1749
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1750
|
+
font-size: 0.875rem;
|
|
1751
|
+
background: var(--bg-secondary);
|
|
1752
|
+
border: 2px solid var(--border-strong);
|
|
1753
|
+
box-shadow: var(--shadow-md);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.sessions-table th,
|
|
1757
|
+
.sessions-table td {
|
|
1758
|
+
border-bottom: 1px solid var(--border);
|
|
1759
|
+
padding: 0.875rem 1rem;
|
|
1760
|
+
text-align: left;
|
|
1761
|
+
vertical-align: middle;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.sessions-table th {
|
|
1765
|
+
color: var(--text-muted);
|
|
1766
|
+
font-weight: 600;
|
|
1767
|
+
text-transform: uppercase;
|
|
1768
|
+
letter-spacing: 0.08em;
|
|
1769
|
+
font-size: 0.625rem;
|
|
1770
|
+
background: var(--bg-tertiary);
|
|
1771
|
+
border-bottom: 2px solid var(--border-strong);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.sessions-table tr:hover td {
|
|
1775
|
+
background: var(--bg-tertiary);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
.sessions-table .session-id {
|
|
1779
|
+
font-weight: 600;
|
|
1780
|
+
color: var(--status-active);
|
|
1781
|
+
cursor: pointer;
|
|
1782
|
+
text-decoration: underline;
|
|
1783
|
+
text-underline-offset: 2px;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
.sessions-table .session-id:hover {
|
|
1787
|
+
color: var(--accent);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
/* Session Filters */
|
|
1791
|
+
.session-filters {
|
|
1792
|
+
display: flex;
|
|
1793
|
+
gap: 1rem;
|
|
1794
|
+
padding: 1rem 1.5rem;
|
|
1795
|
+
background: var(--bg-secondary);
|
|
1796
|
+
border: 2px solid var(--border-strong);
|
|
1797
|
+
box-shadow: var(--shadow-md);
|
|
1798
|
+
margin-bottom: 1rem;
|
|
1799
|
+
flex-wrap: wrap;
|
|
1800
|
+
align-items: flex-end;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
.filter-group {
|
|
1804
|
+
display: flex;
|
|
1805
|
+
flex-direction: column;
|
|
1806
|
+
gap: 0.375rem;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.filter-group label {
|
|
1810
|
+
font-size: 0.75rem;
|
|
1811
|
+
font-weight: 600;
|
|
1812
|
+
color: var(--text-muted);
|
|
1813
|
+
text-transform: uppercase;
|
|
1814
|
+
letter-spacing: 0.05em;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.filter-select,
|
|
1818
|
+
.filter-input {
|
|
1819
|
+
padding: 0.5rem 0.75rem;
|
|
1820
|
+
border: 1px solid var(--border);
|
|
1821
|
+
background: var(--bg-primary);
|
|
1822
|
+
color: var(--text-primary);
|
|
1823
|
+
border-radius: 4px;
|
|
1824
|
+
font-size: 0.875rem;
|
|
1825
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1826
|
+
min-width: 150px;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
.filter-select:focus,
|
|
1830
|
+
.filter-input:focus {
|
|
1831
|
+
outline: none;
|
|
1832
|
+
border-color: var(--accent);
|
|
1833
|
+
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
.filter-input::placeholder {
|
|
1837
|
+
color: var(--text-muted);
|
|
1838
|
+
opacity: 0.6;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
.analytics-header {
|
|
1842
|
+
display: flex;
|
|
1843
|
+
align-items: flex-start;
|
|
1844
|
+
justify-content: space-between;
|
|
1845
|
+
gap: 1rem;
|
|
1846
|
+
padding: 1.25rem 1.5rem;
|
|
1847
|
+
background: var(--bg-secondary);
|
|
1848
|
+
border: 2px solid var(--border-strong);
|
|
1849
|
+
box-shadow: var(--shadow-md);
|
|
1850
|
+
margin-bottom: 1rem;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
.analytics-header h2 {
|
|
1854
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1855
|
+
font-size: 1rem;
|
|
1856
|
+
text-transform: uppercase;
|
|
1857
|
+
letter-spacing: 0.08em;
|
|
1858
|
+
margin-bottom: 0.25rem;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
.analytics-header p {
|
|
1862
|
+
color: var(--text-muted);
|
|
1863
|
+
font-size: 0.875rem;
|
|
1864
|
+
margin: 0;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.analytics-grid {
|
|
1868
|
+
display: grid;
|
|
1869
|
+
grid-template-columns: 1fr;
|
|
1870
|
+
gap: 1.5rem;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/* Timeline is always full width and first */
|
|
1874
|
+
.analytics-grid > #analytics-timeline {
|
|
1875
|
+
grid-column: 1 / -1;
|
|
1876
|
+
order: -10;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
/* Summary metrics grid: 3 columns */
|
|
1880
|
+
.analytics-grid > #analytics-summary {
|
|
1881
|
+
grid-column: 1 / -1;
|
|
1882
|
+
order: -9;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
/* Collaboration metrics: 2 columns */
|
|
1886
|
+
.analytics-grid > #analytics-collaboration {
|
|
1887
|
+
grid-column: 1 / -1;
|
|
1888
|
+
order: -8;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/* Feature and tool analysis: 2 columns each */
|
|
1892
|
+
.analytics-grid > #analytics-features,
|
|
1893
|
+
.analytics-grid > #analytics-tools {
|
|
1894
|
+
order: -7;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/* Commit DAG: full width */
|
|
1898
|
+
.analytics-grid > #analytics-commit-dag {
|
|
1899
|
+
grid-column: 1 / -1;
|
|
1900
|
+
order: -6;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
@media (max-width: 1200px) {
|
|
1904
|
+
.analytics-grid {
|
|
1905
|
+
grid-template-columns: 1fr;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
@media (max-width: 768px) {
|
|
1910
|
+
.analytics-grid {
|
|
1911
|
+
gap: 1rem;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.analytics-card {
|
|
1916
|
+
background: var(--bg-secondary);
|
|
1917
|
+
border: 2px solid var(--border-strong);
|
|
1918
|
+
box-shadow: var(--shadow-md);
|
|
1919
|
+
padding: 1rem 1.25rem;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
.analytics-card h3 {
|
|
1923
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1924
|
+
font-size: 0.75rem;
|
|
1925
|
+
text-transform: uppercase;
|
|
1160
1926
|
letter-spacing: 0.1em;
|
|
1161
1927
|
color: var(--text-muted);
|
|
1162
1928
|
margin-bottom: 0.75rem;
|
|
1163
1929
|
}
|
|
1164
1930
|
|
|
1165
|
-
.analytics-card-wide {
|
|
1166
|
-
grid-column: 1 / -1;
|
|
1931
|
+
.analytics-card-wide {
|
|
1932
|
+
grid-column: 1 / -1;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/* Timeline Section */
|
|
1936
|
+
.analytics-timeline {
|
|
1937
|
+
background: var(--bg-secondary);
|
|
1938
|
+
border: 2px solid var(--border-strong);
|
|
1939
|
+
box-shadow: var(--shadow-md);
|
|
1940
|
+
padding: 1.5rem;
|
|
1941
|
+
position: relative;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
.timeline-header {
|
|
1945
|
+
display: flex;
|
|
1946
|
+
align-items: center;
|
|
1947
|
+
justify-content: space-between;
|
|
1948
|
+
margin-bottom: 1.5rem;
|
|
1949
|
+
padding-bottom: 1rem;
|
|
1950
|
+
border-bottom: 1px solid var(--border);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
.timeline-header h3 {
|
|
1954
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1955
|
+
font-size: 0.875rem;
|
|
1956
|
+
text-transform: uppercase;
|
|
1957
|
+
letter-spacing: 0.1em;
|
|
1958
|
+
color: var(--text-muted);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.timeline-header .timeline-legend {
|
|
1962
|
+
display: flex;
|
|
1963
|
+
gap: 1.5rem;
|
|
1964
|
+
font-size: 0.75rem;
|
|
1965
|
+
color: var(--text-secondary);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
.timeline-legend-item {
|
|
1969
|
+
display: flex;
|
|
1970
|
+
align-items: center;
|
|
1971
|
+
gap: 0.5rem;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
.timeline-legend-dot {
|
|
1975
|
+
width: 10px;
|
|
1976
|
+
height: 10px;
|
|
1977
|
+
border-radius: 50%;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
.timeline-container {
|
|
1981
|
+
display: flex;
|
|
1982
|
+
flex-direction: column;
|
|
1983
|
+
gap: 1rem;
|
|
1984
|
+
max-height: 400px;
|
|
1985
|
+
overflow-y: auto;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
.timeline-entry {
|
|
1989
|
+
display: flex;
|
|
1990
|
+
gap: 1rem;
|
|
1991
|
+
padding: 0.875rem;
|
|
1992
|
+
background: var(--bg-tertiary);
|
|
1993
|
+
border: 1px solid var(--border);
|
|
1994
|
+
border-radius: 4px;
|
|
1995
|
+
transition: all 0.2s var(--ease-out-expo);
|
|
1996
|
+
cursor: pointer;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
.timeline-entry:hover {
|
|
2000
|
+
background: var(--bg-secondary);
|
|
2001
|
+
border-color: var(--accent);
|
|
2002
|
+
box-shadow: 0 0 0 2px rgba(205, 255, 0, 0.1);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
.timeline-entry-marker {
|
|
2006
|
+
flex-shrink: 0;
|
|
2007
|
+
width: 12px;
|
|
2008
|
+
height: 12px;
|
|
2009
|
+
border-radius: 50%;
|
|
2010
|
+
margin-top: 3px;
|
|
2011
|
+
border: 2px solid var(--border-strong);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
.timeline-entry-marker.session { background: var(--status-active); }
|
|
2015
|
+
.timeline-entry-marker.feature { background: var(--accent); }
|
|
2016
|
+
.timeline-entry-marker.commit { background: var(--status-done); }
|
|
2017
|
+
.timeline-entry-marker.error { background: var(--status-blocked); }
|
|
2018
|
+
|
|
2019
|
+
.timeline-entry-content {
|
|
2020
|
+
flex: 1;
|
|
2021
|
+
min-width: 0;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
.timeline-entry-title {
|
|
2025
|
+
font-size: 0.875rem;
|
|
2026
|
+
font-weight: 600;
|
|
2027
|
+
color: var(--text-primary);
|
|
2028
|
+
margin-bottom: 0.25rem;
|
|
2029
|
+
white-space: nowrap;
|
|
2030
|
+
overflow: hidden;
|
|
2031
|
+
text-overflow: ellipsis;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
.timeline-entry-meta {
|
|
2035
|
+
display: flex;
|
|
2036
|
+
gap: 1rem;
|
|
2037
|
+
font-size: 0.75rem;
|
|
2038
|
+
color: var(--text-muted);
|
|
2039
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/* Summary/Health Indicators */
|
|
2043
|
+
.analytics-summary {
|
|
2044
|
+
display: grid;
|
|
2045
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
2046
|
+
gap: 1rem;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.health-card {
|
|
2050
|
+
background: var(--bg-secondary);
|
|
2051
|
+
border: 2px solid var(--border-strong);
|
|
2052
|
+
box-shadow: var(--shadow-md);
|
|
2053
|
+
padding: 1.5rem;
|
|
2054
|
+
position: relative;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
.health-card::before {
|
|
2058
|
+
content: '';
|
|
2059
|
+
position: absolute;
|
|
2060
|
+
top: 0;
|
|
2061
|
+
left: 0;
|
|
2062
|
+
right: 0;
|
|
2063
|
+
height: 4px;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
.health-card.health-good::before { background: var(--status-done); }
|
|
2067
|
+
.health-card.health-ok::before { background: var(--priority-high); }
|
|
2068
|
+
.health-card.health-poor::before { background: var(--status-blocked); }
|
|
2069
|
+
|
|
2070
|
+
.health-label {
|
|
2071
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2072
|
+
font-size: 0.625rem;
|
|
2073
|
+
text-transform: uppercase;
|
|
2074
|
+
letter-spacing: 0.1em;
|
|
2075
|
+
color: var(--text-muted);
|
|
2076
|
+
margin-bottom: 0.5rem;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
.health-value {
|
|
2080
|
+
font-size: 2rem;
|
|
2081
|
+
font-weight: 700;
|
|
2082
|
+
color: var(--text-primary);
|
|
2083
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
.health-detail {
|
|
2087
|
+
font-size: 0.75rem;
|
|
2088
|
+
color: var(--text-secondary);
|
|
2089
|
+
margin-top: 0.75rem;
|
|
2090
|
+
padding-top: 0.75rem;
|
|
2091
|
+
border-top: 1px solid var(--border);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/* Collaboration Metrics */
|
|
2095
|
+
.collaboration-metrics {
|
|
2096
|
+
background: var(--bg-secondary);
|
|
2097
|
+
border: 2px solid var(--border-strong);
|
|
2098
|
+
box-shadow: var(--shadow-md);
|
|
2099
|
+
padding: 1.5rem;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
.collaboration-grid {
|
|
2103
|
+
display: grid;
|
|
2104
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
2105
|
+
gap: 1rem;
|
|
2106
|
+
margin-bottom: 1.5rem;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
.collab-stat {
|
|
2110
|
+
display: flex;
|
|
2111
|
+
flex-direction: column;
|
|
2112
|
+
gap: 0.5rem;
|
|
2113
|
+
padding: 1rem;
|
|
2114
|
+
background: var(--bg-tertiary);
|
|
2115
|
+
border: 1px solid var(--border);
|
|
2116
|
+
border-radius: 4px;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
.collab-stat-value {
|
|
2120
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2121
|
+
font-size: 1.5rem;
|
|
2122
|
+
font-weight: 700;
|
|
2123
|
+
color: var(--text-primary);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
.collab-stat-label {
|
|
2127
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2128
|
+
font-size: 0.65rem;
|
|
2129
|
+
text-transform: uppercase;
|
|
2130
|
+
letter-spacing: 0.08em;
|
|
2131
|
+
color: var(--text-muted);
|
|
1167
2132
|
}
|
|
1168
2133
|
|
|
1169
2134
|
/* Commit DAG Visualization */
|
|
@@ -1272,26 +2237,44 @@
|
|
|
1272
2237
|
|
|
1273
2238
|
.analytics-kpis {
|
|
1274
2239
|
display: grid;
|
|
1275
|
-
grid-template-columns: repeat(
|
|
1276
|
-
gap:
|
|
2240
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
2241
|
+
gap: 1rem;
|
|
1277
2242
|
}
|
|
1278
2243
|
|
|
1279
2244
|
@media (max-width: 700px) {
|
|
1280
2245
|
.analytics-kpis {
|
|
1281
|
-
grid-template-columns: 1fr;
|
|
2246
|
+
grid-template-columns: repeat(2, 1fr);
|
|
1282
2247
|
}
|
|
1283
2248
|
}
|
|
1284
2249
|
|
|
1285
2250
|
.kpi {
|
|
1286
|
-
border:
|
|
1287
|
-
background: var(--bg-
|
|
1288
|
-
padding:
|
|
2251
|
+
border: 2px solid var(--border-strong);
|
|
2252
|
+
background: var(--bg-secondary);
|
|
2253
|
+
padding: 1.25rem;
|
|
2254
|
+
box-shadow: var(--shadow-sm);
|
|
2255
|
+
position: relative;
|
|
2256
|
+
overflow: hidden;
|
|
1289
2257
|
}
|
|
1290
2258
|
|
|
2259
|
+
.kpi::before {
|
|
2260
|
+
content: '';
|
|
2261
|
+
position: absolute;
|
|
2262
|
+
top: 0;
|
|
2263
|
+
left: 0;
|
|
2264
|
+
right: 0;
|
|
2265
|
+
height: 3px;
|
|
2266
|
+
background: var(--accent);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
.kpi.healthy::before { background: var(--status-done); }
|
|
2270
|
+
.kpi.warning::before { background: var(--priority-high); }
|
|
2271
|
+
.kpi.critical::before { background: var(--priority-critical); }
|
|
2272
|
+
|
|
1291
2273
|
.kpi .kpi-value {
|
|
1292
2274
|
font-family: 'JetBrains Mono', monospace;
|
|
1293
|
-
font-size:
|
|
2275
|
+
font-size: 2rem;
|
|
1294
2276
|
font-weight: 700;
|
|
2277
|
+
color: var(--text-primary);
|
|
1295
2278
|
}
|
|
1296
2279
|
|
|
1297
2280
|
.kpi .kpi-label {
|
|
@@ -1300,7 +2283,7 @@
|
|
|
1300
2283
|
text-transform: uppercase;
|
|
1301
2284
|
letter-spacing: 0.1em;
|
|
1302
2285
|
color: var(--text-muted);
|
|
1303
|
-
margin-top: 0.
|
|
2286
|
+
margin-top: 0.5rem;
|
|
1304
2287
|
}
|
|
1305
2288
|
|
|
1306
2289
|
.table {
|
|
@@ -1532,6 +2515,38 @@
|
|
|
1532
2515
|
border-bottom: none;
|
|
1533
2516
|
}
|
|
1534
2517
|
|
|
2518
|
+
/* Parent events with children */
|
|
2519
|
+
.activity-item.parent-event {
|
|
2520
|
+
cursor: pointer;
|
|
2521
|
+
font-weight: 500;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
.activity-item.parent-event .expand-icon {
|
|
2525
|
+
display: inline-block;
|
|
2526
|
+
margin-right: 0.5rem;
|
|
2527
|
+
transition: transform 0.2s;
|
|
2528
|
+
font-size: 0.75rem;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
.activity-item.parent-event .expand-icon::before {
|
|
2532
|
+
content: '▶';
|
|
2533
|
+
color: var(--text-muted);
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
.activity-item.parent-event.expanded .expand-icon::before {
|
|
2537
|
+
content: '▼';
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
/* Child events styling */
|
|
2541
|
+
.activity-item.child-event {
|
|
2542
|
+
background-color: rgba(205, 255, 0, 0.02);
|
|
2543
|
+
margin-left: 0;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
.activity-item.child-event:hover {
|
|
2547
|
+
background-color: rgba(205, 255, 0, 0.05);
|
|
2548
|
+
}
|
|
2549
|
+
|
|
1535
2550
|
.activity-meta {
|
|
1536
2551
|
display: flex;
|
|
1537
2552
|
align-items: center;
|
|
@@ -1592,6 +2607,47 @@
|
|
|
1592
2607
|
font-family: 'JetBrains Mono', monospace;
|
|
1593
2608
|
}
|
|
1594
2609
|
|
|
2610
|
+
/* Delegations */
|
|
2611
|
+
.delegations-list {
|
|
2612
|
+
display: flex;
|
|
2613
|
+
flex-direction: column;
|
|
2614
|
+
gap: 0.75rem;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
.delegation-item {
|
|
2618
|
+
padding: 0.75rem;
|
|
2619
|
+
border: 1px solid var(--border);
|
|
2620
|
+
background: var(--bg-tertiary);
|
|
2621
|
+
border-radius: 2px;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
.delegation-meta {
|
|
2625
|
+
display: flex;
|
|
2626
|
+
gap: 0.5rem;
|
|
2627
|
+
flex-wrap: wrap;
|
|
2628
|
+
align-items: center;
|
|
2629
|
+
margin-bottom: 0.5rem;
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
.delegation-task {
|
|
2633
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2634
|
+
font-size: 0.75rem;
|
|
2635
|
+
color: var(--text-secondary);
|
|
2636
|
+
margin-bottom: 0.375rem;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
.delegation-time {
|
|
2640
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2641
|
+
font-size: 0.65rem;
|
|
2642
|
+
color: var(--text-muted);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
.mono {
|
|
2646
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2647
|
+
font-size: 0.75rem;
|
|
2648
|
+
color: var(--text-secondary);
|
|
2649
|
+
}
|
|
2650
|
+
|
|
1595
2651
|
/* Session Activity Preview */
|
|
1596
2652
|
.session-preview {
|
|
1597
2653
|
background: var(--bg-tertiary);
|
|
@@ -1642,42 +2698,350 @@
|
|
|
1642
2698
|
gap: 0.5rem;
|
|
1643
2699
|
padding: 0.375rem 0.75rem;
|
|
1644
2700
|
font-size: 0.75rem;
|
|
1645
|
-
border-bottom: 1px solid var(--border);
|
|
2701
|
+
border-bottom: 1px solid var(--border);
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
.activity-entry:last-child {
|
|
2705
|
+
border-bottom: none;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
.activity-tool {
|
|
2709
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2710
|
+
font-weight: 600;
|
|
2711
|
+
color: var(--status-active);
|
|
2712
|
+
min-width: 70px;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
.activity-tool.Edit { color: var(--priority-high); }
|
|
2716
|
+
.activity-tool.Bash { color: var(--status-done); }
|
|
2717
|
+
.activity-tool.Read { color: var(--text-muted); }
|
|
2718
|
+
.activity-tool.UserQuery { color: var(--priority-critical); }
|
|
2719
|
+
|
|
2720
|
+
.activity-summary {
|
|
2721
|
+
color: var(--text-secondary);
|
|
2722
|
+
flex: 1;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
.activity-time {
|
|
2726
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2727
|
+
font-size: 0.625rem;
|
|
2728
|
+
color: var(--text-muted);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
.drift-warning {
|
|
2732
|
+
color: var(--priority-high);
|
|
2733
|
+
font-size: 0.625rem;
|
|
2734
|
+
font-size: 0.6875rem;
|
|
2735
|
+
color: var(--text-muted);
|
|
2736
|
+
margin-left: 0.5rem;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
/* Activity Feed Table View */
|
|
2740
|
+
.activity-list.table-view {
|
|
2741
|
+
flex: 1;
|
|
2742
|
+
min-height: 0;
|
|
2743
|
+
overflow: auto;
|
|
2744
|
+
background: var(--bg-secondary);
|
|
2745
|
+
border: 1px solid var(--border);
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
.activity-table {
|
|
2749
|
+
width: 100%;
|
|
2750
|
+
border-collapse: collapse;
|
|
2751
|
+
font-size: 0.8125rem;
|
|
2752
|
+
background: var(--bg-secondary);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
.activity-table thead {
|
|
2756
|
+
position: sticky;
|
|
2757
|
+
top: 0;
|
|
2758
|
+
background: var(--bg-tertiary);
|
|
2759
|
+
border-bottom: 2px solid var(--border-strong);
|
|
2760
|
+
z-index: 10;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
.activity-table th {
|
|
2764
|
+
padding: 0.875rem 1rem;
|
|
2765
|
+
text-align: left;
|
|
2766
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2767
|
+
font-weight: 600;
|
|
2768
|
+
font-size: 0.6875rem;
|
|
2769
|
+
text-transform: uppercase;
|
|
2770
|
+
letter-spacing: 0.05em;
|
|
2771
|
+
color: var(--text-secondary);
|
|
2772
|
+
white-space: nowrap;
|
|
2773
|
+
border-right: 1px solid var(--border);
|
|
2774
|
+
user-select: none;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
.activity-table th:last-child {
|
|
2778
|
+
border-right: none;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
.activity-table th.col-timestamp {
|
|
2782
|
+
cursor: pointer;
|
|
2783
|
+
transition: background 0.2s;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
.activity-table th.col-timestamp:hover {
|
|
2787
|
+
background: var(--bg-secondary);
|
|
2788
|
+
color: var(--accent);
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
.activity-table tbody tr {
|
|
2792
|
+
border-bottom: 1px solid var(--border);
|
|
2793
|
+
transition: background-color 0.2s;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
.activity-table tbody tr:hover {
|
|
2797
|
+
background-color: var(--bg-tertiary);
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
/* Alternating row colors for readability */
|
|
2801
|
+
.activity-table tbody tr:nth-child(even) {
|
|
2802
|
+
background-color: var(--bg-primary);
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
.activity-table tbody tr:nth-child(even):hover {
|
|
2806
|
+
background-color: var(--bg-tertiary);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
/* Parent rows slightly emphasized */
|
|
2810
|
+
.activity-table tbody tr.parent-row {
|
|
2811
|
+
font-weight: 500;
|
|
2812
|
+
border-left: 3px solid var(--accent);
|
|
2813
|
+
cursor: pointer;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
/* Expand icon for parent rows */
|
|
2817
|
+
.activity-table tbody tr.parent-row .expand-icon {
|
|
2818
|
+
display: inline-block;
|
|
2819
|
+
margin-right: 0.5rem;
|
|
2820
|
+
transition: transform 0.2s;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
.activity-table tbody tr.parent-row .expand-icon::before {
|
|
2824
|
+
content: '▶';
|
|
2825
|
+
color: var(--text-muted);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
.activity-table tbody tr.parent-row.expanded .expand-icon::before {
|
|
2829
|
+
content: '▼';
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
/* Child rows with visual indent marker */
|
|
2833
|
+
.activity-table tbody tr.child-row {
|
|
2834
|
+
background-color: rgba(205, 255, 0, 0.02);
|
|
2835
|
+
border-left: 3px solid var(--text-muted);
|
|
2836
|
+
display: none;
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
.activity-table tbody tr.child-row.visible {
|
|
2840
|
+
display: table-row;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
.activity-table tbody tr.child-row:hover {
|
|
2844
|
+
background-color: rgba(205, 255, 0, 0.05);
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
/* Indent child row content */
|
|
2848
|
+
.activity-table tbody tr.child-row td:first-child {
|
|
2849
|
+
padding-left: 2.5rem;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
/* Status-based row coloring */
|
|
2853
|
+
.activity-table tbody tr.event-recorded {
|
|
2854
|
+
border-left-color: var(--status-done);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
.activity-table tbody tr.event-pending {
|
|
2858
|
+
border-left-color: var(--priority-medium);
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
.activity-table tbody tr.event-error {
|
|
2862
|
+
border-left-color: var(--status-blocked);
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
.activity-table td {
|
|
2866
|
+
padding: 0.75rem 1rem;
|
|
2867
|
+
color: var(--text-secondary);
|
|
2868
|
+
border-right: 1px solid var(--border);
|
|
2869
|
+
vertical-align: top;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
.activity-table td:last-child {
|
|
2873
|
+
border-right: none;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
/* Column widths */
|
|
2877
|
+
.activity-table .col-timestamp {
|
|
2878
|
+
width: 180px;
|
|
2879
|
+
min-width: 180px;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
.activity-table .col-agent {
|
|
2883
|
+
width: 120px;
|
|
2884
|
+
min-width: 120px;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
.activity-table .col-tool {
|
|
2888
|
+
width: 100px;
|
|
2889
|
+
min-width: 100px;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
.activity-table .col-input {
|
|
2893
|
+
width: 180px;
|
|
2894
|
+
min-width: 180px;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
.activity-table .col-output {
|
|
2898
|
+
width: 180px;
|
|
2899
|
+
min-width: 180px;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
.activity-table .col-status {
|
|
2903
|
+
width: 90px;
|
|
2904
|
+
min-width: 90px;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
.activity-table .col-id {
|
|
2908
|
+
width: 80px;
|
|
2909
|
+
min-width: 80px;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
/* Table cell content styling */
|
|
2913
|
+
.activity-table .event-type-badge {
|
|
2914
|
+
font-size: 1rem;
|
|
2915
|
+
margin-right: 0.5rem;
|
|
2916
|
+
display: inline-block;
|
|
2917
|
+
vertical-align: middle;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
.activity-table .timestamp-text {
|
|
2921
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2922
|
+
font-size: 0.75rem;
|
|
2923
|
+
color: var(--text-muted);
|
|
2924
|
+
white-space: nowrap;
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
.activity-table .agent-badge {
|
|
2928
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2929
|
+
font-size: 0.75rem;
|
|
2930
|
+
background: var(--bg-tertiary);
|
|
2931
|
+
padding: 0.25rem 0.5rem;
|
|
2932
|
+
border-radius: 2px;
|
|
2933
|
+
margin-right: 0.25rem;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
.activity-table .parent-indicator {
|
|
2937
|
+
font-size: 0.875rem;
|
|
2938
|
+
margin-left: 0.25rem;
|
|
2939
|
+
opacity: 0.7;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
.activity-table .child-indicator {
|
|
2943
|
+
font-size: 0.875rem;
|
|
2944
|
+
margin-left: 0.25rem;
|
|
2945
|
+
opacity: 0.6;
|
|
2946
|
+
color: var(--text-muted);
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
.activity-table .tool-name {
|
|
2950
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2951
|
+
font-size: 0.75rem;
|
|
2952
|
+
background: var(--bg-tertiary);
|
|
2953
|
+
padding: 0.25rem 0.5rem;
|
|
2954
|
+
border-radius: 2px;
|
|
2955
|
+
color: var(--status-active);
|
|
2956
|
+
word-break: break-all;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
.activity-table .truncate {
|
|
2960
|
+
display: inline-block;
|
|
2961
|
+
max-width: 100%;
|
|
2962
|
+
white-space: nowrap;
|
|
2963
|
+
overflow: hidden;
|
|
2964
|
+
text-overflow: ellipsis;
|
|
2965
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2966
|
+
font-size: 0.75rem;
|
|
1646
2967
|
}
|
|
1647
2968
|
|
|
1648
|
-
.activity-
|
|
1649
|
-
|
|
2969
|
+
.activity-table .text-muted {
|
|
2970
|
+
color: var(--text-muted);
|
|
2971
|
+
font-style: italic;
|
|
1650
2972
|
}
|
|
1651
2973
|
|
|
1652
|
-
.activity-
|
|
2974
|
+
.activity-table .status-badge {
|
|
2975
|
+
display: inline-block;
|
|
2976
|
+
padding: 0.375rem 0.625rem;
|
|
2977
|
+
border-radius: 2px;
|
|
1653
2978
|
font-family: 'JetBrains Mono', monospace;
|
|
2979
|
+
font-size: 0.65rem;
|
|
1654
2980
|
font-weight: 600;
|
|
1655
|
-
|
|
1656
|
-
|
|
2981
|
+
text-transform: uppercase;
|
|
2982
|
+
letter-spacing: 0.05em;
|
|
1657
2983
|
}
|
|
1658
2984
|
|
|
1659
|
-
.activity-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
2985
|
+
.activity-table .status-badge.status-recorded {
|
|
2986
|
+
background: rgba(0, 200, 83, 0.15);
|
|
2987
|
+
color: var(--status-done);
|
|
2988
|
+
border: 1px solid var(--status-done);
|
|
2989
|
+
}
|
|
1663
2990
|
|
|
1664
|
-
.activity-
|
|
1665
|
-
|
|
1666
|
-
|
|
2991
|
+
.activity-table .status-badge.status-pending {
|
|
2992
|
+
background: rgba(41, 121, 255, 0.15);
|
|
2993
|
+
color: var(--priority-medium);
|
|
2994
|
+
border: 1px solid var(--priority-medium);
|
|
1667
2995
|
}
|
|
1668
2996
|
|
|
1669
|
-
.activity-
|
|
2997
|
+
.activity-table .status-badge.status-error {
|
|
2998
|
+
background: rgba(255, 23, 68, 0.15);
|
|
2999
|
+
color: var(--status-blocked);
|
|
3000
|
+
border: 1px solid var(--status-blocked);
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
.activity-table .event-id-code {
|
|
1670
3004
|
font-family: 'JetBrains Mono', monospace;
|
|
1671
|
-
font-size: 0.
|
|
3005
|
+
font-size: 0.65rem;
|
|
1672
3006
|
color: var(--text-muted);
|
|
3007
|
+
cursor: pointer;
|
|
3008
|
+
transition: color 0.2s;
|
|
3009
|
+
word-break: break-all;
|
|
1673
3010
|
}
|
|
1674
3011
|
|
|
1675
|
-
.
|
|
1676
|
-
color: var(--
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
3012
|
+
.activity-table .event-id-code:hover {
|
|
3013
|
+
color: var(--accent);
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
/* Responsive table scrolling */
|
|
3017
|
+
@media (max-width: 1200px) {
|
|
3018
|
+
.activity-table .col-input,
|
|
3019
|
+
.activity-table .col-output {
|
|
3020
|
+
width: 120px;
|
|
3021
|
+
min-width: 120px;
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
@media (max-width: 768px) {
|
|
3026
|
+
.activity-table {
|
|
3027
|
+
font-size: 0.75rem;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
.activity-table th,
|
|
3031
|
+
.activity-table td {
|
|
3032
|
+
padding: 0.5rem 0.75rem;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
.activity-table .col-timestamp {
|
|
3036
|
+
width: 140px;
|
|
3037
|
+
min-width: 140px;
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
.activity-table .col-input,
|
|
3041
|
+
.activity-table .col-output {
|
|
3042
|
+
width: 100px;
|
|
3043
|
+
min-width: 100px;
|
|
3044
|
+
}
|
|
1681
3045
|
}
|
|
1682
3046
|
|
|
1683
3047
|
/* Content */
|
|
@@ -2005,32 +3369,14 @@
|
|
|
2005
3369
|
<!-- View Toggle -->
|
|
2006
3370
|
<div class="view-toggle">
|
|
2007
3371
|
<button class="view-btn active" data-view="kanban">Work</button>
|
|
2008
|
-
<button class="view-btn" data-view="graph">Graph</button>
|
|
2009
3372
|
<button class="view-btn" data-view="analytics">Analytics</button>
|
|
3373
|
+
<button class="view-btn" data-view="agents">Agents</button>
|
|
2010
3374
|
<button class="view-btn" data-view="sessions">Sessions</button>
|
|
2011
3375
|
</div>
|
|
2012
3376
|
|
|
2013
3377
|
<!-- Features View (Track-Grouped Kanban) -->
|
|
2014
3378
|
<div class="kanban active" id="kanban"></div>
|
|
2015
3379
|
|
|
2016
|
-
<!-- Graph View -->
|
|
2017
|
-
<div class="graph-container" id="graph-container">
|
|
2018
|
-
<svg class="graph-svg" id="graph-svg">
|
|
2019
|
-
<defs>
|
|
2020
|
-
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="55" refY="3.5" orient="auto">
|
|
2021
|
-
<polygon points="0 0, 10 3.5, 0 7" class="graph-arrowhead"/>
|
|
2022
|
-
</marker>
|
|
2023
|
-
</defs>
|
|
2024
|
-
<g id="graph-edges"></g>
|
|
2025
|
-
<g id="graph-nodes"></g>
|
|
2026
|
-
</svg>
|
|
2027
|
-
<div class="graph-legend">
|
|
2028
|
-
<div class="graph-legend-item"><span class="legend-blocked"></span> Blocked by</div>
|
|
2029
|
-
<div class="graph-legend-item"><span class="legend-related"></span> Related</div>
|
|
2030
|
-
<div class="graph-legend-item"><span class="legend-default"></span> Other</div>
|
|
2031
|
-
</div>
|
|
2032
|
-
</div>
|
|
2033
|
-
|
|
2034
3380
|
<!-- Analytics View -->
|
|
2035
3381
|
<div class="analytics" id="analytics">
|
|
2036
3382
|
<div class="analytics-header">
|
|
@@ -2044,22 +3390,76 @@
|
|
|
2044
3390
|
</div>
|
|
2045
3391
|
|
|
2046
3392
|
<div class="analytics-grid">
|
|
3393
|
+
<!-- Timeline: Primary Focus -->
|
|
3394
|
+
<div class="analytics-timeline" id="analytics-timeline">
|
|
3395
|
+
<div class="timeline-header">
|
|
3396
|
+
<h3>Activity Timeline</h3>
|
|
3397
|
+
<div class="timeline-legend">
|
|
3398
|
+
<div class="timeline-legend-item">
|
|
3399
|
+
<div class="timeline-legend-dot" style="background: var(--status-active);"></div>
|
|
3400
|
+
<span>Sessions</span>
|
|
3401
|
+
</div>
|
|
3402
|
+
<div class="timeline-legend-item">
|
|
3403
|
+
<div class="timeline-legend-dot" style="background: var(--accent);"></div>
|
|
3404
|
+
<span>Features</span>
|
|
3405
|
+
</div>
|
|
3406
|
+
<div class="timeline-legend-item">
|
|
3407
|
+
<div class="timeline-legend-dot" style="background: var(--status-done);"></div>
|
|
3408
|
+
<span>Commits</span>
|
|
3409
|
+
</div>
|
|
3410
|
+
</div>
|
|
3411
|
+
</div>
|
|
3412
|
+
<div class="timeline-container" id="timeline-content">
|
|
3413
|
+
<div class="loading">Loading timeline…</div>
|
|
3414
|
+
</div>
|
|
3415
|
+
</div>
|
|
3416
|
+
|
|
3417
|
+
<!-- Summary Section: Key Metrics & Health -->
|
|
3418
|
+
<div class="analytics-card analytics-card-wide" id="analytics-summary">
|
|
3419
|
+
<h3>Summary & Health</h3>
|
|
3420
|
+
<div class="analytics-summary" id="summary-content">
|
|
3421
|
+
<div class="loading">Loading summary…</div>
|
|
3422
|
+
</div>
|
|
3423
|
+
</div>
|
|
3424
|
+
|
|
3425
|
+
<!-- Collaboration Metrics -->
|
|
3426
|
+
<div class="analytics-card analytics-card-wide" id="analytics-collaboration">
|
|
3427
|
+
<div class="collaboration-metrics">
|
|
3428
|
+
<h3>Collaboration & Handoff Metrics</h3>
|
|
3429
|
+
<div class="collaboration-grid" id="collaboration-content">
|
|
3430
|
+
<div class="loading">Loading collaboration metrics…</div>
|
|
3431
|
+
</div>
|
|
3432
|
+
</div>
|
|
3433
|
+
</div>
|
|
3434
|
+
|
|
3435
|
+
<!-- Overview KPIs -->
|
|
2047
3436
|
<div class="analytics-card" id="analytics-overview">
|
|
2048
|
-
<
|
|
3437
|
+
<h3>Overview</h3>
|
|
3438
|
+
<div id="overview-kpis" class="loading">Loading overview…</div>
|
|
2049
3439
|
</div>
|
|
3440
|
+
|
|
3441
|
+
<!-- Tool Patterns -->
|
|
2050
3442
|
<div class="analytics-card" id="analytics-tools">
|
|
2051
3443
|
<div class="loading">Loading tool patterns…</div>
|
|
2052
3444
|
</div>
|
|
3445
|
+
|
|
3446
|
+
<!-- Feature Analysis -->
|
|
2053
3447
|
<div class="analytics-card" id="analytics-features">
|
|
2054
3448
|
<div class="loading">Loading top features…</div>
|
|
2055
3449
|
</div>
|
|
2056
|
-
|
|
3450
|
+
|
|
3451
|
+
<!-- Feature Continuity (hidden until feature selected) -->
|
|
3452
|
+
<div class="analytics-card analytics-card-wide" id="analytics-continuity" style="display: none;">
|
|
2057
3453
|
<div class="loading">Select a feature to see continuity…</div>
|
|
2058
3454
|
</div>
|
|
2059
|
-
|
|
3455
|
+
|
|
3456
|
+
<!-- Feature Commits (hidden until feature selected) -->
|
|
3457
|
+
<div class="analytics-card analytics-card-wide" id="analytics-commits" style="display: none;">
|
|
2060
3458
|
<div class="loading">Select a feature to see commits…</div>
|
|
2061
3459
|
</div>
|
|
2062
|
-
|
|
3460
|
+
|
|
3461
|
+
<!-- Commit DAG (hidden until feature selected) -->
|
|
3462
|
+
<div class="analytics-card analytics-card-wide" id="analytics-commit-dag" style="display: none;">
|
|
2063
3463
|
<div class="loading">Select a feature to see commit graph…</div>
|
|
2064
3464
|
</div>
|
|
2065
3465
|
</div>
|
|
@@ -2115,6 +3515,89 @@
|
|
|
2115
3515
|
<div id="sessions-list" class="loading">Loading sessions...</div>
|
|
2116
3516
|
</div>
|
|
2117
3517
|
|
|
3518
|
+
<!-- Agents View - Multi-Agent Work Attribution -->
|
|
3519
|
+
<div class="agents" id="agents">
|
|
3520
|
+
<div class="analytics-header">
|
|
3521
|
+
<div>
|
|
3522
|
+
<h2>Multi-Agent Work Attribution</h2>
|
|
3523
|
+
<p>Track which agents completed work items and monitor delegation performance.</p>
|
|
3524
|
+
</div>
|
|
3525
|
+
<div style="display:flex; gap:0.5rem; align-items:center;">
|
|
3526
|
+
<button class="btn btn-primary" id="agents-refresh">Refresh</button>
|
|
3527
|
+
</div>
|
|
3528
|
+
</div>
|
|
3529
|
+
|
|
3530
|
+
<!-- Agent Skills Matrix -->
|
|
3531
|
+
<div class="analytics-card analytics-card-wide" id="agent-skills-matrix">
|
|
3532
|
+
<h3>Agent Specializations & Skills Matrix</h3>
|
|
3533
|
+
<p style="color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.5rem;">
|
|
3534
|
+
Proficiency levels based on work history analysis. Color intensity indicates expertise level (1=novice, 5=expert).
|
|
3535
|
+
</p>
|
|
3536
|
+
<div class="skills-matrix-container" id="skills-matrix-content">
|
|
3537
|
+
<div class="loading">Analyzing agent work history...</div>
|
|
3538
|
+
</div>
|
|
3539
|
+
</div>
|
|
3540
|
+
|
|
3541
|
+
<!-- Orchestration & Delegations -->
|
|
3542
|
+
<div class="analytics-card analytics-card-wide" id="orchestration-view">
|
|
3543
|
+
<h3>Orchestration & Delegations</h3>
|
|
3544
|
+
<p style="color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.5rem;">
|
|
3545
|
+
Track agent-to-agent task delegations and coordination patterns.
|
|
3546
|
+
</p>
|
|
3547
|
+
<div id="orchestration-content">
|
|
3548
|
+
<div class="loading">Loading delegation data...</div>
|
|
3549
|
+
</div>
|
|
3550
|
+
</div>
|
|
3551
|
+
|
|
3552
|
+
<!-- Agent Stats Summary -->
|
|
3553
|
+
<div class="analytics-card" id="agent-summary">
|
|
3554
|
+
<div class="loading">Loading agent statistics...</div>
|
|
3555
|
+
</div>
|
|
3556
|
+
|
|
3557
|
+
<!-- Workload Distribution Chart -->
|
|
3558
|
+
<div class="analytics-card analytics-card-wide">
|
|
3559
|
+
<div class="workload-chart-container" id="workload-chart">
|
|
3560
|
+
<div class="workload-chart-header">
|
|
3561
|
+
<h3>Agent Workload Distribution</h3>
|
|
3562
|
+
<p>Horizontal bar chart showing work completion by agent</p>
|
|
3563
|
+
</div>
|
|
3564
|
+
<div id="workload-chart-content" class="loading">Loading workload data...</div>
|
|
3565
|
+
</div>
|
|
3566
|
+
</div>
|
|
3567
|
+
|
|
3568
|
+
<!-- Agent Work Table -->
|
|
3569
|
+
<div class="analytics-card analytics-card-wide" id="agent-work-table">
|
|
3570
|
+
<div class="loading">Loading agent work data...</div>
|
|
3571
|
+
</div>
|
|
3572
|
+
|
|
3573
|
+
<!-- Agent Performance Metrics -->
|
|
3574
|
+
<div class="analytics-card" id="agent-performance">
|
|
3575
|
+
<div class="loading">Loading agent performance metrics...</div>
|
|
3576
|
+
</div>
|
|
3577
|
+
|
|
3578
|
+
<!-- Agent Cost Breakdown -->
|
|
3579
|
+
<div class="analytics-card analytics-card-wide" id="agent-costs">
|
|
3580
|
+
<div class="cost-breakdown-container">
|
|
3581
|
+
<div class="cost-breakdown-header">
|
|
3582
|
+
<h3>Agent Cost Breakdown</h3>
|
|
3583
|
+
<p>Token costs aggregated by agent type with visual distribution</p>
|
|
3584
|
+
</div>
|
|
3585
|
+
|
|
3586
|
+
<div class="cost-summary-metrics" id="cost-metrics">
|
|
3587
|
+
<!-- Populated by JavaScript -->
|
|
3588
|
+
</div>
|
|
3589
|
+
|
|
3590
|
+
<div class="cost-bars" id="cost-bars-container">
|
|
3591
|
+
<!-- Populated by JavaScript -->
|
|
3592
|
+
</div>
|
|
3593
|
+
|
|
3594
|
+
<div class="cost-breakdown-legend" id="cost-legend">
|
|
3595
|
+
<!-- Populated by JavaScript -->
|
|
3596
|
+
</div>
|
|
3597
|
+
</div>
|
|
3598
|
+
</div>
|
|
3599
|
+
</div>
|
|
3600
|
+
|
|
2118
3601
|
</div>
|
|
2119
3602
|
|
|
2120
3603
|
<!-- Detail Panel -->
|
|
@@ -2228,11 +3711,13 @@
|
|
|
2228
3711
|
// =====================================================================
|
|
2229
3712
|
|
|
2230
3713
|
async function loadData() {
|
|
3714
|
+
console.log('[Dashboard] Loading data from:', API);
|
|
2231
3715
|
const [status, query] = await Promise.all([
|
|
2232
3716
|
fetch(`${API}/status`).then(r => r.json()),
|
|
2233
3717
|
fetch(`${API}/query`).then(r => r.json())
|
|
2234
3718
|
]);
|
|
2235
3719
|
allNodes = query.nodes;
|
|
3720
|
+
console.log('[Dashboard] Loaded nodes:', allNodes.length, 'Status:', status);
|
|
2236
3721
|
return { status, nodes: query.nodes };
|
|
2237
3722
|
}
|
|
2238
3723
|
|
|
@@ -2287,6 +3772,131 @@
|
|
|
2287
3772
|
return Number(value).toFixed(2);
|
|
2288
3773
|
}
|
|
2289
3774
|
|
|
3775
|
+
function renderTimeline(overview, features, transitions) {
|
|
3776
|
+
const el = document.getElementById('timeline-content');
|
|
3777
|
+
if (!overview) {
|
|
3778
|
+
el.innerHTML = '<p class="analytics-note">No timeline data available.</p>';
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
// Build timeline entries from overview data
|
|
3783
|
+
const entries = [];
|
|
3784
|
+
|
|
3785
|
+
// Add overview entry (summary event)
|
|
3786
|
+
if (overview.events > 0) {
|
|
3787
|
+
entries.push({
|
|
3788
|
+
type: 'session',
|
|
3789
|
+
title: `${overview.events} events recorded`,
|
|
3790
|
+
meta: `Failure rate: ${formatPercent(overview.failure_rate)}, Avg drift: ${formatFloat(overview.avg_drift)}`
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
// Add top features as timeline entries
|
|
3795
|
+
if (features && features.length > 0) {
|
|
3796
|
+
features.slice(0, 5).forEach(f => {
|
|
3797
|
+
entries.push({
|
|
3798
|
+
type: 'feature',
|
|
3799
|
+
title: f.feature_id,
|
|
3800
|
+
meta: `${f.count} events, ${f.failures} failures`
|
|
3801
|
+
});
|
|
3802
|
+
});
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
// Add top transitions as timeline entries
|
|
3806
|
+
if (transitions && transitions.length > 0) {
|
|
3807
|
+
transitions.slice(0, 3).forEach(t => {
|
|
3808
|
+
entries.push({
|
|
3809
|
+
type: 'session',
|
|
3810
|
+
title: `${t.tool} → ${t.next_tool}`,
|
|
3811
|
+
meta: `${t.count} transitions`
|
|
3812
|
+
});
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
if (entries.length === 0) {
|
|
3817
|
+
el.innerHTML = '<p class="analytics-note">No timeline events to display.</p>';
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
el.innerHTML = entries.map(e => `
|
|
3822
|
+
<div class="timeline-entry" title="${escapeHtml(e.meta)}">
|
|
3823
|
+
<div class="timeline-entry-marker ${e.type}"></div>
|
|
3824
|
+
<div class="timeline-entry-content">
|
|
3825
|
+
<div class="timeline-entry-title">${escapeHtml(e.title)}</div>
|
|
3826
|
+
<div class="timeline-entry-meta">${escapeHtml(e.meta)}</div>
|
|
3827
|
+
</div>
|
|
3828
|
+
</div>
|
|
3829
|
+
`).join('');
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
function renderAnalyticsSummary(overview) {
|
|
3833
|
+
const el = document.getElementById('summary-content');
|
|
3834
|
+
if (!overview) {
|
|
3835
|
+
el.innerHTML = '<p class="analytics-note">No summary data available.</p>';
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
const failureRate = overview.failure_rate || 0;
|
|
3840
|
+
const avgDrift = overview.avg_drift || 0;
|
|
3841
|
+
const eventCount = overview.events || 0;
|
|
3842
|
+
|
|
3843
|
+
// Determine health status
|
|
3844
|
+
const failureHealth = failureRate < 0.05 ? 'health-good' : failureRate < 0.15 ? 'health-ok' : 'health-poor';
|
|
3845
|
+
const driftHealth = avgDrift < 1.0 ? 'health-good' : avgDrift < 2.0 ? 'health-ok' : 'health-poor';
|
|
3846
|
+
const activityHealth = eventCount > 100 ? 'health-good' : eventCount > 20 ? 'health-ok' : 'health-poor';
|
|
3847
|
+
|
|
3848
|
+
el.innerHTML = `
|
|
3849
|
+
<div class="health-card ${failureHealth}">
|
|
3850
|
+
<div class="health-label">Failure Rate</div>
|
|
3851
|
+
<div class="health-value">${formatPercent(failureRate)}</div>
|
|
3852
|
+
<div class="health-detail">${failureRate < 0.05 ? '✓ Excellent' : failureRate < 0.15 ? '⚠ Acceptable' : '✗ Needs attention'}</div>
|
|
3853
|
+
</div>
|
|
3854
|
+
<div class="health-card ${driftHealth}">
|
|
3855
|
+
<div class="health-label">Avg Context Drift</div>
|
|
3856
|
+
<div class="health-value">${formatFloat(avgDrift)}</div>
|
|
3857
|
+
<div class="health-detail">${avgDrift < 1.0 ? '✓ Clean' : avgDrift < 2.0 ? '⚠ Acceptable' : '✗ High drift'}</div>
|
|
3858
|
+
</div>
|
|
3859
|
+
<div class="health-card ${activityHealth}">
|
|
3860
|
+
<div class="health-label">Total Events</div>
|
|
3861
|
+
<div class="health-value">${eventCount}</div>
|
|
3862
|
+
<div class="health-detail">${eventCount > 100 ? '✓ Active' : eventCount > 20 ? '⚠ Some data' : '✗ Minimal activity'}</div>
|
|
3863
|
+
</div>
|
|
3864
|
+
`;
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
function renderCollaborationMetrics(overview) {
|
|
3868
|
+
const el = document.getElementById('collaboration-content');
|
|
3869
|
+
if (!overview) {
|
|
3870
|
+
el.innerHTML = '<p class="analytics-note">No collaboration data available.</p>';
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
// Extract or calculate collaboration metrics
|
|
3875
|
+
const handoffCount = overview.handoffs || 0;
|
|
3876
|
+
const parallelWorkPct = overview.parallel_work_pct || 0;
|
|
3877
|
+
const agentCount = overview.agent_count || 1;
|
|
3878
|
+
const avgSessionDuration = overview.avg_session_duration || 0;
|
|
3879
|
+
|
|
3880
|
+
el.innerHTML = `
|
|
3881
|
+
<div class="collab-stat">
|
|
3882
|
+
<div class="collab-stat-value">${handoffCount}</div>
|
|
3883
|
+
<div class="collab-stat-label">Handoffs</div>
|
|
3884
|
+
</div>
|
|
3885
|
+
<div class="collab-stat">
|
|
3886
|
+
<div class="collab-stat-value">${formatPercent(parallelWorkPct)}</div>
|
|
3887
|
+
<div class="collab-stat-label">Parallel Work</div>
|
|
3888
|
+
</div>
|
|
3889
|
+
<div class="collab-stat">
|
|
3890
|
+
<div class="collab-stat-value">${agentCount}</div>
|
|
3891
|
+
<div class="collab-stat-label">Active Agents</div>
|
|
3892
|
+
</div>
|
|
3893
|
+
<div class="collab-stat">
|
|
3894
|
+
<div class="collab-stat-value">${formatFloat(avgSessionDuration)}</div>
|
|
3895
|
+
<div class="collab-stat-label">Avg Session (min)</div>
|
|
3896
|
+
</div>
|
|
3897
|
+
`;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
2290
3900
|
function renderAnalyticsOverview(overview) {
|
|
2291
3901
|
const el = document.getElementById('analytics-overview');
|
|
2292
3902
|
el.innerHTML = `
|
|
@@ -2696,40 +4306,275 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2696
4306
|
}
|
|
2697
4307
|
|
|
2698
4308
|
async function loadAndRenderAnalyticsBase() {
|
|
4309
|
+
console.log('[Dashboard] Loading analytics from:', `${API}/analytics/`);
|
|
2699
4310
|
const [overview, features, transitions] = await Promise.all([
|
|
2700
|
-
fetchAnalytics('overview'),
|
|
2701
|
-
fetchAnalytics('features', { limit: 50 }).then(r => r.features),
|
|
2702
|
-
fetchAnalytics('transitions', { limit: 25 }).then(r => r.transitions),
|
|
4311
|
+
fetchAnalytics('overview').catch(e => { console.error('[Analytics] overview failed:', e); return null; }),
|
|
4312
|
+
fetchAnalytics('features', { limit: 50 }).then(r => r.features).catch(e => { console.error('[Analytics] features failed:', e); return null; }),
|
|
4313
|
+
fetchAnalytics('transitions', { limit: 25 }).then(r => r.transitions).catch(e => { console.error('[Analytics] transitions failed:', e); return null; }),
|
|
2703
4314
|
]);
|
|
2704
4315
|
|
|
2705
4316
|
analyticsCache.overview = overview;
|
|
2706
4317
|
analyticsCache.features = features;
|
|
2707
4318
|
analyticsCache.transitions = transitions;
|
|
2708
4319
|
analyticsLoadedAt = Date.now();
|
|
4320
|
+
console.log('[Analytics] Loaded - overview:', !!overview, 'features:', features?.length || 0, 'transitions:', transitions?.length || 0);
|
|
2709
4321
|
|
|
4322
|
+
// Render primary analytics views (new card-based layout)
|
|
4323
|
+
renderTimeline(overview, features, transitions);
|
|
4324
|
+
renderAnalyticsSummary(overview);
|
|
4325
|
+
renderCollaborationMetrics(overview);
|
|
4326
|
+
|
|
4327
|
+
// Render detail cards
|
|
2710
4328
|
renderAnalyticsOverview(overview);
|
|
2711
4329
|
renderAnalyticsTools(transitions);
|
|
2712
4330
|
renderAnalyticsFeatures(features);
|
|
2713
4331
|
}
|
|
2714
4332
|
|
|
2715
|
-
async function loadAndRenderContinuity(featureId) {
|
|
2716
|
-
analyticsCache.selectedFeatureId = featureId;
|
|
2717
|
-
const data = await fetchAnalytics('continuity', { feature_id: featureId, limit: 200 });
|
|
2718
|
-
renderAnalyticsContinuity(featureId, data.sessions || []);
|
|
2719
|
-
}
|
|
4333
|
+
async function loadAndRenderContinuity(featureId) {
|
|
4334
|
+
analyticsCache.selectedFeatureId = featureId;
|
|
4335
|
+
const data = await fetchAnalytics('continuity', { feature_id: featureId, limit: 200 });
|
|
4336
|
+
renderAnalyticsContinuity(featureId, data.sessions || []);
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
async function loadAndRenderCommits(featureId) {
|
|
4340
|
+
const data = await fetchAnalytics('commits', { feature_id: featureId, limit: 200 });
|
|
4341
|
+
renderAnalyticsCommits(featureId, data.commits || []);
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
async function loadFeatureAnalytics(featureId) {
|
|
4345
|
+
analyticsCache.selectedFeatureId = featureId;
|
|
4346
|
+
|
|
4347
|
+
// Show the detail sections for selected feature
|
|
4348
|
+
document.getElementById('analytics-continuity').style.display = 'block';
|
|
4349
|
+
document.getElementById('analytics-commits').style.display = 'block';
|
|
4350
|
+
document.getElementById('analytics-commit-dag').style.display = 'block';
|
|
4351
|
+
|
|
4352
|
+
await Promise.all([
|
|
4353
|
+
loadAndRenderContinuity(featureId),
|
|
4354
|
+
loadAndRenderCommits(featureId),
|
|
4355
|
+
loadAndRenderCommitDag(featureId)
|
|
4356
|
+
]);
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
// =====================================================================
|
|
4360
|
+
// Agent Cost Visualization
|
|
4361
|
+
// =====================================================================
|
|
4362
|
+
|
|
4363
|
+
const AGENT_COLORS = {
|
|
4364
|
+
'claude': '#2979FF',
|
|
4365
|
+
'codex': '#00C853',
|
|
4366
|
+
'orchestrator': '#7C4DFF',
|
|
4367
|
+
'gemini': '#FBC02D',
|
|
4368
|
+
'gemini-2': '#FF9100',
|
|
4369
|
+
'analyst': '#0ea5e9',
|
|
4370
|
+
'developer': '#06b6d4',
|
|
4371
|
+
'researcher': '#d946ef',
|
|
4372
|
+
'debugger': '#ef4444',
|
|
4373
|
+
'default': '#78909C'
|
|
4374
|
+
};
|
|
4375
|
+
|
|
4376
|
+
function getAgentColor(agent) {
|
|
4377
|
+
const normalized = (agent || 'default').toLowerCase();
|
|
4378
|
+
return AGENT_COLORS[normalized] || AGENT_COLORS['default'];
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
function formatCost(tokens) {
|
|
4382
|
+
// Approximate cost: $0.003 per 1K input tokens
|
|
4383
|
+
const cost = (tokens / 1000) * 0.003;
|
|
4384
|
+
return cost.toFixed(4);
|
|
4385
|
+
}
|
|
4386
|
+
|
|
4387
|
+
function formatCostDisplay(tokens) {
|
|
4388
|
+
const cost = parseFloat(formatCost(tokens));
|
|
4389
|
+
if (cost === 0) return '$0.00';
|
|
4390
|
+
return `$${cost.toFixed(2)}`;
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
function getCostRange(cost) {
|
|
4394
|
+
// Define ranges: low (< $0.01), medium ($0.01-$0.05), high (> $0.05)
|
|
4395
|
+
const numCost = parseFloat(formatCost(cost));
|
|
4396
|
+
if (numCost < 0.01) return 'low';
|
|
4397
|
+
if (numCost < 0.05) return 'medium';
|
|
4398
|
+
return 'high';
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
function aggregateAgentCosts(sessions) {
|
|
4402
|
+
const costsByAgent = {};
|
|
4403
|
+
let totalCost = 0;
|
|
4404
|
+
|
|
4405
|
+
sessions.forEach(session => {
|
|
4406
|
+
const agent = session.properties?.agent || 'unknown';
|
|
4407
|
+
const tokens = parseInt(session.properties?.total_tokens) || 0;
|
|
4408
|
+
|
|
4409
|
+
if (!costsByAgent[agent]) {
|
|
4410
|
+
costsByAgent[agent] = {
|
|
4411
|
+
agent,
|
|
4412
|
+
totalTokens: 0,
|
|
4413
|
+
operationCount: 0,
|
|
4414
|
+
sessionCount: 0
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
costsByAgent[agent].totalTokens += tokens;
|
|
4419
|
+
costsByAgent[agent].operationCount += parseInt(session.properties?.event_count) || 0;
|
|
4420
|
+
costsByAgent[agent].sessionCount += 1;
|
|
4421
|
+
totalCost += tokens;
|
|
4422
|
+
});
|
|
4423
|
+
|
|
4424
|
+
return {
|
|
4425
|
+
byAgent: Object.values(costsByAgent).sort((a, b) => b.totalTokens - a.totalTokens),
|
|
4426
|
+
totalTokens: totalCost
|
|
4427
|
+
};
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
function renderAgentCostMetrics(costs) {
|
|
4431
|
+
const metricsEl = document.getElementById('cost-metrics');
|
|
4432
|
+
if (!metricsEl || costs.byAgent.length === 0) return;
|
|
4433
|
+
|
|
4434
|
+
const avgCostPerAgent = costs.totalTokens / costs.byAgent.length;
|
|
4435
|
+
const totalCostUSD = formatCostDisplay(costs.totalTokens);
|
|
4436
|
+
const avgCostUSD = formatCostDisplay(avgCostPerAgent);
|
|
4437
|
+
|
|
4438
|
+
const topAgent = costs.byAgent[0];
|
|
4439
|
+
const topAgentPercent = ((topAgent.totalTokens / costs.totalTokens) * 100).toFixed(1);
|
|
4440
|
+
|
|
4441
|
+
metricsEl.innerHTML = `
|
|
4442
|
+
<div class="cost-metric">
|
|
4443
|
+
<div class="cost-metric-label">Total Cost</div>
|
|
4444
|
+
<div class="cost-metric-value">${totalCostUSD}</div>
|
|
4445
|
+
<div class="cost-metric-unit">${costs.totalTokens.toLocaleString()} tokens</div>
|
|
4446
|
+
</div>
|
|
4447
|
+
<div class="cost-metric">
|
|
4448
|
+
<div class="cost-metric-label">Average Per Agent</div>
|
|
4449
|
+
<div class="cost-metric-value">${avgCostUSD}</div>
|
|
4450
|
+
<div class="cost-metric-unit">~${Math.round(avgCostPerAgent).toLocaleString()} tokens</div>
|
|
4451
|
+
</div>
|
|
4452
|
+
<div class="cost-metric">
|
|
4453
|
+
<div class="cost-metric-label">Top Agent</div>
|
|
4454
|
+
<div class="cost-metric-value">${topAgent.agent}</div>
|
|
4455
|
+
<div class="cost-metric-unit">${topAgentPercent}% of total</div>
|
|
4456
|
+
</div>
|
|
4457
|
+
<div class="cost-metric">
|
|
4458
|
+
<div class="cost-metric-label">Agent Count</div>
|
|
4459
|
+
<div class="cost-metric-value">${costs.byAgent.length}</div>
|
|
4460
|
+
<div class="cost-metric-unit">unique agents</div>
|
|
4461
|
+
</div>
|
|
4462
|
+
`;
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
function renderAgentCostBars(costs) {
|
|
4466
|
+
const containerEl = document.getElementById('cost-bars-container');
|
|
4467
|
+
if (!containerEl || costs.byAgent.length === 0) {
|
|
4468
|
+
if (containerEl) containerEl.innerHTML = '<div class="loading">No cost data available</div>';
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
const maxCost = Math.max(...costs.byAgent.map(a => a.totalTokens));
|
|
4473
|
+
|
|
4474
|
+
const barsHTML = costs.byAgent.map(agent => {
|
|
4475
|
+
const percentOfTotal = (agent.totalTokens / costs.totalTokens) * 100;
|
|
4476
|
+
const costUSD = formatCostDisplay(agent.totalTokens);
|
|
4477
|
+
const costRange = getCostRange(agent.totalTokens);
|
|
4478
|
+
const color = getAgentColor(agent.agent);
|
|
4479
|
+
const avgPerSession = Math.round(agent.totalTokens / agent.sessionCount);
|
|
4480
|
+
|
|
4481
|
+
return `
|
|
4482
|
+
<div class="cost-bar-group">
|
|
4483
|
+
<div class="cost-bar-label">
|
|
4484
|
+
<div class="cost-bar-label-name">
|
|
4485
|
+
<div class="cost-agent-badge" style="background: ${color};">
|
|
4486
|
+
${agent.agent.substring(0, 1).toUpperCase()}
|
|
4487
|
+
</div>
|
|
4488
|
+
<span>${agent.agent}</span>
|
|
4489
|
+
</div>
|
|
4490
|
+
<div class="cost-bar-stats">
|
|
4491
|
+
<div class="cost-bar-stat">
|
|
4492
|
+
<div class="cost-bar-stat-label">Cost</div>
|
|
4493
|
+
<div class="cost-bar-stat-value">${costUSD}</div>
|
|
4494
|
+
</div>
|
|
4495
|
+
<div class="cost-bar-stat">
|
|
4496
|
+
<div class="cost-bar-stat-label">%</div>
|
|
4497
|
+
<div class="cost-bar-stat-value">${percentOfTotal.toFixed(1)}%</div>
|
|
4498
|
+
</div>
|
|
4499
|
+
<div class="cost-bar-stat">
|
|
4500
|
+
<div class="cost-bar-stat-label">Tokens</div>
|
|
4501
|
+
<div class="cost-bar-stat-value">${agent.totalTokens.toLocaleString()}</div>
|
|
4502
|
+
</div>
|
|
4503
|
+
</div>
|
|
4504
|
+
</div>
|
|
4505
|
+
|
|
4506
|
+
<div class="cost-bar-container">
|
|
4507
|
+
<div class="cost-bar-stacked">
|
|
4508
|
+
<div class="cost-bar-segment" style="
|
|
4509
|
+
width: 100%;
|
|
4510
|
+
background: ${color};
|
|
4511
|
+
opacity: 0.85;
|
|
4512
|
+
" title="${agent.agent}: ${costUSD}">
|
|
4513
|
+
<span class="cost-bar-segment-label">
|
|
4514
|
+
${percentOfTotal.toFixed(0)}%
|
|
4515
|
+
</span>
|
|
4516
|
+
</div>
|
|
4517
|
+
</div>
|
|
4518
|
+
<div class="cost-bar-tooltip">
|
|
4519
|
+
Sessions: ${agent.sessionCount} | Avg/Session: ${avgPerSession.toLocaleString()} tokens
|
|
4520
|
+
</div>
|
|
4521
|
+
</div>
|
|
4522
|
+
|
|
4523
|
+
<div class="cost-range-indicator">
|
|
4524
|
+
<div class="cost-range-dot ${costRange}"></div>
|
|
4525
|
+
<span>${costRange === 'low' ? 'Low' : costRange === 'medium' ? 'Medium' : 'High'} cost</span>
|
|
4526
|
+
</div>
|
|
4527
|
+
</div>
|
|
4528
|
+
`;
|
|
4529
|
+
}).join('');
|
|
4530
|
+
|
|
4531
|
+
containerEl.innerHTML = barsHTML;
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
function renderAgentCostLegend(costs) {
|
|
4535
|
+
const legendEl = document.getElementById('cost-legend');
|
|
4536
|
+
if (!legendEl || costs.byAgent.length === 0) return;
|
|
4537
|
+
|
|
4538
|
+
const legendItems = costs.byAgent.map(agent => {
|
|
4539
|
+
const color = getAgentColor(agent.agent);
|
|
4540
|
+
return `
|
|
4541
|
+
<div class="cost-legend-item">
|
|
4542
|
+
<div class="cost-legend-color" style="background: ${color};"></div>
|
|
4543
|
+
<span class="cost-legend-label">${agent.agent}</span>
|
|
4544
|
+
</div>
|
|
4545
|
+
`;
|
|
4546
|
+
}).join('');
|
|
4547
|
+
|
|
4548
|
+
legendEl.innerHTML = legendItems;
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
async function loadAndRenderAgentCosts() {
|
|
4552
|
+
const container = document.getElementById('agent-costs');
|
|
4553
|
+
if (!container) return;
|
|
4554
|
+
|
|
4555
|
+
try {
|
|
4556
|
+
// Fetch all sessions to aggregate costs
|
|
4557
|
+
if (allSessions.length === 0) {
|
|
4558
|
+
const response = await fetch(`${API}/sessions`);
|
|
4559
|
+
if (!response.ok) throw new Error('Failed to load sessions');
|
|
4560
|
+
const data = await response.json();
|
|
4561
|
+
allSessions = data.nodes || [];
|
|
4562
|
+
}
|
|
4563
|
+
|
|
4564
|
+
if (allSessions.length === 0) {
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
2720
4567
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
renderAnalyticsCommits(featureId, data.commits || []);
|
|
2724
|
-
}
|
|
4568
|
+
// Aggregate costs by agent
|
|
4569
|
+
const costs = aggregateAgentCosts(allSessions);
|
|
2725
4570
|
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
4571
|
+
// Render visualization
|
|
4572
|
+
renderAgentCostMetrics(costs);
|
|
4573
|
+
renderAgentCostBars(costs);
|
|
4574
|
+
renderAgentCostLegend(costs);
|
|
4575
|
+
} catch (err) {
|
|
4576
|
+
console.error('Error loading agent costs:', err);
|
|
4577
|
+
}
|
|
2733
4578
|
}
|
|
2734
4579
|
|
|
2735
4580
|
async function ensureAnalyticsLoaded(force = false) {
|
|
@@ -2737,6 +4582,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2737
4582
|
if (!force && analyticsCache.overview && !stale) return;
|
|
2738
4583
|
try {
|
|
2739
4584
|
await loadAndRenderAnalyticsBase();
|
|
4585
|
+
await loadAndRenderAgentCosts();
|
|
2740
4586
|
if (!analyticsCache.selectedFeatureId && analyticsCache.features && analyticsCache.features.length) {
|
|
2741
4587
|
await loadFeatureAnalytics(analyticsCache.features[0].feature_id);
|
|
2742
4588
|
}
|
|
@@ -2753,11 +4599,13 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2753
4599
|
try {
|
|
2754
4600
|
// Fetch all sessions from the API (only on first load)
|
|
2755
4601
|
if (allSessions.length === 0) {
|
|
4602
|
+
console.log('[Sessions] Fetching from:', `${API}/sessions`);
|
|
2756
4603
|
const response = await fetch(`${API}/sessions`);
|
|
2757
|
-
if (!response.ok) throw new Error(
|
|
4604
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2758
4605
|
|
|
2759
4606
|
const data = await response.json();
|
|
2760
4607
|
allSessions = data.nodes || [];
|
|
4608
|
+
console.log('[Sessions] Loaded', allSessions.length, 'sessions from API response keys:', Object.keys(data));
|
|
2761
4609
|
|
|
2762
4610
|
// Populate agent dropdown with unique agents
|
|
2763
4611
|
const agents = [...new Set(allSessions.map(s => s.properties?.agent).filter(Boolean))];
|
|
@@ -3240,7 +5088,9 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3240
5088
|
// Sort tracks by feature completion (incomplete first), then by priority
|
|
3241
5089
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
3242
5090
|
const sortedTracks = Array.from(tracked.values())
|
|
3243
|
-
|
|
5091
|
+
// FIXED: Do NOT filter out tracks with null metadata - they still have features to show!
|
|
5092
|
+
// Only filter if there are no features for this track
|
|
5093
|
+
.filter(t => t.features && t.features.length > 0)
|
|
3244
5094
|
.sort((a, b) => {
|
|
3245
5095
|
// Calculate completion percentage for each track
|
|
3246
5096
|
const aTotal = a.features.length;
|
|
@@ -3257,7 +5107,10 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3257
5107
|
}
|
|
3258
5108
|
|
|
3259
5109
|
// Within same completion status, sort by priority
|
|
3260
|
-
|
|
5110
|
+
// Use track priority if available, otherwise default to 'medium'
|
|
5111
|
+
const aPriority = (a.track && a.track.priority) || 'medium';
|
|
5112
|
+
const bPriority = (b.track && b.track.priority) || 'medium';
|
|
5113
|
+
return (priorityOrder[aPriority] || 2) - (priorityOrder[bPriority] || 2);
|
|
3261
5114
|
});
|
|
3262
5115
|
|
|
3263
5116
|
// Render
|
|
@@ -3426,18 +5279,51 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3426
5279
|
<div class="track-column-cards">
|
|
3427
5280
|
${byStatus[status].length === 0
|
|
3428
5281
|
? '<div class="empty-column">No items</div>'
|
|
3429
|
-
: byStatus[status].map(f =>
|
|
5282
|
+
: byStatus[status].map(f => {
|
|
5283
|
+
// Determine agent badge color based on agent name
|
|
5284
|
+
let agentClass = 'agent-default';
|
|
5285
|
+
if (f.agent_assigned) {
|
|
5286
|
+
const agentName = f.agent_assigned.toLowerCase();
|
|
5287
|
+
// Primary agents
|
|
5288
|
+
if (agentName.includes('claude')) agentClass = 'agent-claude';
|
|
5289
|
+
else if (agentName.includes('codex')) agentClass = 'agent-codex';
|
|
5290
|
+
else if (agentName.includes('orchestrator')) agentClass = 'agent-orchestrator';
|
|
5291
|
+
else if (agentName.includes('gemini-2') || agentName.includes('gemini 2')) agentClass = 'agent-gemini-2';
|
|
5292
|
+
else if (agentName.includes('gemini')) agentClass = 'agent-gemini';
|
|
5293
|
+
// Secondary agents (backward compatibility)
|
|
5294
|
+
else if (agentName.includes('analyst')) agentClass = 'agent-analyst';
|
|
5295
|
+
else if (agentName.includes('developer')) agentClass = 'agent-developer';
|
|
5296
|
+
else if (agentName.includes('researcher')) agentClass = 'agent-researcher';
|
|
5297
|
+
else if (agentName.includes('debugger')) agentClass = 'agent-debugger';
|
|
5298
|
+
}
|
|
5299
|
+
// Check if feature has delegations
|
|
5300
|
+
const delegations = (f.properties && f.properties.delegations) || [];
|
|
5301
|
+
const delegationBadges = delegations.length > 0
|
|
5302
|
+
? `<span class="badge delegation" title="Delegated to ${delegations.length} spawner(s)">Delegated: ${delegations.length}</span>`
|
|
5303
|
+
: '';
|
|
5304
|
+
return `
|
|
3430
5305
|
<div class="card priority-${f.priority}"
|
|
3431
5306
|
data-collection="${f._collection}"
|
|
3432
|
-
data-id="${f.id}"
|
|
5307
|
+
data-id="${f.id}"
|
|
5308
|
+
data-agent="${f.agent_assigned || ''}"
|
|
5309
|
+
onclick="toggleCardTimeline(event)">
|
|
5310
|
+
<button class="card-expand-btn" onclick="toggleCardTimeline(event)" title="Toggle agent timeline">▼</button>
|
|
3433
5311
|
<div class="card-title">${f.title}</div>
|
|
3434
5312
|
<div class="card-meta">
|
|
3435
5313
|
<span class="badge priority-${f.priority}">${f.priority}</span>
|
|
3436
5314
|
${f.type !== 'feature' ? `<span class="badge type">${f.type}</span>` : ''}
|
|
5315
|
+
${f.agent_assigned ? `<span class="badge agent ${agentClass}">${f.agent_assigned}</span>` : ''}
|
|
5316
|
+
${delegationBadges}
|
|
3437
5317
|
<span class="card-path">${f._collection}/${f.id}</span>
|
|
3438
5318
|
</div>
|
|
5319
|
+
<div class="card-timeline" data-feature-id="${f.id}">
|
|
5320
|
+
<div class="timeline-header">Agent Timeline</div>
|
|
5321
|
+
<div class="timeline-list" data-loading="true">
|
|
5322
|
+
<div class="timeline-empty">Loading timeline...</div>
|
|
5323
|
+
</div>
|
|
5324
|
+
</div>
|
|
3439
5325
|
</div>
|
|
3440
|
-
`).join('')}
|
|
5326
|
+
`}).join('')}
|
|
3441
5327
|
</div>
|
|
3442
5328
|
</div>
|
|
3443
5329
|
`).join('')}
|
|
@@ -3573,64 +5459,54 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3573
5459
|
const container = document.getElementById('activity-log-container');
|
|
3574
5460
|
|
|
3575
5461
|
try {
|
|
3576
|
-
//
|
|
3577
|
-
const response = await fetch(`${API}
|
|
3578
|
-
if (!response.ok) throw new Error('Failed to load session
|
|
3579
|
-
|
|
3580
|
-
const html = await response.text();
|
|
3581
|
-
const parser = new DOMParser();
|
|
3582
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
3583
|
-
|
|
3584
|
-
// Find the activity log section
|
|
3585
|
-
const activitySection = doc.querySelector('section[data-activity-log]');
|
|
3586
|
-
if (!activitySection) {
|
|
3587
|
-
container.innerHTML = '<div class="loading">No activity log found</div>';
|
|
3588
|
-
return;
|
|
3589
|
-
}
|
|
5462
|
+
// Use Analytics API to get merged events (including child sessions)
|
|
5463
|
+
const response = await fetch(`${API}/analytics/session?id=${sessionId}&limit=100`);
|
|
5464
|
+
if (!response.ok) throw new Error('Failed to load session events');
|
|
3590
5465
|
|
|
3591
|
-
|
|
3592
|
-
const
|
|
3593
|
-
const eventCount = activitySection.querySelector('h3')?.textContent || 'Activity Log';
|
|
5466
|
+
const data = await response.json();
|
|
5467
|
+
const events = data.events || [];
|
|
3594
5468
|
|
|
3595
|
-
if (
|
|
5469
|
+
if (events.length === 0) {
|
|
3596
5470
|
container.innerHTML = '<div class="loading">No events recorded</div>';
|
|
3597
5471
|
return;
|
|
3598
5472
|
}
|
|
3599
5473
|
|
|
3600
|
-
// Render activity log (show first 50 events)
|
|
3601
|
-
const limit = 50;
|
|
3602
|
-
const events = Array.from(activityItems).slice(0, limit);
|
|
3603
|
-
|
|
3604
5474
|
let html_content = `
|
|
3605
5475
|
<div class="activity-header">
|
|
3606
|
-
<strong>${
|
|
3607
|
-
|
|
5476
|
+
<strong>${events.length}</strong>
|
|
5477
|
+
<span>(most recent first)</span>
|
|
3608
5478
|
</div>
|
|
3609
5479
|
<ol class="activity-list" reversed>
|
|
3610
5480
|
`;
|
|
3611
5481
|
|
|
3612
5482
|
events.forEach(item => {
|
|
3613
|
-
const ts = new Date(item.
|
|
3614
|
-
const tool = item.
|
|
3615
|
-
const success = item.
|
|
3616
|
-
const feature = item.
|
|
3617
|
-
const drift = item.
|
|
3618
|
-
const content = item.
|
|
5483
|
+
const ts = new Date(item.ts).toLocaleString();
|
|
5484
|
+
const tool = item.tool || 'Unknown';
|
|
5485
|
+
const success = item.success === 1 || item.success === true;
|
|
5486
|
+
const feature = item.feature_id || '';
|
|
5487
|
+
const drift = item.drift_score ? parseFloat(item.drift_score).toFixed(2) : '';
|
|
5488
|
+
const content = item.summary || '';
|
|
5489
|
+
|
|
5490
|
+
// Identify child events (from sub-sessions)
|
|
5491
|
+
const isChild = item.session_id !== sessionId;
|
|
5492
|
+
const childClass = isChild ? 'child-event' : '';
|
|
5493
|
+
const childBadge = isChild ? '<span class="badge" style="background: var(--bg-tertiary); color: var(--text-muted);">sub-task</span>' : '';
|
|
3619
5494
|
|
|
3620
5495
|
const statusIcon = success ? '✅' : '❌';
|
|
3621
5496
|
const featureBadge = feature ? `<span class="badge">${feature}</span>` : '';
|
|
3622
5497
|
const driftBadge = drift ? `<span class="badge drift-${drift >= 0.7 ? 'high' : 'low'}">drift: ${drift}</span>` : '';
|
|
3623
5498
|
|
|
3624
5499
|
html_content += `
|
|
3625
|
-
<li class="activity-item">
|
|
5500
|
+
<li class="activity-item ${childClass}" style="${isChild ? 'padding-left: 1.5rem; border-left: 2px solid var(--border);' : ''}">
|
|
3626
5501
|
<div class="activity-meta">
|
|
3627
5502
|
<span class="activity-time">${ts}</span>
|
|
3628
5503
|
${statusIcon}
|
|
3629
5504
|
<span class="activity-tool">${tool}</span>
|
|
5505
|
+
${childBadge}
|
|
3630
5506
|
${featureBadge}
|
|
3631
5507
|
${driftBadge}
|
|
3632
5508
|
</div>
|
|
3633
|
-
<div class="activity-content">${content}</div>
|
|
5509
|
+
<div class="activity-content">${escapeHtml(content)}</div>
|
|
3634
5510
|
</li>
|
|
3635
5511
|
`;
|
|
3636
5512
|
});
|
|
@@ -3639,10 +5515,253 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3639
5515
|
container.innerHTML = html_content;
|
|
3640
5516
|
|
|
3641
5517
|
} catch (err) {
|
|
5518
|
+
console.error("Activity log error:", err);
|
|
3642
5519
|
container.innerHTML = `<div class="loading">Error loading activity log: ${err.message}</div>`;
|
|
3643
5520
|
}
|
|
3644
5521
|
}
|
|
3645
5522
|
|
|
5523
|
+
/**
|
|
5524
|
+
* Fetch complete activity feed from all sources (hooks, subagents, spikes).
|
|
5525
|
+
* This provides unified visibility into ALL activity including delegated work.
|
|
5526
|
+
* See GitHub issue #14859 for Claude Code hook limitations.
|
|
5527
|
+
*/
|
|
5528
|
+
async function fetchCompleteActivityFeed(sessionId = null, limit = 100) {
|
|
5529
|
+
const container = document.getElementById('complete-activity-container');
|
|
5530
|
+
if (!container) return;
|
|
5531
|
+
|
|
5532
|
+
try {
|
|
5533
|
+
let url = `${API}/complete-activity-feed?limit=${limit}`;
|
|
5534
|
+
if (sessionId) url += `&session_id=${sessionId}`;
|
|
5535
|
+
|
|
5536
|
+
const response = await fetch(url);
|
|
5537
|
+
if (!response.ok) throw new Error('Failed to load complete activity feed');
|
|
5538
|
+
|
|
5539
|
+
const data = await response.json();
|
|
5540
|
+
const events = data.events || [];
|
|
5541
|
+
const sources = data.sources || {};
|
|
5542
|
+
|
|
5543
|
+
if (events.length === 0) {
|
|
5544
|
+
container.innerHTML = `
|
|
5545
|
+
<div class="loading">
|
|
5546
|
+
No events recorded yet.
|
|
5547
|
+
<div style="font-size: 0.75rem; margin-top: 0.5rem; color: var(--text-muted);">
|
|
5548
|
+
Events are captured via PreToolUse hooks and SubagentStop hooks.
|
|
5549
|
+
</div>
|
|
5550
|
+
</div>
|
|
5551
|
+
`;
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
|
|
5555
|
+
// Build parent-child map
|
|
5556
|
+
const parentMap = new Map();
|
|
5557
|
+
const topLevelEvents = [];
|
|
5558
|
+
|
|
5559
|
+
events.forEach(event => {
|
|
5560
|
+
if (event.parent_event_id) {
|
|
5561
|
+
if (!parentMap.has(event.parent_event_id)) {
|
|
5562
|
+
parentMap.set(event.parent_event_id, []);
|
|
5563
|
+
}
|
|
5564
|
+
parentMap.get(event.parent_event_id).push(event);
|
|
5565
|
+
} else {
|
|
5566
|
+
topLevelEvents.push(event);
|
|
5567
|
+
}
|
|
5568
|
+
});
|
|
5569
|
+
|
|
5570
|
+
// Build source summary
|
|
5571
|
+
const sourceSummary = `
|
|
5572
|
+
<div style="display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap;">
|
|
5573
|
+
<span class="badge source source-hook">Hook Events: ${sources.hook_events || 0}</span>
|
|
5574
|
+
<span class="badge source source-subagent">Subagent: ${sources.delegations || 0}</span>
|
|
5575
|
+
<span class="badge source source-spike">SDK Spikes: ${sources.spike_logs || 0}</span>
|
|
5576
|
+
</div>
|
|
5577
|
+
`;
|
|
5578
|
+
|
|
5579
|
+
let html_content = `
|
|
5580
|
+
<div class="activity-header">
|
|
5581
|
+
<strong>${events.length}</strong>
|
|
5582
|
+
<span>events from all sources (${topLevelEvents.length} top-level)</span>
|
|
5583
|
+
</div>
|
|
5584
|
+
${sourceSummary}
|
|
5585
|
+
<ol class="activity-list" reversed>
|
|
5586
|
+
`;
|
|
5587
|
+
|
|
5588
|
+
// Render helper function
|
|
5589
|
+
function renderEvent(item, isChild = false, depth = 0) {
|
|
5590
|
+
const ts = item.timestamp ? new Date(item.timestamp).toLocaleString() : 'Unknown';
|
|
5591
|
+
const tool = item.tool_name || item.event_type || 'Unknown';
|
|
5592
|
+
const agentId = item.agent_id || 'unknown';
|
|
5593
|
+
const status = item.status || 'recorded';
|
|
5594
|
+
const source = item.source || 'hook_event';
|
|
5595
|
+
const content = item.input_summary || item.output_summary || '';
|
|
5596
|
+
const eventId = item.event_id || '';
|
|
5597
|
+
const hasChildren = parentMap.has(eventId);
|
|
5598
|
+
|
|
5599
|
+
// Source-specific styling
|
|
5600
|
+
let sourceClass = 'hook';
|
|
5601
|
+
let sourceBadge = '<span class="badge source source-hook">HOOK</span>';
|
|
5602
|
+
if (source === 'spike_log') {
|
|
5603
|
+
sourceClass = 'spike';
|
|
5604
|
+
sourceBadge = '<span class="badge source source-spike">SDK</span>';
|
|
5605
|
+
} else if (source === 'delegation' || item.event_type === 'delegation') {
|
|
5606
|
+
sourceClass = 'subagent';
|
|
5607
|
+
sourceBadge = '<span class="badge source source-subagent">SUBAGENT</span>';
|
|
5608
|
+
} else if (item.event_type === 'handoff') {
|
|
5609
|
+
sourceClass = 'delegation';
|
|
5610
|
+
sourceBadge = '<span class="badge source source-delegation">HANDOFF</span>';
|
|
5611
|
+
}
|
|
5612
|
+
|
|
5613
|
+
// Status icon
|
|
5614
|
+
const statusIcon = status === 'completed' ? '✅' :
|
|
5615
|
+
status === 'error' ? '❌' :
|
|
5616
|
+
status === 'subagent_completed' ? '🤖' : '📊';
|
|
5617
|
+
|
|
5618
|
+
// Agent badge
|
|
5619
|
+
const agentClass = agentId.includes('claude') ? 'agent-claude' :
|
|
5620
|
+
agentId.includes('gemini') ? 'agent-gemini' :
|
|
5621
|
+
agentId.includes('codex') ? 'agent-codex' :
|
|
5622
|
+
agentId.includes('subagent') ? 'agent-default' : '';
|
|
5623
|
+
const agentBadge = agentClass ? `<span class="badge agent ${agentClass}">${agentId}</span>` :
|
|
5624
|
+
`<span class="badge">${agentId}</span>`;
|
|
5625
|
+
|
|
5626
|
+
// Add expand icon for parents
|
|
5627
|
+
const expandIcon = hasChildren ? '<span class="expand-icon"></span>' : '';
|
|
5628
|
+
const indent = isChild ? 'padding-left: ' + (1.5 + depth * 1.5) + 'rem;' : '';
|
|
5629
|
+
|
|
5630
|
+
return `
|
|
5631
|
+
<li class="activity-item ${hasChildren ? 'parent-event' : ''} ${isChild ? 'child-event' : ''}"
|
|
5632
|
+
data-event-id="${eventId}"
|
|
5633
|
+
style="border-left: 3px solid ${sourceClass === 'hook' ? '#2979FF' : sourceClass === 'spike' ? '#00C853' : sourceClass === 'subagent' ? '#7C4DFF' : '#FF9100'}; ${indent}">
|
|
5634
|
+
<div class="activity-meta">
|
|
5635
|
+
${expandIcon}
|
|
5636
|
+
<span class="activity-time">${ts}</span>
|
|
5637
|
+
${statusIcon}
|
|
5638
|
+
${sourceBadge}
|
|
5639
|
+
<span class="activity-tool">${tool}</span>
|
|
5640
|
+
${agentBadge}
|
|
5641
|
+
</div>
|
|
5642
|
+
<div class="activity-content">${escapeHtml(content.substring(0, 200))}${content.length > 200 ? '...' : ''}</div>
|
|
5643
|
+
</li>
|
|
5644
|
+
`;
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
// Render events recursively
|
|
5648
|
+
function renderEventTree(eventList, depth = 0) {
|
|
5649
|
+
let html = '';
|
|
5650
|
+
eventList.forEach(item => {
|
|
5651
|
+
html += renderEvent(item, depth > 0, depth);
|
|
5652
|
+
const eventId = item.event_id || '';
|
|
5653
|
+
if (parentMap.has(eventId)) {
|
|
5654
|
+
const children = parentMap.get(eventId);
|
|
5655
|
+
html += renderEventTree(children, depth + 1);
|
|
5656
|
+
}
|
|
5657
|
+
});
|
|
5658
|
+
return html;
|
|
5659
|
+
}
|
|
5660
|
+
|
|
5661
|
+
html_content += renderEventTree(topLevelEvents);
|
|
5662
|
+
html_content += `</ol>`;
|
|
5663
|
+
|
|
5664
|
+
// Add limitation notice
|
|
5665
|
+
html_content += `
|
|
5666
|
+
<div style="margin-top: 1rem; padding: 0.75rem; background: var(--bg-tertiary); border-radius: 4px; font-size: 0.75rem; color: var(--text-muted);">
|
|
5667
|
+
<strong>Note:</strong> Subagent tool activity is not captured (Claude Code limitation).
|
|
5668
|
+
<a href="https://github.com/anthropics/claude-code/issues/14859" target="_blank" style="color: var(--status-active);">See GitHub #14859</a>
|
|
5669
|
+
</div>
|
|
5670
|
+
`;
|
|
5671
|
+
|
|
5672
|
+
container.innerHTML = html_content;
|
|
5673
|
+
|
|
5674
|
+
// Add click handlers for expandable parent events
|
|
5675
|
+
const parentEvents = container.querySelectorAll('.activity-item.parent-event');
|
|
5676
|
+
parentEvents.forEach(parentEl => {
|
|
5677
|
+
parentEl.addEventListener('click', (e) => {
|
|
5678
|
+
// Don't toggle if clicking on a link
|
|
5679
|
+
if (e.target.tagName === 'A') return;
|
|
5680
|
+
|
|
5681
|
+
const eventId = parentEl.dataset.eventId;
|
|
5682
|
+
const isExpanded = parentEl.classList.contains('expanded');
|
|
5683
|
+
|
|
5684
|
+
if (isExpanded) {
|
|
5685
|
+
// Collapse - hide children
|
|
5686
|
+
parentEl.classList.remove('expanded');
|
|
5687
|
+
hideChildren(eventId);
|
|
5688
|
+
} else {
|
|
5689
|
+
// Expand - show direct children only
|
|
5690
|
+
parentEl.classList.add('expanded');
|
|
5691
|
+
showDirectChildren(eventId);
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5694
|
+
});
|
|
5695
|
+
|
|
5696
|
+
// Helper to show direct children
|
|
5697
|
+
function showDirectChildren(parentId) {
|
|
5698
|
+
const allItems = container.querySelectorAll('.activity-item');
|
|
5699
|
+
let foundParent = false;
|
|
5700
|
+
let parentDepth = -1;
|
|
5701
|
+
|
|
5702
|
+
allItems.forEach(item => {
|
|
5703
|
+
if (item.dataset.eventId === parentId) {
|
|
5704
|
+
foundParent = true;
|
|
5705
|
+
// Calculate depth from indent
|
|
5706
|
+
const style = item.getAttribute('style') || '';
|
|
5707
|
+
const match = style.match(/padding-left:\s*([\d.]+)rem/);
|
|
5708
|
+
parentDepth = match ? parseFloat(match[1]) : 0;
|
|
5709
|
+
} else if (foundParent) {
|
|
5710
|
+
const style = item.getAttribute('style') || '';
|
|
5711
|
+
const match = style.match(/padding-left:\s*([\d.]+)rem/);
|
|
5712
|
+
const itemDepth = match ? parseFloat(match[1]) : 0;
|
|
5713
|
+
|
|
5714
|
+
// Stop when we reach same or lower depth (sibling or parent)
|
|
5715
|
+
if (itemDepth <= parentDepth) {
|
|
5716
|
+
foundParent = false;
|
|
5717
|
+
return;
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5720
|
+
// Show only direct children (next depth level)
|
|
5721
|
+
if (itemDepth === parentDepth + 1.5) {
|
|
5722
|
+
item.style.display = 'block';
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
});
|
|
5726
|
+
}
|
|
5727
|
+
|
|
5728
|
+
// Helper to hide all descendants
|
|
5729
|
+
function hideChildren(parentId) {
|
|
5730
|
+
const allItems = container.querySelectorAll('.activity-item');
|
|
5731
|
+
let foundParent = false;
|
|
5732
|
+
let parentDepth = -1;
|
|
5733
|
+
|
|
5734
|
+
allItems.forEach(item => {
|
|
5735
|
+
if (item.dataset.eventId === parentId) {
|
|
5736
|
+
foundParent = true;
|
|
5737
|
+
// Calculate depth from indent
|
|
5738
|
+
const style = item.getAttribute('style') || '';
|
|
5739
|
+
const match = style.match(/padding-left:\s*([\d.]+)rem/);
|
|
5740
|
+
parentDepth = match ? parseFloat(match[1]) : 0;
|
|
5741
|
+
} else if (foundParent) {
|
|
5742
|
+
const style = item.getAttribute('style') || '';
|
|
5743
|
+
const match = style.match(/padding-left:\s*([\d.]+)rem/);
|
|
5744
|
+
const itemDepth = match ? parseFloat(match[1]) : 0;
|
|
5745
|
+
|
|
5746
|
+
// Stop when we reach same or lower depth
|
|
5747
|
+
if (itemDepth <= parentDepth) {
|
|
5748
|
+
foundParent = false;
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
// Hide all descendants and collapse them
|
|
5753
|
+
item.style.display = 'none';
|
|
5754
|
+
item.classList.remove('expanded');
|
|
5755
|
+
}
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
} catch (err) {
|
|
5760
|
+
console.error("Complete activity feed error:", err);
|
|
5761
|
+
container.innerHTML = `<div class="loading">Error loading activity feed: ${err.message}</div>`;
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
|
|
3646
5765
|
async function fetchTranscriptStats(sessionId) {
|
|
3647
5766
|
const container = document.getElementById('transcript-stats-container');
|
|
3648
5767
|
if (!container) return;
|
|
@@ -3731,6 +5850,24 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3731
5850
|
|
|
3732
5851
|
let bodyHtml = '';
|
|
3733
5852
|
|
|
5853
|
+
// Helper function to get agent badge class
|
|
5854
|
+
function getAgentClass(agentName) {
|
|
5855
|
+
if (!agentName) return 'agent-default';
|
|
5856
|
+
const name = agentName.toLowerCase();
|
|
5857
|
+
// Primary agents
|
|
5858
|
+
if (name.includes('claude')) return 'agent-claude';
|
|
5859
|
+
if (name.includes('codex')) return 'agent-codex';
|
|
5860
|
+
if (name.includes('orchestrator')) return 'agent-orchestrator';
|
|
5861
|
+
if (name.includes('gemini-2') || name.includes('gemini 2')) return 'agent-gemini-2';
|
|
5862
|
+
if (name.includes('gemini')) return 'agent-gemini';
|
|
5863
|
+
// Secondary agents (backward compatibility)
|
|
5864
|
+
if (name.includes('analyst')) return 'agent-analyst';
|
|
5865
|
+
if (name.includes('developer')) return 'agent-developer';
|
|
5866
|
+
if (name.includes('researcher')) return 'agent-researcher';
|
|
5867
|
+
if (name.includes('debugger')) return 'agent-debugger';
|
|
5868
|
+
return 'agent-default';
|
|
5869
|
+
}
|
|
5870
|
+
|
|
3734
5871
|
// Meta section
|
|
3735
5872
|
bodyHtml += `
|
|
3736
5873
|
<div class="panel-section">
|
|
@@ -3739,7 +5876,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3739
5876
|
<span class="badge priority-${node.priority}">${node.priority}</span>
|
|
3740
5877
|
<span class="badge type">${node.type}</span>
|
|
3741
5878
|
<span class="badge">${node.status}</span>
|
|
3742
|
-
${node.agent_assigned ? `<span class="badge">Agent: ${node.agent_assigned}</span>` : ''}
|
|
5879
|
+
${node.agent_assigned ? `<span class="badge agent ${getAgentClass(node.agent_assigned)}">Agent: ${node.agent_assigned}</span>` : ''}
|
|
3743
5880
|
</div>
|
|
3744
5881
|
</div>
|
|
3745
5882
|
`;
|
|
@@ -3768,6 +5905,43 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3768
5905
|
`;
|
|
3769
5906
|
}
|
|
3770
5907
|
|
|
5908
|
+
// Delegation information section (if delegation data exists in properties)
|
|
5909
|
+
if (node.properties && (node.properties.delegated_tasks || node.properties.delegations)) {
|
|
5910
|
+
const delegations = node.properties.delegations || node.properties.delegated_tasks || [];
|
|
5911
|
+
if (delegations && delegations.length > 0) {
|
|
5912
|
+
bodyHtml += `
|
|
5913
|
+
<div class="panel-section">
|
|
5914
|
+
<h3>Delegations (${delegations.length})</h3>
|
|
5915
|
+
<div class="delegations-list">
|
|
5916
|
+
${delegations.map((d, idx) => {
|
|
5917
|
+
const spawner = d.spawner || d.executor || 'unknown';
|
|
5918
|
+
const executorType = d.executor_type || 'direct';
|
|
5919
|
+
let executorBadge = 'delegation-direct';
|
|
5920
|
+
if (executorType === 'external_cli') executorBadge = 'delegation-external';
|
|
5921
|
+
else if (executorType === 'fallback') executorBadge = 'delegation-fallback';
|
|
5922
|
+
|
|
5923
|
+
const tokens = d.tokens_used ? ` (${d.tokens_used} tokens)` : '';
|
|
5924
|
+
const cost = d.cost ? ` - $${d.cost.toFixed(2)}` : '';
|
|
5925
|
+
|
|
5926
|
+
return `
|
|
5927
|
+
<div class="delegation-item">
|
|
5928
|
+
<div class="delegation-meta">
|
|
5929
|
+
<span class="badge delegation ${executorBadge}">${spawner}</span>
|
|
5930
|
+
<span class="badge delegation">${executorType}</span>
|
|
5931
|
+
${tokens ? `<span class="mono">${tokens}</span>` : ''}
|
|
5932
|
+
${cost ? `<span class="mono">${cost}</span>` : ''}
|
|
5933
|
+
</div>
|
|
5934
|
+
${d.task_id ? `<div class="delegation-task">Task: ${d.task_id}</div>` : ''}
|
|
5935
|
+
${d.timestamp ? `<div class="delegation-time">${new Date(d.timestamp).toLocaleString()}</div>` : ''}
|
|
5936
|
+
</div>
|
|
5937
|
+
`;
|
|
5938
|
+
}).join('')}
|
|
5939
|
+
</div>
|
|
5940
|
+
</div>
|
|
5941
|
+
`;
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
|
|
3771
5945
|
// Edges section (excluding implemented-in which gets special handling)
|
|
3772
5946
|
const edgeTypes = Object.keys(node.edges || {}).filter(t => t !== 'implemented-in');
|
|
3773
5947
|
if (edgeTypes.length > 0) {
|
|
@@ -4089,213 +6263,796 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4089
6263
|
updateKanbanGrid();
|
|
4090
6264
|
}
|
|
4091
6265
|
|
|
4092
|
-
// =====================================================================
|
|
4093
|
-
// Graph Visualization
|
|
4094
|
-
// =====================================================================
|
|
6266
|
+
// =====================================================================
|
|
6267
|
+
// Graph Visualization
|
|
6268
|
+
// =====================================================================
|
|
6269
|
+
|
|
6270
|
+
let visNetwork = null; // Vis.js network instance
|
|
6271
|
+
|
|
6272
|
+
function getNodeColor(node) {
|
|
6273
|
+
const colors = {
|
|
6274
|
+
'done': getComputedStyle(document.documentElement).getPropertyValue('--status-done').trim(),
|
|
6275
|
+
'in-progress': getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(),
|
|
6276
|
+
'blocked': getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(),
|
|
6277
|
+
'todo': getComputedStyle(document.documentElement).getPropertyValue('--status-todo').trim()
|
|
6278
|
+
};
|
|
6279
|
+
return colors[node.status] || colors['todo'];
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6282
|
+
function getNodeRadius(node) {
|
|
6283
|
+
const statusSizes = {
|
|
6284
|
+
'done': 20,
|
|
6285
|
+
'in-progress': 35,
|
|
6286
|
+
'blocked': 30,
|
|
6287
|
+
'todo': 28
|
|
6288
|
+
};
|
|
6289
|
+
return statusSizes[node.status] || 25;
|
|
6290
|
+
}
|
|
6291
|
+
|
|
6292
|
+
function wrapText(text, maxCharsPerLine = 10) {
|
|
6293
|
+
const words = text.split(/\s+/);
|
|
6294
|
+
const lines = [];
|
|
6295
|
+
let currentLine = '';
|
|
6296
|
+
|
|
6297
|
+
for (const word of words) {
|
|
6298
|
+
const testLine = currentLine ? currentLine + ' ' + word : word;
|
|
6299
|
+
if (testLine.length <= maxCharsPerLine) {
|
|
6300
|
+
currentLine = testLine;
|
|
6301
|
+
} else {
|
|
6302
|
+
if (currentLine) lines.push(currentLine);
|
|
6303
|
+
currentLine = word.length > maxCharsPerLine
|
|
6304
|
+
? word.substring(0, maxCharsPerLine - 1) + '…'
|
|
6305
|
+
: word;
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
if (currentLine) lines.push(currentLine);
|
|
6309
|
+
|
|
6310
|
+
if (lines.length > 3) {
|
|
6311
|
+
lines.length = 3;
|
|
6312
|
+
lines[2] = lines[2].substring(0, lines[2].length - 1) + '…';
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
return lines.join('\n');
|
|
6316
|
+
}
|
|
6317
|
+
|
|
6318
|
+
function buildGraphData(nodes) {
|
|
6319
|
+
const graphNodes = nodes.map(n => ({
|
|
6320
|
+
id: n.id,
|
|
6321
|
+
title: n.title,
|
|
6322
|
+
status: n.status,
|
|
6323
|
+
type: n.type,
|
|
6324
|
+
priority: n.priority,
|
|
6325
|
+
edges: n.edges || {},
|
|
6326
|
+
_collection: n._collection
|
|
6327
|
+
}));
|
|
6328
|
+
|
|
6329
|
+
const nodeIds = new Set(nodes.map(n => n.id));
|
|
6330
|
+
const graphEdges = [];
|
|
6331
|
+
|
|
6332
|
+
nodes.forEach(node => {
|
|
6333
|
+
Object.entries(node.edges || {}).forEach(([edgeType, edges]) => {
|
|
6334
|
+
edges.forEach(edge => {
|
|
6335
|
+
if (nodeIds.has(edge.target_id)) {
|
|
6336
|
+
graphEdges.push({
|
|
6337
|
+
from: node.id,
|
|
6338
|
+
to: edge.target_id,
|
|
6339
|
+
type: edgeType
|
|
6340
|
+
});
|
|
6341
|
+
}
|
|
6342
|
+
});
|
|
6343
|
+
});
|
|
6344
|
+
});
|
|
6345
|
+
|
|
6346
|
+
return { nodes: graphNodes, edges: graphEdges };
|
|
6347
|
+
}
|
|
6348
|
+
|
|
6349
|
+
// Graph State Management
|
|
6350
|
+
let graphState = {
|
|
6351
|
+
allNodes: [],
|
|
6352
|
+
allEdges: [],
|
|
6353
|
+
visibleNodeIds: new Set(),
|
|
6354
|
+
searchQuery: '',
|
|
6355
|
+
filters: {
|
|
6356
|
+
todo: true,
|
|
6357
|
+
'in-progress': true,
|
|
6358
|
+
blocked: true,
|
|
6359
|
+
done: false
|
|
6360
|
+
}
|
|
6361
|
+
};
|
|
6362
|
+
|
|
6363
|
+
function applyGraphFilters() {
|
|
6364
|
+
const filters = {};
|
|
6365
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6366
|
+
filters[cb.dataset.status] = cb.checked;
|
|
6367
|
+
});
|
|
6368
|
+
|
|
6369
|
+
graphState.filters = filters;
|
|
6370
|
+
graphState.searchQuery = (document.getElementById('graph-search')?.value || '').toLowerCase();
|
|
6371
|
+
|
|
6372
|
+
// Determine visible nodes
|
|
6373
|
+
graphState.visibleNodeIds = new Set();
|
|
6374
|
+
graphState.allNodes.forEach(node => {
|
|
6375
|
+
const statusMatch = filters[node.status] || false;
|
|
6376
|
+
const searchMatch = !graphState.searchQuery || node.title.toLowerCase().includes(graphState.searchQuery);
|
|
6377
|
+
if (statusMatch && searchMatch) {
|
|
6378
|
+
graphState.visibleNodeIds.add(node.id);
|
|
6379
|
+
}
|
|
6380
|
+
});
|
|
6381
|
+
|
|
6382
|
+
// Update Vis.js network with visible nodes and edges
|
|
6383
|
+
if (visNetwork) {
|
|
6384
|
+
const visibleNodes = graphState.allNodes.filter(n => graphState.visibleNodeIds.has(n.id));
|
|
6385
|
+
const visibleEdges = graphState.allEdges.filter(e =>
|
|
6386
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
6387
|
+
);
|
|
6388
|
+
|
|
6389
|
+
const nodesDataset = new vis.DataSet(visibleNodes.map(n => ({
|
|
6390
|
+
id: n.id,
|
|
6391
|
+
label: wrapText(n.title),
|
|
6392
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
6393
|
+
color: {
|
|
6394
|
+
background: getNodeColor(n),
|
|
6395
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
6396
|
+
highlight: {
|
|
6397
|
+
background: getNodeColor(n),
|
|
6398
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
6399
|
+
}
|
|
6400
|
+
},
|
|
6401
|
+
size: getNodeRadius(n),
|
|
6402
|
+
font: {
|
|
6403
|
+
size: 12,
|
|
6404
|
+
face: "'JetBrains Mono', monospace",
|
|
6405
|
+
color: 'white',
|
|
6406
|
+
strokeWidth: 0
|
|
6407
|
+
},
|
|
6408
|
+
physics: true,
|
|
6409
|
+
borderWidth: 2,
|
|
6410
|
+
status: n.status,
|
|
6411
|
+
_collection: n._collection,
|
|
6412
|
+
x: undefined, // Let physics handle positioning
|
|
6413
|
+
y: undefined
|
|
6414
|
+
})));
|
|
6415
|
+
|
|
6416
|
+
const edgesDataset = new vis.DataSet(visibleEdges.map(e => ({
|
|
6417
|
+
from: e.from,
|
|
6418
|
+
to: e.to,
|
|
6419
|
+
arrows: 'to',
|
|
6420
|
+
smooth: { type: 'continuous' },
|
|
6421
|
+
color: e.type === 'blocked_by'
|
|
6422
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
6423
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
6424
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
6425
|
+
width: 1.5
|
|
6426
|
+
})));
|
|
6427
|
+
|
|
6428
|
+
visNetwork.setData({ nodes: nodesDataset, edges: edgesDataset });
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
updateGraphStats();
|
|
6432
|
+
localStorage.setItem('graphFilters', JSON.stringify(graphState.filters));
|
|
6433
|
+
}
|
|
6434
|
+
|
|
6435
|
+
function updateGraphStats() {
|
|
6436
|
+
const visibleNodeCount = graphState.visibleNodeIds.size;
|
|
6437
|
+
const visibleEdgeCount = graphState.allEdges.filter(e =>
|
|
6438
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
6439
|
+
).length;
|
|
6440
|
+
const nodeCountEl = document.getElementById('graph-node-count');
|
|
6441
|
+
const edgeCountEl = document.getElementById('graph-edge-count');
|
|
6442
|
+
if (nodeCountEl) nodeCountEl.textContent = `${visibleNodeCount} nodes`;
|
|
6443
|
+
if (edgeCountEl) edgeCountEl.textContent = `${visibleEdgeCount} edges`;
|
|
6444
|
+
}
|
|
6445
|
+
|
|
6446
|
+
function resetGraphView() {
|
|
6447
|
+
const searchEl = document.getElementById('graph-search');
|
|
6448
|
+
if (searchEl) searchEl.value = '';
|
|
6449
|
+
graphState.searchQuery = '';
|
|
6450
|
+
applyGraphFilters();
|
|
6451
|
+
if (visNetwork) visNetwork.fit();
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6454
|
+
function showAllNodes() {
|
|
6455
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6456
|
+
cb.checked = true;
|
|
6457
|
+
});
|
|
6458
|
+
graphState.filters = { todo: true, 'in-progress': true, blocked: true, done: true };
|
|
6459
|
+
applyGraphFilters();
|
|
6460
|
+
if (visNetwork) visNetwork.fit();
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
function renderGraph(nodes) {
|
|
6464
|
+
if (nodes.length === 0) {
|
|
6465
|
+
if (visNetwork) visNetwork.destroy();
|
|
6466
|
+
visNetwork = null;
|
|
6467
|
+
return;
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6470
|
+
const { nodes: graphNodes, edges: graphEdges } = buildGraphData(nodes);
|
|
6471
|
+
|
|
6472
|
+
graphState.allNodes = graphNodes;
|
|
6473
|
+
graphState.allEdges = graphEdges;
|
|
6474
|
+
|
|
6475
|
+
// FILTER FIRST: Apply default filters before rendering
|
|
6476
|
+
// Default: show todo, in-progress, blocked (exclude done items)
|
|
6477
|
+
graphState.visibleNodeIds = new Set();
|
|
6478
|
+
graphState.allNodes.forEach(node => {
|
|
6479
|
+
if (graphState.filters[node.status] !== false) {
|
|
6480
|
+
graphState.visibleNodeIds.add(node.id);
|
|
6481
|
+
}
|
|
6482
|
+
});
|
|
6483
|
+
|
|
6484
|
+
// Only render visible nodes and edges
|
|
6485
|
+
const visibleNodes = graphState.allNodes.filter(n => graphState.visibleNodeIds.has(n.id));
|
|
6486
|
+
const visibleEdges = graphState.allEdges.filter(e =>
|
|
6487
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
6488
|
+
);
|
|
6489
|
+
|
|
6490
|
+
// Create Vis.js nodes dataset with FILTERED nodes only
|
|
6491
|
+
const nodesData = new vis.DataSet(visibleNodes.map(n => ({
|
|
6492
|
+
id: n.id,
|
|
6493
|
+
label: wrapText(n.title),
|
|
6494
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
6495
|
+
color: {
|
|
6496
|
+
background: getNodeColor(n),
|
|
6497
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
6498
|
+
highlight: {
|
|
6499
|
+
background: getNodeColor(n),
|
|
6500
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
6501
|
+
}
|
|
6502
|
+
},
|
|
6503
|
+
size: getNodeRadius(n),
|
|
6504
|
+
font: {
|
|
6505
|
+
size: 12,
|
|
6506
|
+
face: "'JetBrains Mono', monospace",
|
|
6507
|
+
color: 'white',
|
|
6508
|
+
strokeWidth: 0
|
|
6509
|
+
},
|
|
6510
|
+
physics: true,
|
|
6511
|
+
borderWidth: 2,
|
|
6512
|
+
status: n.status,
|
|
6513
|
+
_collection: n._collection
|
|
6514
|
+
})));
|
|
6515
|
+
|
|
6516
|
+
// Create Vis.js edges dataset with FILTERED edges only
|
|
6517
|
+
const edgesData = new vis.DataSet(visibleEdges.map(e => ({
|
|
6518
|
+
from: e.from,
|
|
6519
|
+
to: e.to,
|
|
6520
|
+
arrows: 'to',
|
|
6521
|
+
smooth: { type: 'continuous' },
|
|
6522
|
+
color: e.type === 'blocked_by'
|
|
6523
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
6524
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
6525
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
6526
|
+
width: 1.5
|
|
6527
|
+
})));
|
|
6528
|
+
|
|
6529
|
+
// Destroy existing network if it exists
|
|
6530
|
+
if (visNetwork) {
|
|
6531
|
+
visNetwork.destroy();
|
|
6532
|
+
}
|
|
6533
|
+
|
|
6534
|
+
// Create new Vis.js network
|
|
6535
|
+
const container = document.getElementById('graph-network');
|
|
6536
|
+
const data = {
|
|
6537
|
+
nodes: nodesData,
|
|
6538
|
+
edges: edgesData
|
|
6539
|
+
};
|
|
6540
|
+
|
|
6541
|
+
// Optimize physics based on node count
|
|
6542
|
+
const nodeCount = visibleNodes.length;
|
|
6543
|
+
const stabilizationIterations = nodeCount > 300 ? 100 : (nodeCount > 150 ? 150 : 200);
|
|
6544
|
+
|
|
6545
|
+
const options = {
|
|
6546
|
+
physics: {
|
|
6547
|
+
enabled: true,
|
|
6548
|
+
stabilization: {
|
|
6549
|
+
iterations: stabilizationIterations,
|
|
6550
|
+
fit: true
|
|
6551
|
+
},
|
|
6552
|
+
barnesHut: {
|
|
6553
|
+
gravitationalConstant: -30000,
|
|
6554
|
+
centralGravity: 0.3,
|
|
6555
|
+
springLength: 200,
|
|
6556
|
+
springConstant: 0.04
|
|
6557
|
+
},
|
|
6558
|
+
maxVelocity: 50
|
|
6559
|
+
},
|
|
6560
|
+
interaction: {
|
|
6561
|
+
navigationButtons: true,
|
|
6562
|
+
keyboard: true,
|
|
6563
|
+
zoomView: true,
|
|
6564
|
+
dragView: true
|
|
6565
|
+
},
|
|
6566
|
+
nodes: {
|
|
6567
|
+
shape: 'circle',
|
|
6568
|
+
scaling: {
|
|
6569
|
+
min: 10,
|
|
6570
|
+
max: 50
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
};
|
|
6574
|
+
|
|
6575
|
+
visNetwork = new vis.Network(container, data, options);
|
|
6576
|
+
|
|
6577
|
+
// Handle node clicks
|
|
6578
|
+
visNetwork.on('click', (params) => {
|
|
6579
|
+
if (params.nodes.length > 0) {
|
|
6580
|
+
const nodeId = params.nodes[0];
|
|
6581
|
+
const node = graphState.allNodes.find(n => n.id === nodeId);
|
|
6582
|
+
if (node) {
|
|
6583
|
+
openPanel(node._collection, node.id);
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
});
|
|
6587
|
+
|
|
6588
|
+
// Apply filters after network is initialized
|
|
6589
|
+
applyGraphFilters();
|
|
6590
|
+
}
|
|
6591
|
+
|
|
6592
|
+
// =====================================================================
|
|
6593
|
+
// Agent Skills Analysis
|
|
6594
|
+
// =====================================================================
|
|
6595
|
+
|
|
6596
|
+
function analyzeAgentSkills(sessions) {
|
|
6597
|
+
const skillProfiles = {};
|
|
6598
|
+
const agents = [...new Set(sessions.map(s => s.properties?.agent).filter(Boolean))];
|
|
6599
|
+
agents.forEach(agent => {
|
|
6600
|
+
skillProfiles[agent] = {Implementation: 0, Analysis: 0, Testing: 0, Documentation: 0, Coordination: 0};
|
|
6601
|
+
});
|
|
6602
|
+
sessions.forEach(session => {
|
|
6603
|
+
const agent = session.properties?.agent;
|
|
6604
|
+
if (!agent) return;
|
|
6605
|
+
const desc = (session.name || session.id || '').toLowerCase();
|
|
6606
|
+
const cnt = session.properties?.event_count || 0;
|
|
6607
|
+
if (desc.includes('test') || desc.includes('validate')) skillProfiles[agent].Testing += Math.min(cnt / 10, 2);
|
|
6608
|
+
if (desc.includes('implement') || desc.includes('code') || desc.includes('build')) skillProfiles[agent].Implementation += Math.min(cnt / 10, 2);
|
|
6609
|
+
if (desc.includes('analyze') || desc.includes('research')) skillProfiles[agent].Analysis += Math.min(cnt / 10, 2);
|
|
6610
|
+
if (desc.includes('document') || desc.includes('explain')) skillProfiles[agent].Documentation += Math.min(cnt / 10, 2);
|
|
6611
|
+
if (desc.includes('coordinate') || desc.includes('delegate')) skillProfiles[agent].Coordination += Math.min(cnt / 10, 2);
|
|
6612
|
+
if (agent.includes('Claude')) {
|
|
6613
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6614
|
+
skillProfiles[agent].Documentation = Math.max(skillProfiles[agent].Documentation, 4);
|
|
6615
|
+
}
|
|
6616
|
+
if (agent.includes('Codex')) {
|
|
6617
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 5);
|
|
6618
|
+
skillProfiles[agent].Testing = Math.max(skillProfiles[agent].Testing, 4);
|
|
6619
|
+
}
|
|
6620
|
+
if (agent.includes('Orchestrator')) {
|
|
6621
|
+
skillProfiles[agent].Coordination = Math.max(skillProfiles[agent].Coordination, 5);
|
|
6622
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4);
|
|
6623
|
+
}
|
|
6624
|
+
if (agent.includes('Gemini')) {
|
|
6625
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6626
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 3);
|
|
6627
|
+
}
|
|
6628
|
+
});
|
|
6629
|
+
agents.forEach(agent => {
|
|
6630
|
+
Object.keys(skillProfiles[agent]).forEach(skill => {
|
|
6631
|
+
skillProfiles[agent][skill] = Math.min(5, Math.max(1, skillProfiles[agent][skill]));
|
|
6632
|
+
});
|
|
6633
|
+
});
|
|
6634
|
+
return { agents, skillProfiles };
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
function getProficiencyColor(level) {
|
|
6638
|
+
return `proficiency-${Math.round(level)}`;
|
|
6639
|
+
}
|
|
4095
6640
|
|
|
4096
|
-
|
|
6641
|
+
function getProficiencyLabel(level) {
|
|
6642
|
+
const labels = ['', 'Novice', 'Beginner', 'Intermediate', 'Advanced', 'Expert'];
|
|
6643
|
+
return labels[Math.round(level)] || 'Expert';
|
|
6644
|
+
}
|
|
4097
6645
|
|
|
4098
|
-
function
|
|
4099
|
-
const
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
6646
|
+
function renderSkillsMatrix(agents, skillProfiles) {
|
|
6647
|
+
const skills = ['Implementation', 'Analysis', 'Testing', 'Documentation', 'Coordination'];
|
|
6648
|
+
let html = '<div class="skills-matrix">';
|
|
6649
|
+
html += '<div class="skills-matrix-cell skills-matrix-header-row">AGENT</div>';
|
|
6650
|
+
skills.forEach(skill => html += `<div class="skills-matrix-cell skills-matrix-header-row">${skill}</div>`);
|
|
6651
|
+
agents.forEach(agent => {
|
|
6652
|
+
html += `<div class="skills-matrix-cell skills-matrix-agent-name">${agent}</div>`;
|
|
6653
|
+
skills.forEach(skill => {
|
|
6654
|
+
const level = skillProfiles[agent][skill];
|
|
6655
|
+
const rnd = Math.round(level);
|
|
6656
|
+
html += `<div class="skills-matrix-cell"><div class="proficiency-dot ${getProficiencyColor(level)}" title="${getProficiencyLabel(level)} (${rnd}/5)">${rnd}</div></div>`;
|
|
6657
|
+
});
|
|
6658
|
+
});
|
|
6659
|
+
html += '</div><div class="skill-category-legend"><div style="font-weight: 600; width: 100%; margin-bottom: 0.5rem;">Proficiency Scale:</div>';
|
|
6660
|
+
for (let i = 1; i <= 5; i++) {
|
|
6661
|
+
html += `<div class="skill-category-item"><span class="proficiency-dot proficiency-${i}">${i}</span> ${getProficiencyLabel(i)}</div>`;
|
|
6662
|
+
}
|
|
6663
|
+
html += '</div>';
|
|
6664
|
+
return html;
|
|
4106
6665
|
}
|
|
4107
6666
|
|
|
4108
|
-
function
|
|
4109
|
-
const
|
|
4110
|
-
|
|
4111
|
-
|
|
6667
|
+
async function loadAndRenderAgents() {
|
|
6668
|
+
const el = document.getElementById('skills-matrix-content');
|
|
6669
|
+
try {
|
|
6670
|
+
let sessions = allSessions;
|
|
6671
|
+
if (!sessions.length) {
|
|
6672
|
+
const r = await fetch(`${API}/sessions`);
|
|
6673
|
+
if (!r.ok) throw new Error('Failed to load');
|
|
6674
|
+
sessions = (await r.json()).nodes || [];
|
|
6675
|
+
}
|
|
6676
|
+
if (!sessions.length) {
|
|
6677
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents found</div>';
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
const { agents, skillProfiles } = analyzeAgentSkills(sessions);
|
|
6681
|
+
if (!agents.length) {
|
|
6682
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents</div>';
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
el.innerHTML = renderSkillsMatrix(agents, skillProfiles);
|
|
6686
|
+
} catch (e) {
|
|
6687
|
+
el.innerHTML = `<div style="padding: 2rem; color: red;">Error: ${e.message}</div>`;
|
|
6688
|
+
}
|
|
4112
6689
|
}
|
|
4113
6690
|
|
|
4114
|
-
function
|
|
4115
|
-
const
|
|
4116
|
-
|
|
4117
|
-
|
|
6691
|
+
async function loadOrchestrationView() {
|
|
6692
|
+
const el = document.getElementById('orchestration-content');
|
|
6693
|
+
try {
|
|
6694
|
+
const r = await fetch(`${API}/orchestration`);
|
|
6695
|
+
if (!r.ok) throw new Error('Failed to load orchestration data');
|
|
6696
|
+
const data = await r.json();
|
|
4118
6697
|
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
currentLine = testLine;
|
|
4123
|
-
} else {
|
|
4124
|
-
if (currentLine) lines.push(currentLine);
|
|
4125
|
-
currentLine = word.length > maxCharsPerLine
|
|
4126
|
-
? word.substring(0, maxCharsPerLine - 1) + '…'
|
|
4127
|
-
: word;
|
|
6698
|
+
if (data.delegation_count === 0) {
|
|
6699
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No delegations found</div>';
|
|
6700
|
+
return;
|
|
4128
6701
|
}
|
|
4129
|
-
}
|
|
4130
|
-
if (currentLine) lines.push(currentLine);
|
|
4131
6702
|
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
6703
|
+
// Render delegation summary
|
|
6704
|
+
let html = '<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem;">';
|
|
6705
|
+
html += `<div class="metric-card"><div class="metric-value">${data.delegation_count}</div><div class="metric-label">Total Delegations</div></div>`;
|
|
6706
|
+
html += `<div class="metric-card"><div class="metric-value">${data.unique_agents}</div><div class="metric-label">Agents Involved</div></div>`;
|
|
6707
|
+
html += '</div>';
|
|
6708
|
+
|
|
6709
|
+
// Render delegation chains
|
|
6710
|
+
html += '<div class="delegations-list">';
|
|
6711
|
+
for (const [fromAgent, delegations] of Object.entries(data.delegation_chains)) {
|
|
6712
|
+
html += `<div style="margin-bottom: 1.5rem;">`;
|
|
6713
|
+
html += `<h4 style="margin-bottom: 0.75rem; color: var(--text-primary);">${fromAgent}</h4>`;
|
|
6714
|
+
delegations.forEach(d => {
|
|
6715
|
+
const statusColor = d.status === 'completed' ? 'var(--status-done)' :
|
|
6716
|
+
d.status === 'failed' ? 'var(--status-blocked)' :
|
|
6717
|
+
'var(--status-active)';
|
|
6718
|
+
html += `<div class="delegation-item" style="margin-left: 1.5rem; margin-bottom: 0.5rem; padding: 0.75rem; background: var(--bg-tertiary); border-radius: 6px;">`;
|
|
6719
|
+
html += `<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;">`;
|
|
6720
|
+
html += `<span style="color: var(--text-secondary);">→</span>`;
|
|
6721
|
+
html += `<strong style="color: var(--text-primary);">${d.to_agent}</strong>`;
|
|
6722
|
+
html += `<span class="badge" style="background: ${statusColor}; color: white; font-size: 0.65rem;">${d.status}</span>`;
|
|
6723
|
+
html += `</div>`;
|
|
6724
|
+
html += `<div style="color: var(--text-secondary); font-size: 0.875rem; margin-left: 1.5rem;">${d.task}</div>`;
|
|
6725
|
+
if (d.timestamp) {
|
|
6726
|
+
html += `<div style="color: var(--text-muted); font-size: 0.75rem; margin-left: 1.5rem; margin-top: 0.25rem;">${d.timestamp.replace('T', ' ').substring(0, 19)}</div>`;
|
|
6727
|
+
}
|
|
6728
|
+
html += `</div>`;
|
|
6729
|
+
});
|
|
6730
|
+
html += `</div>`;
|
|
6731
|
+
}
|
|
6732
|
+
html += '</div>';
|
|
4136
6733
|
|
|
4137
|
-
|
|
6734
|
+
el.innerHTML = html;
|
|
6735
|
+
} catch (e) {
|
|
6736
|
+
el.innerHTML = `<div style="padding: 2rem; color: red;">Error: ${e.message}</div>`;
|
|
6737
|
+
}
|
|
4138
6738
|
}
|
|
4139
6739
|
|
|
4140
|
-
function buildGraphData(nodes) {
|
|
4141
|
-
const graphNodes = nodes.map(n => ({
|
|
4142
|
-
id: n.id,
|
|
4143
|
-
title: n.title,
|
|
4144
|
-
status: n.status,
|
|
4145
|
-
type: n.type,
|
|
4146
|
-
priority: n.priority,
|
|
4147
|
-
edges: n.edges || {},
|
|
4148
|
-
_collection: n._collection,
|
|
4149
|
-
x: null,
|
|
4150
|
-
y: null
|
|
4151
|
-
}));
|
|
4152
6740
|
|
|
4153
|
-
|
|
4154
|
-
|
|
6741
|
+
// =====================================================================
|
|
6742
|
+
// View Toggle
|
|
6743
|
+
// =====================================================================
|
|
4155
6744
|
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
});
|
|
4167
|
-
});
|
|
6745
|
+
function switchView(view) {
|
|
6746
|
+
const kanban = document.getElementById('kanban');
|
|
6747
|
+
const graph = document.getElementById('graph-container');
|
|
6748
|
+
const analytics = document.getElementById('analytics');
|
|
6749
|
+
const agents = document.getElementById('agents');
|
|
6750
|
+
const sessions = document.getElementById('sessions');
|
|
6751
|
+
const buttons = document.querySelectorAll('.view-btn');
|
|
6752
|
+
|
|
6753
|
+
buttons.forEach(btn => {
|
|
6754
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
4168
6755
|
});
|
|
4169
6756
|
|
|
4170
|
-
|
|
6757
|
+
if (view === 'kanban') {
|
|
6758
|
+
kanban.classList.add('active');
|
|
6759
|
+
graph.classList.remove('active');
|
|
6760
|
+
analytics.classList.remove('active');
|
|
6761
|
+
agents.classList.remove('active');
|
|
6762
|
+
sessions.classList.remove('active');
|
|
6763
|
+
renderKanban(allNodes);
|
|
6764
|
+
} else if (view === 'graph') {
|
|
6765
|
+
kanban.classList.remove('active');
|
|
6766
|
+
graph.classList.add('active');
|
|
6767
|
+
analytics.classList.remove('active');
|
|
6768
|
+
agents.classList.remove('active');
|
|
6769
|
+
sessions.classList.remove('active');
|
|
6770
|
+
renderGraph(allNodes);
|
|
6771
|
+
} else if (view === 'analytics') {
|
|
6772
|
+
kanban.classList.remove('active');
|
|
6773
|
+
graph.classList.remove('active');
|
|
6774
|
+
analytics.classList.add('active');
|
|
6775
|
+
agents.classList.remove('active');
|
|
6776
|
+
sessions.classList.remove('active');
|
|
6777
|
+
ensureAnalyticsLoaded(false);
|
|
6778
|
+
} else if (view === 'agents') {
|
|
6779
|
+
kanban.classList.remove('active');
|
|
6780
|
+
graph.classList.remove('active');
|
|
6781
|
+
analytics.classList.remove('active');
|
|
6782
|
+
agents.classList.add('active');
|
|
6783
|
+
sessions.classList.remove('active');
|
|
6784
|
+
loadAndRenderAgents();
|
|
6785
|
+
loadOrchestrationView();
|
|
6786
|
+
} else if (view === 'sessions') {
|
|
6787
|
+
kanban.classList.remove('active');
|
|
6788
|
+
graph.classList.remove('active');
|
|
6789
|
+
analytics.classList.remove('active');
|
|
6790
|
+
agents.classList.remove('active');
|
|
6791
|
+
sessions.classList.add('active');
|
|
6792
|
+
loadAndRenderSessions();
|
|
6793
|
+
}
|
|
4171
6794
|
}
|
|
4172
6795
|
|
|
4173
|
-
function renderGraph(nodes) {
|
|
4174
|
-
const svg = document.getElementById('graph-svg');
|
|
4175
|
-
const edgesGroup = document.getElementById('graph-edges');
|
|
4176
|
-
const nodesGroup = document.getElementById('graph-nodes');
|
|
4177
6796
|
|
|
4178
|
-
|
|
4179
|
-
|
|
6797
|
+
// =====================================================================
|
|
6798
|
+
// Init
|
|
6799
|
+
// =====================================================================
|
|
4180
6800
|
|
|
4181
|
-
|
|
6801
|
+
document.getElementById('panel-close').addEventListener('click', closePanel);
|
|
6802
|
+
document.getElementById('panel-overlay').addEventListener('click', closePanel);
|
|
4182
6803
|
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
6804
|
+
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
6805
|
+
btn.addEventListener('click', () => switchView(btn.dataset.view));
|
|
6806
|
+
});
|
|
4186
6807
|
|
|
4187
|
-
|
|
4188
|
-
|
|
6808
|
+
document.getElementById('analytics-refresh').addEventListener('click', () => {
|
|
6809
|
+
ensureAnalyticsLoaded(true);
|
|
6810
|
+
});
|
|
4189
6811
|
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
6812
|
+
document.getElementById('analytics-features').addEventListener('click', (e) => {
|
|
6813
|
+
const btn = e.target.closest && e.target.closest('button[data-feature]');
|
|
6814
|
+
if (!btn) return;
|
|
6815
|
+
loadFeatureAnalytics(btn.dataset.feature).catch(err => renderAnalyticsError(err));
|
|
6816
|
+
});
|
|
4195
6817
|
|
|
4196
|
-
|
|
6818
|
+
// Session filter event listeners
|
|
6819
|
+
document.getElementById('filter-status').addEventListener('change', applySessionFilters);
|
|
6820
|
+
document.getElementById('filter-agent').addEventListener('change', applySessionFilters);
|
|
6821
|
+
document.getElementById('filter-search').addEventListener('input', applySessionFilters);
|
|
6822
|
+
document.getElementById('filter-date-from').addEventListener('change', applySessionFilters);
|
|
6823
|
+
document.getElementById('filter-date-to').addEventListener('change', applySessionFilters);
|
|
6824
|
+
document.getElementById('filter-clear').addEventListener('click', clearSessionFilters);
|
|
6825
|
+
document.getElementById('compare-sessions-btn').addEventListener('click', compareSessions);
|
|
4197
6826
|
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
.distance(120)
|
|
4202
|
-
.strength(0.5))
|
|
4203
|
-
.force('charge', d3.forceManyBody()
|
|
4204
|
-
.strength(-400))
|
|
4205
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
4206
|
-
.force('collision', d3.forceCollide().radius(70))
|
|
4207
|
-
.on('tick', updatePositions);
|
|
6827
|
+
// Session comparison modal
|
|
6828
|
+
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
6829
|
+
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4208
6830
|
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
line.setAttribute('marker-end', 'url(#arrowhead)');
|
|
4213
|
-
line.dataset.source = edge.source.id || edge.source;
|
|
4214
|
-
line.dataset.target = edge.target.id || edge.target;
|
|
4215
|
-
edgesGroup.appendChild(line);
|
|
4216
|
-
});
|
|
6831
|
+
document.addEventListener('keydown', (e) => {
|
|
6832
|
+
if (e.key === 'Escape') closePanel();
|
|
6833
|
+
});
|
|
4217
6834
|
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
6835
|
+
loadData().then(async ({ status, nodes }) => {
|
|
6836
|
+
await renderKanban(nodes);
|
|
6837
|
+
updateKanbanGrid();
|
|
6838
|
+
}).catch(err => {
|
|
6839
|
+
console.error('Error loading dashboard data:', err);
|
|
6840
|
+
});
|
|
4223
6841
|
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
6842
|
+
function showAllNodes() {
|
|
6843
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6844
|
+
cb.checked = true;
|
|
6845
|
+
});
|
|
6846
|
+
graphState.filters = { todo: true, 'in-progress': true, blocked: true, done: true };
|
|
6847
|
+
applyGraphFilters();
|
|
6848
|
+
}
|
|
4228
6849
|
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
|
4236
|
-
tspan.setAttribute('x', '0');
|
|
4237
|
-
tspan.setAttribute('dy', i === 0 ? `${startY}px` : `${lineHeight}px`);
|
|
4238
|
-
tspan.textContent = line;
|
|
4239
|
-
text.appendChild(tspan);
|
|
4240
|
-
});
|
|
6850
|
+
function renderGraph(nodes) {
|
|
6851
|
+
if (nodes.length === 0) {
|
|
6852
|
+
if (visNetwork) visNetwork.destroy();
|
|
6853
|
+
visNetwork = null;
|
|
6854
|
+
return;
|
|
6855
|
+
}
|
|
4241
6856
|
|
|
4242
|
-
|
|
4243
|
-
g.appendChild(text);
|
|
4244
|
-
nodesGroup.appendChild(g);
|
|
6857
|
+
const { nodes: graphNodes, edges: graphEdges } = buildGraphData(nodes);
|
|
4245
6858
|
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
});
|
|
6859
|
+
graphState.allNodes = graphNodes;
|
|
6860
|
+
graphState.allEdges = graphEdges;
|
|
4249
6861
|
|
|
4250
|
-
|
|
4251
|
-
|
|
6862
|
+
// Create Vis.js nodes dataset
|
|
6863
|
+
const nodesData = new vis.DataSet(graphNodes.map(n => ({
|
|
6864
|
+
id: n.id,
|
|
6865
|
+
label: wrapText(n.title),
|
|
6866
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
6867
|
+
color: {
|
|
6868
|
+
background: getNodeColor(n),
|
|
6869
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
6870
|
+
highlight: {
|
|
6871
|
+
background: getNodeColor(n),
|
|
6872
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
6873
|
+
}
|
|
6874
|
+
},
|
|
6875
|
+
size: getNodeRadius(n),
|
|
6876
|
+
font: {
|
|
6877
|
+
size: 12,
|
|
6878
|
+
face: "'JetBrains Mono', monospace",
|
|
6879
|
+
color: 'white',
|
|
6880
|
+
strokeWidth: 0
|
|
6881
|
+
},
|
|
6882
|
+
physics: true,
|
|
6883
|
+
borderWidth: 2,
|
|
6884
|
+
status: n.status,
|
|
6885
|
+
_collection: n._collection
|
|
6886
|
+
})));
|
|
6887
|
+
|
|
6888
|
+
// Create Vis.js edges dataset
|
|
6889
|
+
const edgesData = new vis.DataSet(graphEdges.map(e => ({
|
|
6890
|
+
from: e.from,
|
|
6891
|
+
to: e.to,
|
|
6892
|
+
arrows: 'to',
|
|
6893
|
+
smooth: { type: 'continuous' },
|
|
6894
|
+
color: e.type === 'blocked_by'
|
|
6895
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
6896
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
6897
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
6898
|
+
width: 1.5
|
|
6899
|
+
})));
|
|
6900
|
+
|
|
6901
|
+
// Destroy existing network if it exists
|
|
6902
|
+
if (visNetwork) {
|
|
6903
|
+
visNetwork.destroy();
|
|
6904
|
+
}
|
|
4252
6905
|
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
6906
|
+
// Create new Vis.js network
|
|
6907
|
+
const container = document.getElementById('graph-network');
|
|
6908
|
+
const data = {
|
|
6909
|
+
nodes: nodesData,
|
|
6910
|
+
edges: edgesData
|
|
6911
|
+
};
|
|
4259
6912
|
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
6913
|
+
const options = {
|
|
6914
|
+
physics: {
|
|
6915
|
+
enabled: true,
|
|
6916
|
+
stabilization: {
|
|
6917
|
+
iterations: 200,
|
|
6918
|
+
fit: true
|
|
6919
|
+
},
|
|
6920
|
+
barnesHut: {
|
|
6921
|
+
gravitationalConstant: -30000,
|
|
6922
|
+
centralGravity: 0.3,
|
|
6923
|
+
springLength: 200,
|
|
6924
|
+
springConstant: 0.04
|
|
6925
|
+
},
|
|
6926
|
+
maxVelocity: 50
|
|
6927
|
+
},
|
|
6928
|
+
interaction: {
|
|
6929
|
+
navigationButtons: true,
|
|
6930
|
+
keyboard: true,
|
|
6931
|
+
zoomView: true,
|
|
6932
|
+
dragView: true
|
|
6933
|
+
},
|
|
6934
|
+
nodes: {
|
|
6935
|
+
shape: 'circle',
|
|
6936
|
+
scaling: {
|
|
6937
|
+
min: 10,
|
|
6938
|
+
max: 50
|
|
4264
6939
|
}
|
|
4265
|
-
}
|
|
6940
|
+
}
|
|
6941
|
+
};
|
|
4266
6942
|
|
|
4267
|
-
|
|
4268
|
-
if (isDragging) {
|
|
4269
|
-
isDragging = false;
|
|
4270
|
-
node.fx = null;
|
|
4271
|
-
node.fy = null;
|
|
4272
|
-
simulation.alphaTarget(0);
|
|
4273
|
-
}
|
|
4274
|
-
});
|
|
4275
|
-
});
|
|
6943
|
+
visNetwork = new vis.Network(container, data, options);
|
|
4276
6944
|
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
const
|
|
6945
|
+
// Handle node clicks
|
|
6946
|
+
visNetwork.on('click', (params) => {
|
|
6947
|
+
if (params.nodes.length > 0) {
|
|
6948
|
+
const nodeId = params.nodes[0];
|
|
6949
|
+
const node = graphState.allNodes.find(n => n.id === nodeId);
|
|
4281
6950
|
if (node) {
|
|
4282
|
-
node.
|
|
4283
|
-
node.y = Math.max(30, Math.min(height - 30, node.y));
|
|
4284
|
-
g.setAttribute('transform', `translate(${node.x}, ${node.y})`);
|
|
6951
|
+
openPanel(node._collection, node.id);
|
|
4285
6952
|
}
|
|
6953
|
+
}
|
|
6954
|
+
});
|
|
6955
|
+
|
|
6956
|
+
// Apply filters after network is initialized
|
|
6957
|
+
applyGraphFilters();
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
// =====================================================================
|
|
6961
|
+
// Agent Skills Analysis
|
|
6962
|
+
// =====================================================================
|
|
6963
|
+
|
|
6964
|
+
function analyzeAgentSkills(sessions) {
|
|
6965
|
+
const skillProfiles = {};
|
|
6966
|
+
const agents = [...new Set(sessions.map(s => s.properties?.agent).filter(Boolean))];
|
|
6967
|
+
agents.forEach(agent => {
|
|
6968
|
+
skillProfiles[agent] = {Implementation: 0, Analysis: 0, Testing: 0, Documentation: 0, Coordination: 0};
|
|
6969
|
+
});
|
|
6970
|
+
sessions.forEach(session => {
|
|
6971
|
+
const agent = session.properties?.agent;
|
|
6972
|
+
if (!agent) return;
|
|
6973
|
+
const desc = (session.name || session.id || '').toLowerCase();
|
|
6974
|
+
const cnt = session.properties?.event_count || 0;
|
|
6975
|
+
if (desc.includes('test') || desc.includes('validate')) skillProfiles[agent].Testing += Math.min(cnt / 10, 2);
|
|
6976
|
+
if (desc.includes('implement') || desc.includes('code') || desc.includes('build')) skillProfiles[agent].Implementation += Math.min(cnt / 10, 2);
|
|
6977
|
+
if (desc.includes('analyze') || desc.includes('research')) skillProfiles[agent].Analysis += Math.min(cnt / 10, 2);
|
|
6978
|
+
if (desc.includes('document') || desc.includes('explain')) skillProfiles[agent].Documentation += Math.min(cnt / 10, 2);
|
|
6979
|
+
if (desc.includes('coordinate') || desc.includes('delegate')) skillProfiles[agent].Coordination += Math.min(cnt / 10, 2);
|
|
6980
|
+
if (agent.includes('Claude')) {
|
|
6981
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6982
|
+
skillProfiles[agent].Documentation = Math.max(skillProfiles[agent].Documentation, 4);
|
|
6983
|
+
}
|
|
6984
|
+
if (agent.includes('Codex')) {
|
|
6985
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 5);
|
|
6986
|
+
skillProfiles[agent].Testing = Math.max(skillProfiles[agent].Testing, 4);
|
|
6987
|
+
}
|
|
6988
|
+
if (agent.includes('Orchestrator')) {
|
|
6989
|
+
skillProfiles[agent].Coordination = Math.max(skillProfiles[agent].Coordination, 5);
|
|
6990
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4);
|
|
6991
|
+
}
|
|
6992
|
+
if (agent.includes('Gemini')) {
|
|
6993
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6994
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 3);
|
|
6995
|
+
}
|
|
6996
|
+
});
|
|
6997
|
+
agents.forEach(agent => {
|
|
6998
|
+
Object.keys(skillProfiles[agent]).forEach(skill => {
|
|
6999
|
+
skillProfiles[agent][skill] = Math.min(5, Math.max(1, skillProfiles[agent][skill]));
|
|
4286
7000
|
});
|
|
7001
|
+
});
|
|
7002
|
+
return { agents, skillProfiles };
|
|
7003
|
+
}
|
|
4287
7004
|
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
7005
|
+
function getProficiencyColor(level) {
|
|
7006
|
+
return `proficiency-${Math.round(level)}`;
|
|
7007
|
+
}
|
|
7008
|
+
|
|
7009
|
+
function getProficiencyLabel(level) {
|
|
7010
|
+
const labels = ['', 'Novice', 'Beginner', 'Intermediate', 'Advanced', 'Expert'];
|
|
7011
|
+
return labels[Math.round(level)] || 'Expert';
|
|
7012
|
+
}
|
|
7013
|
+
|
|
7014
|
+
function renderSkillsMatrix(agents, skillProfiles) {
|
|
7015
|
+
const skills = ['Implementation', 'Analysis', 'Testing', 'Documentation', 'Coordination'];
|
|
7016
|
+
let html = '<div class="skills-matrix">';
|
|
7017
|
+
html += '<div class="skills-matrix-cell skills-matrix-header-row">AGENT</div>';
|
|
7018
|
+
skills.forEach(skill => html += `<div class="skills-matrix-cell skills-matrix-header-row">${skill}</div>`);
|
|
7019
|
+
agents.forEach(agent => {
|
|
7020
|
+
html += `<div class="skills-matrix-cell skills-matrix-agent-name">${agent}</div>`;
|
|
7021
|
+
skills.forEach(skill => {
|
|
7022
|
+
const level = skillProfiles[agent][skill];
|
|
7023
|
+
const rnd = Math.round(level);
|
|
7024
|
+
html += `<div class="skills-matrix-cell"><div class="proficiency-dot ${getProficiencyColor(level)}" title="${getProficiencyLabel(level)} (${rnd}/5)">${rnd}</div></div>`;
|
|
4298
7025
|
});
|
|
7026
|
+
});
|
|
7027
|
+
html += '</div><div class="skill-category-legend"><div style="font-weight: 600; width: 100%; margin-bottom: 0.5rem;">Proficiency Scale:</div>';
|
|
7028
|
+
for (let i = 1; i <= 5; i++) {
|
|
7029
|
+
html += `<div class="skill-category-item"><span class="proficiency-dot proficiency-${i}">${i}</span> ${getProficiencyLabel(i)}</div>`;
|
|
7030
|
+
}
|
|
7031
|
+
html += '</div>';
|
|
7032
|
+
return html;
|
|
7033
|
+
}
|
|
7034
|
+
|
|
7035
|
+
async function loadAndRenderAgents() {
|
|
7036
|
+
const el = document.getElementById('skills-matrix-content');
|
|
7037
|
+
try {
|
|
7038
|
+
let sessions = allSessions;
|
|
7039
|
+
if (!sessions.length) {
|
|
7040
|
+
const r = await fetch(`${API}/sessions`);
|
|
7041
|
+
if (!r.ok) throw new Error('Failed to load');
|
|
7042
|
+
sessions = (await r.json()).nodes || [];
|
|
7043
|
+
}
|
|
7044
|
+
if (!sessions.length) {
|
|
7045
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents found</div>';
|
|
7046
|
+
return;
|
|
7047
|
+
}
|
|
7048
|
+
const { agents, skillProfiles } = analyzeAgentSkills(sessions);
|
|
7049
|
+
if (!agents.length) {
|
|
7050
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents</div>';
|
|
7051
|
+
return;
|
|
7052
|
+
}
|
|
7053
|
+
el.innerHTML = renderSkillsMatrix(agents, skillProfiles);
|
|
7054
|
+
} catch (e) {
|
|
7055
|
+
el.innerHTML = `<div style="padding: 2rem; color: red;">Error: ${e.message}</div>`;
|
|
4299
7056
|
}
|
|
4300
7057
|
}
|
|
4301
7058
|
|
|
@@ -4307,6 +7064,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4307
7064
|
const kanban = document.getElementById('kanban');
|
|
4308
7065
|
const graph = document.getElementById('graph-container');
|
|
4309
7066
|
const analytics = document.getElementById('analytics');
|
|
7067
|
+
const agents = document.getElementById('agents');
|
|
4310
7068
|
const sessions = document.getElementById('sessions');
|
|
4311
7069
|
const buttons = document.querySelectorAll('.view-btn');
|
|
4312
7070
|
|
|
@@ -4318,29 +7076,42 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4318
7076
|
kanban.classList.add('active');
|
|
4319
7077
|
graph.classList.remove('active');
|
|
4320
7078
|
analytics.classList.remove('active');
|
|
7079
|
+
agents.classList.remove('active');
|
|
4321
7080
|
sessions.classList.remove('active');
|
|
4322
7081
|
renderKanban(allNodes);
|
|
4323
7082
|
} else if (view === 'graph') {
|
|
4324
7083
|
kanban.classList.remove('active');
|
|
4325
7084
|
graph.classList.add('active');
|
|
4326
7085
|
analytics.classList.remove('active');
|
|
7086
|
+
agents.classList.remove('active');
|
|
4327
7087
|
sessions.classList.remove('active');
|
|
4328
7088
|
renderGraph(allNodes);
|
|
4329
7089
|
} else if (view === 'analytics') {
|
|
4330
7090
|
kanban.classList.remove('active');
|
|
4331
7091
|
graph.classList.remove('active');
|
|
4332
7092
|
analytics.classList.add('active');
|
|
7093
|
+
agents.classList.remove('active');
|
|
4333
7094
|
sessions.classList.remove('active');
|
|
4334
7095
|
ensureAnalyticsLoaded(false);
|
|
7096
|
+
} else if (view === 'agents') {
|
|
7097
|
+
kanban.classList.remove('active');
|
|
7098
|
+
graph.classList.remove('active');
|
|
7099
|
+
analytics.classList.remove('active');
|
|
7100
|
+
agents.classList.add('active');
|
|
7101
|
+
sessions.classList.remove('active');
|
|
7102
|
+
loadAndRenderAgents();
|
|
7103
|
+
loadOrchestrationView();
|
|
4335
7104
|
} else if (view === 'sessions') {
|
|
4336
7105
|
kanban.classList.remove('active');
|
|
4337
7106
|
graph.classList.remove('active');
|
|
4338
7107
|
analytics.classList.remove('active');
|
|
7108
|
+
agents.classList.remove('active');
|
|
4339
7109
|
sessions.classList.add('active');
|
|
4340
7110
|
loadAndRenderSessions();
|
|
4341
7111
|
}
|
|
4342
7112
|
}
|
|
4343
7113
|
|
|
7114
|
+
|
|
4344
7115
|
// =====================================================================
|
|
4345
7116
|
// Init
|
|
4346
7117
|
// =====================================================================
|
|
@@ -4375,6 +7146,11 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4375
7146
|
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
4376
7147
|
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4377
7148
|
|
|
7149
|
+
// Graph filter event listeners
|
|
7150
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
7151
|
+
cb.addEventListener('change', applyGraphFilters);
|
|
7152
|
+
});
|
|
7153
|
+
|
|
4378
7154
|
document.addEventListener('keydown', (e) => {
|
|
4379
7155
|
if (e.key === 'Escape') closePanel();
|
|
4380
7156
|
});
|
|
@@ -4560,6 +7336,53 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4560
7336
|
closeSpecPlanModal();
|
|
4561
7337
|
}
|
|
4562
7338
|
});
|
|
7339
|
+
|
|
7340
|
+
// Convert UTC timestamps to local timezone
|
|
7341
|
+
function convertTimestampsToLocal() {
|
|
7342
|
+
const timestampElements = document.querySelectorAll('[data-utc-time]');
|
|
7343
|
+
timestampElements.forEach(element => {
|
|
7344
|
+
const utcTime = element.getAttribute('data-utc-time');
|
|
7345
|
+
if (utcTime) {
|
|
7346
|
+
try {
|
|
7347
|
+
// Parse ISO 8601 UTC time - convert naive datetime to UTC format
|
|
7348
|
+
// Input: "2026-01-06 18:01:19" → "2026-01-06T18:01:19Z"
|
|
7349
|
+
const date = new Date(utcTime.replace(' ', 'T') + 'Z');
|
|
7350
|
+
// Convert to local timezone using Intl API for best compatibility
|
|
7351
|
+
const localTime = new Intl.DateTimeFormat('en-US', {
|
|
7352
|
+
year: 'numeric',
|
|
7353
|
+
month: '2-digit',
|
|
7354
|
+
day: '2-digit',
|
|
7355
|
+
hour: '2-digit',
|
|
7356
|
+
minute: '2-digit',
|
|
7357
|
+
second: '2-digit',
|
|
7358
|
+
hour12: false,
|
|
7359
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
7360
|
+
}).format(date);
|
|
7361
|
+
// Replace the displayed timestamp with local time
|
|
7362
|
+
element.textContent = localTime;
|
|
7363
|
+
// Add title attribute to show full ISO format on hover
|
|
7364
|
+
element.setAttribute('title', `UTC: ${utcTime} | Local: ${localTime}`);
|
|
7365
|
+
} catch (err) {
|
|
7366
|
+
console.warn('Failed to convert timestamp:', utcTime, err);
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
});
|
|
7370
|
+
}
|
|
7371
|
+
|
|
7372
|
+
// Convert timestamps on page load
|
|
7373
|
+
document.addEventListener('DOMContentLoaded', convertTimestampsToLocal);
|
|
7374
|
+
|
|
7375
|
+
// Also convert timestamps when new content is dynamically loaded (e.g., via HTMX)
|
|
7376
|
+
if (typeof htmx !== 'undefined') {
|
|
7377
|
+
document.addEventListener('htmx:afterSwap', convertTimestampsToLocal);
|
|
7378
|
+
}
|
|
7379
|
+
|
|
7380
|
+
// Convert timestamps via WebSocket updates
|
|
7381
|
+
const originalWebSocketOpen = WebSocket.prototype.open;
|
|
7382
|
+
if (originalWebSocketOpen) {
|
|
7383
|
+
// Re-convert after WebSocket message arrives
|
|
7384
|
+
document.addEventListener('ws:update', convertTimestampsToLocal);
|
|
7385
|
+
}
|
|
4563
7386
|
</script>
|
|
4564
7387
|
|
|
4565
7388
|
<!-- Spec/Plan Modal -->
|