htmlgraph 0.25.0__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 +1 -1
- htmlgraph/api/main.py +193 -45
- htmlgraph/api/templates/dashboard.html +11 -0
- htmlgraph/api/templates/partials/activity-feed.html +458 -8
- htmlgraph/dashboard.html +41 -0
- htmlgraph/db/schema.py +254 -4
- 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/cigs_pretool_enforcer.py +2 -2
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +57 -10
- htmlgraph/hooks/drift_handler.py +24 -20
- htmlgraph/hooks/event_tracker.py +204 -177
- htmlgraph/hooks/orchestrator.py +6 -4
- htmlgraph/hooks/orchestrator_reflector.py +4 -4
- htmlgraph/hooks/pretooluse.py +3 -6
- htmlgraph/hooks/prompt_analyzer.py +14 -25
- htmlgraph/hooks/session_handler.py +123 -69
- htmlgraph/hooks/state_manager.py +7 -4
- htmlgraph/hooks/validator.py +15 -11
- htmlgraph/orchestration/headless_spawner.py +322 -15
- htmlgraph/orchestration/live_events.py +377 -0
- {htmlgraph-0.25.0.data → htmlgraph-0.26.1.data}/data/htmlgraph/dashboard.html +41 -0
- {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.1.dist-info}/METADATA +1 -1
- {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.1.dist-info}/RECORD +32 -27
- {htmlgraph-0.25.0.data → htmlgraph-0.26.1.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.25.0.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.25.0.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.25.0.data → htmlgraph-0.26.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.1.dist-info}/WHEEL +0 -0
- {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.1.dist-info}/entry_points.txt +0 -0
|
@@ -86,12 +86,14 @@
|
|
|
86
86
|
<!-- Child Events Container (Hidden by default) -->
|
|
87
87
|
<div class="turn-children collapsed" id="children-{{ turn.userQuery.event_id }}">
|
|
88
88
|
{% if turn.children %}
|
|
89
|
-
{% for child in turn.children %}
|
|
89
|
+
{% for child in turn.children recursive %}
|
|
90
90
|
<!-- Child Event Row -->
|
|
91
|
-
<div class="child-event-row
|
|
91
|
+
<div class="child-event-row depth-{{ child.depth|default(0) }}"
|
|
92
|
+
data-event-id="{{ child.event_id }}"
|
|
93
|
+
style="margin-left: {{ (child.depth|default(0)) * 20 }}px;">
|
|
92
94
|
<!-- Tree Connector -->
|
|
93
95
|
<span class="tree-connector">
|
|
94
|
-
{% if loop.last %}└─{% else %}├─{% endif %}
|
|
96
|
+
{% if loop.last and not child.children %}└─{% else %}├─{% endif %}
|
|
95
97
|
</span>
|
|
96
98
|
|
|
97
99
|
<!-- Tool Name -->
|
|
@@ -107,6 +109,9 @@
|
|
|
107
109
|
<!-- Spawner delegation: show orchestrator → spawned AI -->
|
|
108
110
|
<span class="child-agent-badge agent-{{ child.agent|lower|replace(' ', '-') }}">
|
|
109
111
|
{{ child.agent }}
|
|
112
|
+
{% if child.model %}
|
|
113
|
+
<span class="model-indicator">{{ child.model }}</span>
|
|
114
|
+
{% endif %}
|
|
110
115
|
</span>
|
|
111
116
|
<span class="delegation-arrow">→</span>
|
|
112
117
|
<span class="spawner-badge spawner-{{ child.spawner_type|lower }}">
|
|
@@ -116,9 +121,12 @@
|
|
|
116
121
|
{% endif %}
|
|
117
122
|
</span>
|
|
118
123
|
{% else %}
|
|
119
|
-
<!-- Regular agent: just show agent name -->
|
|
124
|
+
<!-- Regular agent: just show agent name + model if available -->
|
|
120
125
|
<span class="child-agent-badge agent-{{ child.agent|lower|replace(' ', '-') }}">
|
|
121
126
|
{{ child.agent }}
|
|
127
|
+
{% if child.model %}
|
|
128
|
+
<span class="model-indicator">{{ child.model }}</span>
|
|
129
|
+
{% endif %}
|
|
122
130
|
</span>
|
|
123
131
|
{% endif %}
|
|
124
132
|
|
|
@@ -132,6 +140,10 @@
|
|
|
132
140
|
{{ child.timestamp }}
|
|
133
141
|
</span>
|
|
134
142
|
</div>
|
|
143
|
+
<!-- Recursively render nested children -->
|
|
144
|
+
{% if child.children %}
|
|
145
|
+
{{ loop(child.children) }}
|
|
146
|
+
{% endif %}
|
|
135
147
|
{% endfor %}
|
|
136
148
|
{% else %}
|
|
137
149
|
<div class="no-children-message">
|
|
@@ -300,7 +312,7 @@
|
|
|
300
312
|
flex-direction: column;
|
|
301
313
|
background: rgba(163, 230, 53, 0.02);
|
|
302
314
|
border-top: 1px solid var(--border-subtle);
|
|
303
|
-
padding: 0.5rem
|
|
315
|
+
padding: 0.5rem 1rem 0.5rem 2.5rem; /* Indent children under parent prompt */
|
|
304
316
|
}
|
|
305
317
|
|
|
306
318
|
.turn-children.collapsed {
|
|
@@ -328,9 +340,9 @@
|
|
|
328
340
|
font-size: 0.9rem;
|
|
329
341
|
font-weight: bold;
|
|
330
342
|
font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
|
|
331
|
-
white-space: pre
|
|
343
|
+
white-space: nowrap; /* Changed from 'pre' to collapse template whitespace */
|
|
332
344
|
flex-shrink: 0;
|
|
333
|
-
|
|
345
|
+
width: 24px; /* Fixed width for consistent alignment */
|
|
334
346
|
}
|
|
335
347
|
|
|
336
348
|
/* Child Tool Name */
|
|
@@ -356,7 +368,9 @@
|
|
|
356
368
|
|
|
357
369
|
/* Child Agent Badge */
|
|
358
370
|
.child-agent-badge {
|
|
359
|
-
display: inline-
|
|
371
|
+
display: inline-flex;
|
|
372
|
+
align-items: center;
|
|
373
|
+
gap: 0.25rem;
|
|
360
374
|
padding: 0.1rem 0.35rem;
|
|
361
375
|
font-size: 0.6rem;
|
|
362
376
|
font-weight: 600;
|
|
@@ -368,6 +382,19 @@
|
|
|
368
382
|
flex-shrink: 0;
|
|
369
383
|
}
|
|
370
384
|
|
|
385
|
+
/* Model Indicator Badge */
|
|
386
|
+
.model-indicator {
|
|
387
|
+
display: inline-block;
|
|
388
|
+
padding: 0.05rem 0.2rem;
|
|
389
|
+
font-size: 0.55rem;
|
|
390
|
+
background: rgba(100, 200, 255, 0.15);
|
|
391
|
+
color: #64c8ff;
|
|
392
|
+
border-radius: 1px;
|
|
393
|
+
font-weight: 700;
|
|
394
|
+
letter-spacing: 0.02em;
|
|
395
|
+
text-transform: capitalize;
|
|
396
|
+
}
|
|
397
|
+
|
|
371
398
|
.child-agent-badge.agent-claude-code,
|
|
372
399
|
.child-agent-badge.agent-claude {
|
|
373
400
|
background: rgba(200, 255, 0, 0.15);
|
|
@@ -526,6 +553,215 @@
|
|
|
526
553
|
width: 100%;
|
|
527
554
|
}
|
|
528
555
|
}
|
|
556
|
+
|
|
557
|
+
/* ============================================
|
|
558
|
+
Live Spawner Indicator Styles
|
|
559
|
+
============================================ */
|
|
560
|
+
|
|
561
|
+
.live-spawner-indicator {
|
|
562
|
+
display: none;
|
|
563
|
+
align-items: center;
|
|
564
|
+
gap: 0.75rem;
|
|
565
|
+
padding: 0.75rem 1rem;
|
|
566
|
+
margin: 0.5rem;
|
|
567
|
+
background: linear-gradient(135deg, rgba(163, 230, 53, 0.1) 0%, rgba(100, 200, 255, 0.1) 100%);
|
|
568
|
+
border: 1px solid rgba(163, 230, 53, 0.3);
|
|
569
|
+
border-radius: 6px;
|
|
570
|
+
font-size: 0.8rem;
|
|
571
|
+
transition: all 0.3s ease;
|
|
572
|
+
overflow: hidden;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.live-spawner-indicator.active {
|
|
576
|
+
display: flex;
|
|
577
|
+
border-color: rgba(163, 230, 53, 0.6);
|
|
578
|
+
box-shadow: 0 0 20px rgba(163, 230, 53, 0.2);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.live-spawner-indicator.completed {
|
|
582
|
+
display: flex;
|
|
583
|
+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(34, 197, 94, 0.05) 100%);
|
|
584
|
+
border-color: rgba(34, 197, 94, 0.5);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.live-spawner-indicator.failed {
|
|
588
|
+
display: flex;
|
|
589
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(239, 68, 68, 0.05) 100%);
|
|
590
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.live-spawner-indicator.fade-out {
|
|
594
|
+
opacity: 0;
|
|
595
|
+
transform: translateY(-10px);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* Spawner Icons */
|
|
599
|
+
.live-spawner-icon {
|
|
600
|
+
width: 24px;
|
|
601
|
+
height: 24px;
|
|
602
|
+
border-radius: 50%;
|
|
603
|
+
display: flex;
|
|
604
|
+
align-items: center;
|
|
605
|
+
justify-content: center;
|
|
606
|
+
flex-shrink: 0;
|
|
607
|
+
position: relative;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.live-spawner-icon::before {
|
|
611
|
+
content: '';
|
|
612
|
+
width: 12px;
|
|
613
|
+
height: 12px;
|
|
614
|
+
border-radius: 50%;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.live-spawner-icon.spawner-gemini {
|
|
618
|
+
background: rgba(74, 222, 128, 0.2);
|
|
619
|
+
}
|
|
620
|
+
.live-spawner-icon.spawner-gemini::before {
|
|
621
|
+
background: #4ade80;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.live-spawner-icon.spawner-codex {
|
|
625
|
+
background: rgba(100, 200, 255, 0.2);
|
|
626
|
+
}
|
|
627
|
+
.live-spawner-icon.spawner-codex::before {
|
|
628
|
+
background: #64c8ff;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.live-spawner-icon.spawner-copilot {
|
|
632
|
+
background: rgba(168, 85, 247, 0.2);
|
|
633
|
+
}
|
|
634
|
+
.live-spawner-icon.spawner-copilot::before {
|
|
635
|
+
background: #a855f7;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* Pulsing animation for active spawners */
|
|
639
|
+
.live-spawner-icon.pulsing::after {
|
|
640
|
+
content: '';
|
|
641
|
+
position: absolute;
|
|
642
|
+
width: 100%;
|
|
643
|
+
height: 100%;
|
|
644
|
+
border-radius: 50%;
|
|
645
|
+
border: 2px solid currentColor;
|
|
646
|
+
animation: spawner-pulse 1.5s ease-out infinite;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.live-spawner-icon.spawner-gemini.pulsing::after { border-color: #4ade80; }
|
|
650
|
+
.live-spawner-icon.spawner-codex.pulsing::after { border-color: #64c8ff; }
|
|
651
|
+
.live-spawner-icon.spawner-copilot.pulsing::after { border-color: #a855f7; }
|
|
652
|
+
|
|
653
|
+
@keyframes spawner-pulse {
|
|
654
|
+
0% {
|
|
655
|
+
transform: scale(1);
|
|
656
|
+
opacity: 1;
|
|
657
|
+
}
|
|
658
|
+
100% {
|
|
659
|
+
transform: scale(2);
|
|
660
|
+
opacity: 0;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/* Streaming animation */
|
|
665
|
+
.live-spawner-icon.streaming::after {
|
|
666
|
+
content: '';
|
|
667
|
+
position: absolute;
|
|
668
|
+
width: 6px;
|
|
669
|
+
height: 6px;
|
|
670
|
+
background: currentColor;
|
|
671
|
+
border-radius: 50%;
|
|
672
|
+
animation: spawner-stream 0.6s ease-in-out infinite;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
@keyframes spawner-stream {
|
|
676
|
+
0%, 100% { opacity: 0.3; transform: translateX(-8px); }
|
|
677
|
+
50% { opacity: 1; transform: translateX(8px); }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* Done state */
|
|
681
|
+
.live-spawner-icon.done::after {
|
|
682
|
+
content: '';
|
|
683
|
+
position: absolute;
|
|
684
|
+
width: 8px;
|
|
685
|
+
height: 4px;
|
|
686
|
+
border-left: 2px solid #22c55e;
|
|
687
|
+
border-bottom: 2px solid #22c55e;
|
|
688
|
+
transform: rotate(-45deg);
|
|
689
|
+
top: 8px;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* Error state */
|
|
693
|
+
.live-spawner-icon.error::before {
|
|
694
|
+
background: #ef4444;
|
|
695
|
+
}
|
|
696
|
+
.live-spawner-icon.error::after {
|
|
697
|
+
content: '!';
|
|
698
|
+
position: absolute;
|
|
699
|
+
color: white;
|
|
700
|
+
font-size: 10px;
|
|
701
|
+
font-weight: bold;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/* Text elements */
|
|
705
|
+
.live-spawner-text {
|
|
706
|
+
color: var(--text-primary);
|
|
707
|
+
white-space: nowrap;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.live-spawner-text strong {
|
|
711
|
+
color: var(--accent-lime, #a3e635);
|
|
712
|
+
letter-spacing: 0.05em;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.live-spawner-prompt,
|
|
716
|
+
.live-spawner-message {
|
|
717
|
+
flex: 1;
|
|
718
|
+
color: var(--text-secondary);
|
|
719
|
+
overflow: hidden;
|
|
720
|
+
text-overflow: ellipsis;
|
|
721
|
+
white-space: nowrap;
|
|
722
|
+
font-style: italic;
|
|
723
|
+
opacity: 0.8;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.live-spawner-progress {
|
|
727
|
+
padding: 0.15rem 0.4rem;
|
|
728
|
+
background: rgba(100, 200, 255, 0.2);
|
|
729
|
+
color: #64c8ff;
|
|
730
|
+
border-radius: 3px;
|
|
731
|
+
font-weight: 600;
|
|
732
|
+
font-size: 0.7rem;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.live-spawner-duration {
|
|
736
|
+
padding: 0.15rem 0.4rem;
|
|
737
|
+
background: rgba(34, 197, 94, 0.2);
|
|
738
|
+
color: #22c55e;
|
|
739
|
+
border-radius: 3px;
|
|
740
|
+
font-weight: 600;
|
|
741
|
+
font-size: 0.7rem;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.live-spawner-tokens {
|
|
745
|
+
padding: 0.15rem 0.4rem;
|
|
746
|
+
background: rgba(163, 230, 53, 0.2);
|
|
747
|
+
color: #a3e635;
|
|
748
|
+
border-radius: 3px;
|
|
749
|
+
font-size: 0.65rem;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.live-spawner-details {
|
|
753
|
+
color: var(--text-muted);
|
|
754
|
+
font-size: 0.7rem;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.live-spawner-error {
|
|
758
|
+
color: #ef4444;
|
|
759
|
+
font-size: 0.7rem;
|
|
760
|
+
overflow: hidden;
|
|
761
|
+
text-overflow: ellipsis;
|
|
762
|
+
white-space: nowrap;
|
|
763
|
+
max-width: 300px;
|
|
764
|
+
}
|
|
529
765
|
</style>
|
|
530
766
|
|
|
531
767
|
<script>
|
|
@@ -567,4 +803,218 @@ function collapseAllConversationTurns() {
|
|
|
567
803
|
toggle.classList.remove('expanded');
|
|
568
804
|
});
|
|
569
805
|
}
|
|
806
|
+
|
|
807
|
+
// ============================================
|
|
808
|
+
// Live Spawner Event Handling via WebSocket
|
|
809
|
+
// ============================================
|
|
810
|
+
|
|
811
|
+
// Track active spawner sessions by parent_event_id
|
|
812
|
+
const activeSpawners = new Map();
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Handle incoming spawner events from WebSocket
|
|
816
|
+
* Called from the main WebSocket handler in dashboard
|
|
817
|
+
*/
|
|
818
|
+
function handleSpawnerEvent(event) {
|
|
819
|
+
const { event_type, spawner_type, parent_event_id, data, timestamp } = event;
|
|
820
|
+
|
|
821
|
+
console.log('[SpawnerEvent]', event_type, spawner_type, data);
|
|
822
|
+
|
|
823
|
+
switch (event_type) {
|
|
824
|
+
case 'spawner_start':
|
|
825
|
+
showSpawnerStarted(spawner_type, parent_event_id, data, timestamp);
|
|
826
|
+
break;
|
|
827
|
+
case 'spawner_phase':
|
|
828
|
+
updateSpawnerPhase(spawner_type, parent_event_id, data);
|
|
829
|
+
break;
|
|
830
|
+
case 'spawner_complete':
|
|
831
|
+
showSpawnerCompleted(spawner_type, parent_event_id, data, timestamp);
|
|
832
|
+
break;
|
|
833
|
+
case 'spawner_tool_use':
|
|
834
|
+
showSpawnerToolUse(spawner_type, parent_event_id, data);
|
|
835
|
+
break;
|
|
836
|
+
case 'spawner_message':
|
|
837
|
+
showSpawnerMessage(spawner_type, parent_event_id, data);
|
|
838
|
+
break;
|
|
839
|
+
default:
|
|
840
|
+
console.log('[SpawnerEvent] Unknown event type:', event_type);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Show spawner started indicator
|
|
846
|
+
*/
|
|
847
|
+
function showSpawnerStarted(spawnerType, parentEventId, data, timestamp) {
|
|
848
|
+
// Store in active spawners
|
|
849
|
+
activeSpawners.set(parentEventId || `spawner-${Date.now()}`, {
|
|
850
|
+
spawnerType,
|
|
851
|
+
startTime: new Date(timestamp),
|
|
852
|
+
phase: 'initializing',
|
|
853
|
+
data
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// Create or update the live activity indicator
|
|
857
|
+
const indicator = getOrCreateLiveIndicator();
|
|
858
|
+
indicator.innerHTML = `
|
|
859
|
+
<span class="live-spawner-icon spawner-${spawnerType}"></span>
|
|
860
|
+
<span class="live-spawner-text">
|
|
861
|
+
<strong>${spawnerType.toUpperCase()}</strong> starting...
|
|
862
|
+
</span>
|
|
863
|
+
<span class="live-spawner-prompt">${(data.prompt_preview || '').substring(0, 60)}...</span>
|
|
864
|
+
`;
|
|
865
|
+
indicator.classList.add('active');
|
|
866
|
+
indicator.classList.remove('completed', 'failed');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Update spawner phase/progress
|
|
871
|
+
*/
|
|
872
|
+
function updateSpawnerPhase(spawnerType, parentEventId, data) {
|
|
873
|
+
const indicator = getOrCreateLiveIndicator();
|
|
874
|
+
const phaseText = data.phase || 'processing';
|
|
875
|
+
const progress = data.progress;
|
|
876
|
+
|
|
877
|
+
let progressHtml = '';
|
|
878
|
+
if (progress !== undefined && progress !== null) {
|
|
879
|
+
progressHtml = `<span class="live-spawner-progress">${progress}%</span>`;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
indicator.innerHTML = `
|
|
883
|
+
<span class="live-spawner-icon spawner-${spawnerType} pulsing"></span>
|
|
884
|
+
<span class="live-spawner-text">
|
|
885
|
+
<strong>${spawnerType.toUpperCase()}</strong> ${phaseText}
|
|
886
|
+
</span>
|
|
887
|
+
${progressHtml}
|
|
888
|
+
${data.details ? `<span class="live-spawner-details">${data.details}</span>` : ''}
|
|
889
|
+
`;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Show spawner completed
|
|
894
|
+
*/
|
|
895
|
+
function showSpawnerCompleted(spawnerType, parentEventId, data, timestamp) {
|
|
896
|
+
const indicator = getOrCreateLiveIndicator();
|
|
897
|
+
const success = data.success;
|
|
898
|
+
const duration = data.duration_seconds ? `${data.duration_seconds.toFixed(1)}s` : '';
|
|
899
|
+
|
|
900
|
+
indicator.classList.remove('active');
|
|
901
|
+
indicator.classList.add(success ? 'completed' : 'failed');
|
|
902
|
+
|
|
903
|
+
if (success) {
|
|
904
|
+
indicator.innerHTML = `
|
|
905
|
+
<span class="live-spawner-icon spawner-${spawnerType} done"></span>
|
|
906
|
+
<span class="live-spawner-text">
|
|
907
|
+
<strong>${spawnerType.toUpperCase()}</strong> completed
|
|
908
|
+
</span>
|
|
909
|
+
<span class="live-spawner-duration">${duration}</span>
|
|
910
|
+
${data.tokens_used ? `<span class="live-spawner-tokens">${data.tokens_used} tokens</span>` : ''}
|
|
911
|
+
`;
|
|
912
|
+
} else {
|
|
913
|
+
indicator.innerHTML = `
|
|
914
|
+
<span class="live-spawner-icon spawner-${spawnerType} error"></span>
|
|
915
|
+
<span class="live-spawner-text">
|
|
916
|
+
<strong>${spawnerType.toUpperCase()}</strong> failed
|
|
917
|
+
</span>
|
|
918
|
+
<span class="live-spawner-error">${data.error || 'Unknown error'}</span>
|
|
919
|
+
`;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Remove from active spawners
|
|
923
|
+
if (parentEventId) {
|
|
924
|
+
activeSpawners.delete(parentEventId);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Auto-hide after 5 seconds
|
|
928
|
+
setTimeout(() => {
|
|
929
|
+
if (!indicator.classList.contains('active')) {
|
|
930
|
+
indicator.classList.add('fade-out');
|
|
931
|
+
setTimeout(() => {
|
|
932
|
+
indicator.classList.remove('fade-out', 'completed', 'failed');
|
|
933
|
+
indicator.innerHTML = '';
|
|
934
|
+
}, 500);
|
|
935
|
+
}
|
|
936
|
+
}, 5000);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Show spawner tool use (brief flash)
|
|
941
|
+
*/
|
|
942
|
+
function showSpawnerToolUse(spawnerType, parentEventId, data) {
|
|
943
|
+
const indicator = getOrCreateLiveIndicator();
|
|
944
|
+
const toolName = data.tool_name || 'unknown';
|
|
945
|
+
|
|
946
|
+
// Quick update showing tool use
|
|
947
|
+
const currentHtml = indicator.innerHTML;
|
|
948
|
+
indicator.innerHTML = `
|
|
949
|
+
<span class="live-spawner-icon spawner-${spawnerType} tool-use"></span>
|
|
950
|
+
<span class="live-spawner-text">
|
|
951
|
+
<strong>${spawnerType.toUpperCase()}</strong> using ${toolName}
|
|
952
|
+
</span>
|
|
953
|
+
`;
|
|
954
|
+
|
|
955
|
+
// Revert after brief display (if still active)
|
|
956
|
+
setTimeout(() => {
|
|
957
|
+
if (activeSpawners.has(parentEventId)) {
|
|
958
|
+
// Still active, show executing state
|
|
959
|
+
indicator.innerHTML = `
|
|
960
|
+
<span class="live-spawner-icon spawner-${spawnerType} pulsing"></span>
|
|
961
|
+
<span class="live-spawner-text">
|
|
962
|
+
<strong>${spawnerType.toUpperCase()}</strong> executing
|
|
963
|
+
</span>
|
|
964
|
+
`;
|
|
965
|
+
}
|
|
966
|
+
}, 1500);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Show spawner message (streaming text)
|
|
971
|
+
*/
|
|
972
|
+
function showSpawnerMessage(spawnerType, parentEventId, data) {
|
|
973
|
+
// Just update the indicator with message preview
|
|
974
|
+
const indicator = getOrCreateLiveIndicator();
|
|
975
|
+
const preview = (data.message_preview || '').substring(0, 80);
|
|
976
|
+
|
|
977
|
+
indicator.innerHTML = `
|
|
978
|
+
<span class="live-spawner-icon spawner-${spawnerType} streaming"></span>
|
|
979
|
+
<span class="live-spawner-text">
|
|
980
|
+
<strong>${spawnerType.toUpperCase()}</strong> responding
|
|
981
|
+
</span>
|
|
982
|
+
<span class="live-spawner-message">${preview}...</span>
|
|
983
|
+
`;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Get or create the live activity indicator element
|
|
988
|
+
*/
|
|
989
|
+
function getOrCreateLiveIndicator() {
|
|
990
|
+
let indicator = document.getElementById('live-spawner-indicator');
|
|
991
|
+
if (!indicator) {
|
|
992
|
+
indicator = document.createElement('div');
|
|
993
|
+
indicator.id = 'live-spawner-indicator';
|
|
994
|
+
indicator.className = 'live-spawner-indicator';
|
|
995
|
+
|
|
996
|
+
// Insert at top of conversation feed
|
|
997
|
+
const feed = document.querySelector('.conversation-feed');
|
|
998
|
+
if (feed) {
|
|
999
|
+
feed.insertBefore(indicator, feed.firstChild);
|
|
1000
|
+
} else {
|
|
1001
|
+
// Fallback: insert in activity feed view
|
|
1002
|
+
const view = document.querySelector('.activity-feed-view');
|
|
1003
|
+
if (view) {
|
|
1004
|
+
const header = view.querySelector('.view-header');
|
|
1005
|
+
if (header && header.nextSibling) {
|
|
1006
|
+
view.insertBefore(indicator, header.nextSibling);
|
|
1007
|
+
} else {
|
|
1008
|
+
view.appendChild(indicator);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return indicator;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Export for use by main WebSocket handler
|
|
1017
|
+
if (typeof window !== 'undefined') {
|
|
1018
|
+
window.handleSpawnerEvent = handleSpawnerEvent;
|
|
1019
|
+
}
|
|
570
1020
|
</script>
|
htmlgraph/dashboard.html
CHANGED
|
@@ -967,6 +967,20 @@
|
|
|
967
967
|
.event-source-indicator.spike::before { background: #00C853; }
|
|
968
968
|
.event-source-indicator.delegation::before { background: #FF9100; }
|
|
969
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
|
+
|
|
970
984
|
.card-path {
|
|
971
985
|
font-family: 'JetBrains Mono', monospace;
|
|
972
986
|
font-size: 0.625rem;
|
|
@@ -5585,6 +5599,26 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
5585
5599
|
<ol class="activity-list" reversed>
|
|
5586
5600
|
`;
|
|
5587
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
|
+
|
|
5588
5622
|
// Render helper function
|
|
5589
5623
|
function renderEvent(item, isChild = false, depth = 0) {
|
|
5590
5624
|
const ts = item.timestamp ? new Date(item.timestamp).toLocaleString() : 'Unknown';
|
|
@@ -5623,6 +5657,12 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
5623
5657
|
const agentBadge = agentClass ? `<span class="badge agent ${agentClass}">${agentId}</span>` :
|
|
5624
5658
|
`<span class="badge">${agentId}</span>`;
|
|
5625
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
|
+
|
|
5626
5666
|
// Add expand icon for parents
|
|
5627
5667
|
const expandIcon = hasChildren ? '<span class="expand-icon"></span>' : '';
|
|
5628
5668
|
const indent = isChild ? 'padding-left: ' + (1.5 + depth * 1.5) + 'rem;' : '';
|
|
@@ -5638,6 +5678,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
5638
5678
|
${sourceBadge}
|
|
5639
5679
|
<span class="activity-tool">${tool}</span>
|
|
5640
5680
|
${agentBadge}
|
|
5681
|
+
${modelBadge}
|
|
5641
5682
|
</div>
|
|
5642
5683
|
<div class="activity-content">${escapeHtml(content.substring(0, 200))}${content.length > 200 ? '...' : ''}</div>
|
|
5643
5684
|
</li>
|