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