htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -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 +1366 -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 +1100 -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 +578 -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 +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- 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 +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- 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 +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- 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 +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- 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 +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1366 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>HtmlGraph Dashboard - Agent Activity & Orchestration</title>
|
|
7
|
+
<script src="/static/htmx.min.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="/static/style-redesign.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="dashboard-container">
|
|
12
|
+
<!-- HEADER -->
|
|
13
|
+
<header class="dashboard-header">
|
|
14
|
+
<div class="header-content">
|
|
15
|
+
<div class="logo">
|
|
16
|
+
<span class="logo-icon">▲</span>
|
|
17
|
+
<span>HtmlGraph</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="header-stats">
|
|
20
|
+
<div class="stat-badge">
|
|
21
|
+
<span class="stat-label">Events</span>
|
|
22
|
+
<span class="stat-value" id="event-count">0</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="stat-badge">
|
|
25
|
+
<span class="stat-label">Agents</span>
|
|
26
|
+
<span class="stat-value" id="agent-count">0</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="stat-badge">
|
|
29
|
+
<span class="stat-label">Sessions</span>
|
|
30
|
+
<span class="stat-value" id="session-count">0</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="ws-indicator">
|
|
33
|
+
<div class="ws-dot connected" id="ws-indicator"></div>
|
|
34
|
+
<span id="ws-status">Connected</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</header>
|
|
39
|
+
|
|
40
|
+
<!-- NAVIGATION TABS -->
|
|
41
|
+
<nav class="tabs-navigation">
|
|
42
|
+
<button class="tab-button active" data-tab="activity"
|
|
43
|
+
hx-get="/views/activity-feed"
|
|
44
|
+
hx-target="#content-area"
|
|
45
|
+
hx-trigger="click">
|
|
46
|
+
<span class="tab-icon">▤</span>
|
|
47
|
+
ACTIVITY
|
|
48
|
+
</button>
|
|
49
|
+
<button class="tab-button" data-tab="orchestration"
|
|
50
|
+
hx-get="/views/orchestration"
|
|
51
|
+
hx-target="#content-area"
|
|
52
|
+
hx-trigger="click">
|
|
53
|
+
<span class="tab-icon">◊</span>
|
|
54
|
+
ORCHESTRATION
|
|
55
|
+
</button>
|
|
56
|
+
<button class="tab-button" data-tab="work-items"
|
|
57
|
+
hx-get="/views/work-items"
|
|
58
|
+
hx-target="#content-area"
|
|
59
|
+
hx-trigger="click">
|
|
60
|
+
<span class="tab-icon">█</span>
|
|
61
|
+
WORK ITEMS
|
|
62
|
+
</button>
|
|
63
|
+
<button class="tab-button" data-tab="agents"
|
|
64
|
+
hx-get="/views/agents"
|
|
65
|
+
hx-target="#content-area"
|
|
66
|
+
hx-trigger="click">
|
|
67
|
+
<span class="tab-icon">◆</span>
|
|
68
|
+
AGENTS
|
|
69
|
+
</button>
|
|
70
|
+
<button class="tab-button" data-tab="metrics"
|
|
71
|
+
hx-get="/views/metrics"
|
|
72
|
+
hx-target="#content-area"
|
|
73
|
+
hx-trigger="click">
|
|
74
|
+
<span class="tab-icon">▼</span>
|
|
75
|
+
METRICS
|
|
76
|
+
</button>
|
|
77
|
+
</nav>
|
|
78
|
+
|
|
79
|
+
<!-- CONTENT AREA -->
|
|
80
|
+
<main class="content-area" id="content-area">
|
|
81
|
+
<div class="loading-indicator">
|
|
82
|
+
<div class="spinner"></div>
|
|
83
|
+
<p>Loading dashboard...</p>
|
|
84
|
+
</div>
|
|
85
|
+
</main>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- SCRIPTS -->
|
|
89
|
+
<script>
|
|
90
|
+
let eventCount = 0;
|
|
91
|
+
let agentSet = new Set();
|
|
92
|
+
let sessionCount = 0;
|
|
93
|
+
let processedEventIds = new Set();
|
|
94
|
+
let wsConnected = false;
|
|
95
|
+
|
|
96
|
+
// Load initial stats from server
|
|
97
|
+
async function loadInitialStats() {
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch('/api/initial-stats');
|
|
100
|
+
const data = await response.json();
|
|
101
|
+
|
|
102
|
+
eventCount = data.total_events || 0;
|
|
103
|
+
sessionCount = data.total_sessions || 0;
|
|
104
|
+
|
|
105
|
+
if (data.agents) {
|
|
106
|
+
data.agents.forEach(agent => agentSet.add(agent));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
document.getElementById('event-count').textContent = eventCount;
|
|
110
|
+
document.getElementById('agent-count').textContent = agentSet.size;
|
|
111
|
+
document.getElementById('session-count').textContent = sessionCount;
|
|
112
|
+
|
|
113
|
+
console.log('Initial stats loaded:', data);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Failed to load initial stats:', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Initialize dashboard on load
|
|
120
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
121
|
+
loadInitialStats();
|
|
122
|
+
htmx.ajax('GET', '/views/activity-feed', {target: '#content-area'});
|
|
123
|
+
connectWebSocket();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Tab switching
|
|
127
|
+
document.querySelectorAll('.tab-button').forEach(button => {
|
|
128
|
+
button.addEventListener('click', function() {
|
|
129
|
+
document.querySelectorAll('.tab-button').forEach(b => {
|
|
130
|
+
b.classList.remove('active');
|
|
131
|
+
});
|
|
132
|
+
this.classList.add('active');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// WebSocket Connection
|
|
137
|
+
function connectWebSocket() {
|
|
138
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
139
|
+
const ws = new WebSocket(wsProtocol + '//' + window.location.host + '/ws/events');
|
|
140
|
+
|
|
141
|
+
ws.onopen = function(event) {
|
|
142
|
+
console.log('WebSocket connected');
|
|
143
|
+
wsConnected = true;
|
|
144
|
+
updateWSStatus(true);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
ws.onmessage = function(event) {
|
|
148
|
+
try {
|
|
149
|
+
const data = JSON.parse(event.data);
|
|
150
|
+
|
|
151
|
+
if (data.type === 'event') {
|
|
152
|
+
if (processedEventIds.has(data.event_id)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
processedEventIds.add(data.event_id);
|
|
156
|
+
|
|
157
|
+
eventCount++;
|
|
158
|
+
if (data.agent_id) {
|
|
159
|
+
agentSet.add(data.agent_id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
document.getElementById('event-count').textContent = eventCount;
|
|
163
|
+
document.getElementById('agent-count').textContent = agentSet.size;
|
|
164
|
+
|
|
165
|
+
const badge = document.getElementById('event-count').parentElement;
|
|
166
|
+
badge.classList.add('pulse');
|
|
167
|
+
setTimeout(() => badge.classList.remove('pulse'), 500);
|
|
168
|
+
|
|
169
|
+
insertNewEventIntoActivityFeed(data);
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error('WebSocket message error:', e);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
ws.onerror = function(event) {
|
|
177
|
+
console.error('WebSocket error:', event);
|
|
178
|
+
updateWSStatus(false);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
ws.onclose = function(event) {
|
|
182
|
+
console.log('WebSocket disconnected, reconnecting in 3s...');
|
|
183
|
+
updateWSStatus(false);
|
|
184
|
+
setTimeout(connectWebSocket, 3000);
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function updateWSStatus(isConnected) {
|
|
189
|
+
wsConnected = isConnected;
|
|
190
|
+
const indicator = document.getElementById('ws-indicator');
|
|
191
|
+
const status = document.getElementById('ws-status');
|
|
192
|
+
if (indicator) {
|
|
193
|
+
indicator.classList.toggle('connected', isConnected);
|
|
194
|
+
indicator.classList.toggle('disconnected', !isConnected);
|
|
195
|
+
}
|
|
196
|
+
if (status) {
|
|
197
|
+
status.textContent = isConnected ? 'Connected' : 'Disconnected';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Insert new event into the grouped conversation turn activity feed
|
|
203
|
+
* Handles both UserQuery events (new turns) and child events (tool calls, etc.)
|
|
204
|
+
*/
|
|
205
|
+
function insertNewEventIntoActivityFeed(eventData) {
|
|
206
|
+
const conversationFeed = document.querySelector('.conversation-feed');
|
|
207
|
+
if (!conversationFeed) return;
|
|
208
|
+
|
|
209
|
+
// Check for empty state and remove it
|
|
210
|
+
const emptyState = conversationFeed.querySelector('.empty-state');
|
|
211
|
+
if (emptyState) {
|
|
212
|
+
emptyState.remove();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Get or create the turns list container
|
|
216
|
+
let turnsList = conversationFeed.querySelector('.conversation-turns-list');
|
|
217
|
+
if (!turnsList) {
|
|
218
|
+
turnsList = document.createElement('div');
|
|
219
|
+
turnsList.className = 'conversation-turns-list';
|
|
220
|
+
conversationFeed.appendChild(turnsList);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check for duplicates
|
|
224
|
+
if (document.querySelector(`[data-event-id="${eventData.event_id}"]`)) {
|
|
225
|
+
console.log('Event already exists:', eventData.event_id);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle UserQuery events - create new conversation turn
|
|
230
|
+
if (eventData.tool_name === 'UserQuery') {
|
|
231
|
+
insertNewConversationTurn(eventData, turnsList);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handle child events (tool calls, etc.)
|
|
236
|
+
if (eventData.parent_event_id) {
|
|
237
|
+
insertChildEvent(eventData);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.warn('Event with no parent_event_id and not UserQuery:', eventData);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create and insert a new conversation turn for a UserQuery event
|
|
246
|
+
*/
|
|
247
|
+
function insertNewConversationTurn(userQueryEvent, turnsList) {
|
|
248
|
+
const turnId = userQueryEvent.event_id;
|
|
249
|
+
const prompt = userQueryEvent.input_summary || userQueryEvent.summary || '';
|
|
250
|
+
const timestamp = formatTimestamp(userQueryEvent.timestamp);
|
|
251
|
+
|
|
252
|
+
// Determine if this turn has spawner delegation
|
|
253
|
+
const hasSpawner = userQueryEvent.context && userQueryEvent.context.spawner_type ? 'spawner' : 'direct';
|
|
254
|
+
const agentId = userQueryEvent.agent_id || 'unknown';
|
|
255
|
+
|
|
256
|
+
const turnHtml = `
|
|
257
|
+
<div class="conversation-turn"
|
|
258
|
+
data-turn-id="${turnId}"
|
|
259
|
+
data-spawner-type="${hasSpawner}"
|
|
260
|
+
data-agent="${agentId}">
|
|
261
|
+
<!-- User Query Parent Row (Clickable) -->
|
|
262
|
+
<div class="userquery-parent"
|
|
263
|
+
onclick="toggleConversationTurn('${turnId}')"
|
|
264
|
+
data-turn-id="${turnId}">
|
|
265
|
+
|
|
266
|
+
<!-- Expand/Collapse Toggle -->
|
|
267
|
+
<span class="expand-toggle-turn" id="toggle-${turnId}">▶</span>
|
|
268
|
+
|
|
269
|
+
<!-- User Prompt Text -->
|
|
270
|
+
<div class="prompt-section">
|
|
271
|
+
<span class="prompt-text" title="${escapeHtml(prompt)}">
|
|
272
|
+
${escapeHtml(prompt.substring(0, 100))}${prompt.length > 100 ? '...' : ''}
|
|
273
|
+
</span>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<!-- Stats Badges (initialized to 0) -->
|
|
277
|
+
<div class="turn-stats">
|
|
278
|
+
<span class="stat-badge tool-count" data-value="0" style="display: none;"></span>
|
|
279
|
+
<span class="stat-badge duration" data-value="0">0s</span>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<!-- Timestamp -->
|
|
283
|
+
<div class="turn-timestamp">
|
|
284
|
+
${timestamp}
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<!-- Child Events Container (Hidden by default) -->
|
|
289
|
+
<div class="turn-children collapsed" id="children-${turnId}">
|
|
290
|
+
<div class="no-children-message">
|
|
291
|
+
<span class="tree-connector">└─</span>
|
|
292
|
+
<span class="text-muted">No child events</span>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
`;
|
|
297
|
+
|
|
298
|
+
// Insert at top of turns list
|
|
299
|
+
const firstTurn = turnsList.firstChild;
|
|
300
|
+
if (firstTurn) {
|
|
301
|
+
firstTurn.insertAdjacentHTML('beforebegin', turnHtml);
|
|
302
|
+
} else {
|
|
303
|
+
turnsList.insertAdjacentHTML('afterbegin', turnHtml);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Auto-expand the new turn
|
|
307
|
+
const childrenContainer = document.getElementById(`children-${turnId}`);
|
|
308
|
+
const toggleButton = document.getElementById(`toggle-${turnId}`);
|
|
309
|
+
if (childrenContainer && toggleButton) {
|
|
310
|
+
childrenContainer.classList.remove('collapsed');
|
|
311
|
+
toggleButton.classList.add('expanded');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Highlight the new turn briefly
|
|
315
|
+
const newTurn = document.querySelector(`[data-turn-id="${turnId}"]`);
|
|
316
|
+
if (newTurn) {
|
|
317
|
+
highlightElement(newTurn.querySelector('.userquery-parent'));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Find the root conversation turn ID that contains this event in the DOM.
|
|
323
|
+
* Walks up the DOM tree to find the parent conversation-turn element.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} eventId - The event ID to find
|
|
326
|
+
* @returns {string|null} - The root UserQuery event_id or null if not found
|
|
327
|
+
*/
|
|
328
|
+
function findRootConversationTurn(eventId) {
|
|
329
|
+
// First, check if this event is already in the DOM
|
|
330
|
+
const eventElement = document.querySelector(`[data-event-id="${eventId}"]`);
|
|
331
|
+
if (!eventElement) {
|
|
332
|
+
return null; // Event not in DOM yet
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Walk up the DOM to find the nearest conversation turn container
|
|
336
|
+
let current = eventElement;
|
|
337
|
+
while (current && current.parentElement) {
|
|
338
|
+
current = current.parentElement;
|
|
339
|
+
if (current.classList && current.classList.contains('conversation-turn')) {
|
|
340
|
+
return current.getAttribute('data-turn-id');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Find or create a children container for an event.
|
|
349
|
+
* If the event is the root conversation turn, use children-${turnId}.
|
|
350
|
+
* If the event is a nested event, create a nested children container.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} parentEventId - The parent event ID
|
|
353
|
+
* @returns {HTMLElement|null} - The children container or null if not found
|
|
354
|
+
*/
|
|
355
|
+
function findOrCreateChildrenContainer(parentEventId) {
|
|
356
|
+
// First, check if this is a conversation turn (root level)
|
|
357
|
+
let container = document.getElementById(`children-${parentEventId}`);
|
|
358
|
+
if (container) {
|
|
359
|
+
return container;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Otherwise, look for the parent event in the DOM
|
|
363
|
+
const parentElement = document.querySelector(`[data-event-id="${parentEventId}"]`);
|
|
364
|
+
if (!parentElement) {
|
|
365
|
+
return null; // Parent event not in DOM yet
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Check if parent event already has a nested children container
|
|
369
|
+
let nestedContainer = parentElement.querySelector(':scope > .event-children');
|
|
370
|
+
if (!nestedContainer) {
|
|
371
|
+
// Create a nested children container
|
|
372
|
+
nestedContainer = document.createElement('div');
|
|
373
|
+
nestedContainer.className = 'event-children';
|
|
374
|
+
nestedContainer.setAttribute('data-parent-id', parentEventId);
|
|
375
|
+
parentElement.appendChild(nestedContainer);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return nestedContainer;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Calculate the depth of an event based on how many containers separate it from the root turn.
|
|
383
|
+
* Walks up the DOM tree counting .turn-children and .event-children containers.
|
|
384
|
+
*
|
|
385
|
+
* @param {string} parentEventId - The parent event ID
|
|
386
|
+
* @returns {number} - The depth (0 for direct children of a UserQuery turn)
|
|
387
|
+
*/
|
|
388
|
+
function calculateEventDepth(parentEventId) {
|
|
389
|
+
let depth = 0;
|
|
390
|
+
|
|
391
|
+
// Start by finding the children container for this parent
|
|
392
|
+
let container = document.getElementById(`children-${parentEventId}`);
|
|
393
|
+
if (!container) {
|
|
394
|
+
const parentElement = document.querySelector(`[data-event-id="${parentEventId}"]`);
|
|
395
|
+
if (parentElement) {
|
|
396
|
+
container = parentElement.querySelector(':scope > .event-children');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!container) {
|
|
401
|
+
return 0; // Parent not yet in DOM or is a root turn
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Walk up the DOM to count nesting levels
|
|
405
|
+
let current = container.parentElement; // Start from parent of container
|
|
406
|
+
while (current) {
|
|
407
|
+
// Check if we're in a .turn-children container (root level)
|
|
408
|
+
if (current.classList && current.classList.contains('turn-children')) {
|
|
409
|
+
return depth; // We've reached the root turn
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Check if we're in a child-event-row (nested event) that has children
|
|
413
|
+
if (current.classList && current.classList.contains('child-event-row')) {
|
|
414
|
+
depth++;
|
|
415
|
+
// Move up to find the next ancestor event row
|
|
416
|
+
current = current.parentElement; // Move to .event-children container
|
|
417
|
+
if (current) {
|
|
418
|
+
current = current.parentElement; // Move to parent event row
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
current = current.parentElement;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return depth;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Insert a child event into its parent (which could be a conversation turn or another event).
|
|
430
|
+
* Handles multi-level nesting for spawner delegations.
|
|
431
|
+
*/
|
|
432
|
+
function insertChildEvent(eventData) {
|
|
433
|
+
const parentEventId = eventData.parent_event_id;
|
|
434
|
+
if (!parentEventId) {
|
|
435
|
+
console.warn('Child event has no parent_event_id:', eventData.event_id);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Find or create the children container for this parent
|
|
440
|
+
const childrenContainer = findOrCreateChildrenContainer(parentEventId);
|
|
441
|
+
if (!childrenContainer) {
|
|
442
|
+
console.warn('Could not find or create children container for parent:', parentEventId);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Remove "no children" message if it exists
|
|
447
|
+
const noChildrenMsg = childrenContainer.querySelector('.no-children-message');
|
|
448
|
+
if (noChildrenMsg) {
|
|
449
|
+
noChildrenMsg.remove();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Extract event data
|
|
453
|
+
const toolName = eventData.tool_name || 'unknown';
|
|
454
|
+
const summary = eventData.output_summary || eventData.input_summary || eventData.summary || '';
|
|
455
|
+
const duration = eventData.duration_seconds || 0;
|
|
456
|
+
const timestamp = formatTimestamp(eventData.timestamp);
|
|
457
|
+
const agentId = eventData.agent_id || 'Claude Code';
|
|
458
|
+
const model = eventData.context && eventData.context.model ? eventData.context.model : null;
|
|
459
|
+
const spawnerType = eventData.context && eventData.context.spawner_type ? eventData.context.spawner_type : null;
|
|
460
|
+
const spawnedAgent = eventData.context && eventData.context.spawned_agent ? eventData.context.spawned_agent : null;
|
|
461
|
+
const costUsd = eventData.context && eventData.context.cost_usd ? eventData.context.cost_usd : null;
|
|
462
|
+
|
|
463
|
+
// Calculate depth: count how many levels of nesting this event is at
|
|
464
|
+
const depth = calculateEventDepth(parentEventId);
|
|
465
|
+
|
|
466
|
+
// Determine tree connector based on whether this is the last child
|
|
467
|
+
const existingChildren = childrenContainer.querySelectorAll(':scope > .child-event-row');
|
|
468
|
+
const isLastChild = existingChildren.length === 0;
|
|
469
|
+
const hasChildren = eventData.context && eventData.context.has_children;
|
|
470
|
+
const treeConnector = (isLastChild && !hasChildren) ? '└─' : '├─';
|
|
471
|
+
|
|
472
|
+
// Build child event HTML with nested container placeholder
|
|
473
|
+
let childHtml = `
|
|
474
|
+
<div class="child-event-row depth-${depth}"
|
|
475
|
+
data-event-id="${eventData.event_id}"
|
|
476
|
+
data-parent-id="${parentEventId}"
|
|
477
|
+
style="margin-left: ${depth * 20}px;">
|
|
478
|
+
|
|
479
|
+
<!-- Tree Connector -->
|
|
480
|
+
<span class="tree-connector">${treeConnector}</span>
|
|
481
|
+
|
|
482
|
+
<!-- Tool Name -->
|
|
483
|
+
<span class="child-tool-name">${escapeHtml(toolName)}</span>
|
|
484
|
+
|
|
485
|
+
<!-- Summary/Input -->
|
|
486
|
+
<span class="child-summary" title="${escapeHtml(summary)}">
|
|
487
|
+
${escapeHtml(summary.substring(0, 80))}${summary.length > 80 ? '...' : ''}
|
|
488
|
+
</span>
|
|
489
|
+
`;
|
|
490
|
+
|
|
491
|
+
// Add agent badge with spawner support
|
|
492
|
+
if (spawnerType) {
|
|
493
|
+
// Spawner delegation: show orchestrator → spawned AI
|
|
494
|
+
childHtml += `
|
|
495
|
+
<span class="child-agent-badge agent-${agentId.toLowerCase().replace(/\\s+/g, '-')}">
|
|
496
|
+
${escapeHtml(agentId)}
|
|
497
|
+
${model ? `<span class="model-indicator">${escapeHtml(model)}</span>` : ''}
|
|
498
|
+
</span>
|
|
499
|
+
<span class="delegation-arrow">→</span>
|
|
500
|
+
<span class="spawner-badge spawner-${spawnerType.toLowerCase()}">
|
|
501
|
+
${escapeHtml(spawnedAgent || spawnerType)}
|
|
502
|
+
${costUsd ? `<span class="cost-badge">$${costUsd.toFixed(2)}</span>` : ''}
|
|
503
|
+
</span>
|
|
504
|
+
`;
|
|
505
|
+
} else {
|
|
506
|
+
// Regular agent: just show agent name + model if available
|
|
507
|
+
childHtml += `
|
|
508
|
+
<span class="child-agent-badge agent-${agentId.toLowerCase().replace(/\\s+/g, '-')}">
|
|
509
|
+
${escapeHtml(agentId)}
|
|
510
|
+
${model ? `<span class="model-indicator">${escapeHtml(model)}</span>` : ''}
|
|
511
|
+
</span>
|
|
512
|
+
`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Add duration and timestamp
|
|
516
|
+
childHtml += `
|
|
517
|
+
<!-- Duration -->
|
|
518
|
+
<span class="child-duration">
|
|
519
|
+
${duration.toFixed(2)}s
|
|
520
|
+
</span>
|
|
521
|
+
|
|
522
|
+
<!-- Timestamp -->
|
|
523
|
+
<span class="child-timestamp">
|
|
524
|
+
${timestamp}
|
|
525
|
+
</span>
|
|
526
|
+
</div>
|
|
527
|
+
`;
|
|
528
|
+
|
|
529
|
+
// Insert child event
|
|
530
|
+
childrenContainer.insertAdjacentHTML('beforeend', childHtml);
|
|
531
|
+
|
|
532
|
+
// Update root conversation turn statistics
|
|
533
|
+
const rootTurnId = findRootConversationTurn(eventData.event_id);
|
|
534
|
+
if (rootTurnId) {
|
|
535
|
+
updateParentTurnStats(rootTurnId, eventData);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Highlight the new child event
|
|
539
|
+
const newChild = document.querySelector(`[data-event-id="${eventData.event_id}"]`);
|
|
540
|
+
if (newChild) {
|
|
541
|
+
highlightElement(newChild);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Update the statistics of a parent conversation turn based on a child event
|
|
547
|
+
*/
|
|
548
|
+
function updateParentTurnStats(parentTurnId, childEvent) {
|
|
549
|
+
const turnElement = document.querySelector(`[data-turn-id="${parentTurnId}"]`);
|
|
550
|
+
if (!turnElement) return;
|
|
551
|
+
|
|
552
|
+
const statsContainer = turnElement.querySelector('.turn-stats');
|
|
553
|
+
if (!statsContainer) return;
|
|
554
|
+
|
|
555
|
+
// Get current stats from badges
|
|
556
|
+
let toolCount = parseInt(statsContainer.querySelector('.stat-badge.tool-count')?.getAttribute('data-value') || '0', 10);
|
|
557
|
+
let totalDuration = parseFloat(statsContainer.querySelector('.stat-badge.duration')?.getAttribute('data-value') || '0');
|
|
558
|
+
let successCount = parseInt(statsContainer.querySelector('.stat-badge.success')?.getAttribute('data-value') || '0', 10);
|
|
559
|
+
let errorCount = parseInt(statsContainer.querySelector('.stat-badge.error')?.getAttribute('data-value') || '0', 10);
|
|
560
|
+
|
|
561
|
+
// Update counts based on event
|
|
562
|
+
if (childEvent.tool_name !== 'UserQuery') {
|
|
563
|
+
toolCount++;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
totalDuration += (childEvent.duration_seconds || 0);
|
|
567
|
+
|
|
568
|
+
// Determine success/error based on status
|
|
569
|
+
const status = childEvent.status || 'completed';
|
|
570
|
+
if (status === 'completed' || status === 'success') {
|
|
571
|
+
successCount++;
|
|
572
|
+
} else if (status === 'error' || status === 'failed') {
|
|
573
|
+
errorCount++;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Update tool count badge
|
|
577
|
+
const toolCountBadge = statsContainer.querySelector('.stat-badge.tool-count');
|
|
578
|
+
if (toolCountBadge) {
|
|
579
|
+
toolCountBadge.setAttribute('data-value', toolCount);
|
|
580
|
+
toolCountBadge.textContent = toolCount;
|
|
581
|
+
toolCountBadge.style.display = toolCount > 0 ? 'inline-block' : 'none';
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Update duration badge
|
|
585
|
+
const durationBadge = statsContainer.querySelector('.stat-badge.duration');
|
|
586
|
+
if (durationBadge) {
|
|
587
|
+
durationBadge.setAttribute('data-value', totalDuration.toFixed(2));
|
|
588
|
+
durationBadge.textContent = totalDuration.toFixed(2) + 's';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Update success badge
|
|
592
|
+
let successBadge = statsContainer.querySelector('.stat-badge.success');
|
|
593
|
+
if (successCount > 0) {
|
|
594
|
+
if (!successBadge) {
|
|
595
|
+
const badge = document.createElement('span');
|
|
596
|
+
badge.className = 'stat-badge success';
|
|
597
|
+
statsContainer.appendChild(badge);
|
|
598
|
+
successBadge = badge;
|
|
599
|
+
}
|
|
600
|
+
successBadge.setAttribute('data-value', successCount);
|
|
601
|
+
successBadge.textContent = `✓ ${successCount}`;
|
|
602
|
+
} else if (successBadge) {
|
|
603
|
+
successBadge.remove();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Update error badge
|
|
607
|
+
let errorBadge = statsContainer.querySelector('.stat-badge.error');
|
|
608
|
+
if (errorCount > 0) {
|
|
609
|
+
if (!errorBadge) {
|
|
610
|
+
const badge = document.createElement('span');
|
|
611
|
+
badge.className = 'stat-badge error';
|
|
612
|
+
statsContainer.appendChild(badge);
|
|
613
|
+
errorBadge = badge;
|
|
614
|
+
}
|
|
615
|
+
errorBadge.setAttribute('data-value', errorCount);
|
|
616
|
+
errorBadge.textContent = `✗ ${errorCount}`;
|
|
617
|
+
} else if (errorBadge) {
|
|
618
|
+
errorBadge.remove();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Format timestamp to readable format (HH:MM:SS)
|
|
624
|
+
*/
|
|
625
|
+
function formatTimestamp(timestamp) {
|
|
626
|
+
try {
|
|
627
|
+
const date = new Date(timestamp);
|
|
628
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
629
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
630
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
631
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
632
|
+
} catch (e) {
|
|
633
|
+
return timestamp;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Escape HTML special characters to prevent XSS
|
|
639
|
+
*/
|
|
640
|
+
function escapeHtml(text) {
|
|
641
|
+
if (!text) return '';
|
|
642
|
+
const div = document.createElement('div');
|
|
643
|
+
div.textContent = text;
|
|
644
|
+
return div.innerHTML;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Highlight an element briefly with a background color animation
|
|
649
|
+
*/
|
|
650
|
+
function highlightElement(element) {
|
|
651
|
+
if (!element) return;
|
|
652
|
+
element.style.transition = 'background-color 0.3s ease';
|
|
653
|
+
element.style.backgroundColor = 'rgba(163, 230, 53, 0.2)';
|
|
654
|
+
setTimeout(() => {
|
|
655
|
+
element.style.backgroundColor = '';
|
|
656
|
+
}, 500);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function highlightRow(row) {
|
|
660
|
+
if (row) {
|
|
661
|
+
row.classList.add('new-event-highlight');
|
|
662
|
+
setTimeout(() => {
|
|
663
|
+
row.classList.remove('new-event-highlight');
|
|
664
|
+
}, 2000);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function createActivityRowHTML(eventData) {
|
|
669
|
+
// Event type emoji mapping
|
|
670
|
+
let eventEmoji = '📋'; // clipboard
|
|
671
|
+
if (eventData.event_type === 'delegation') eventEmoji = '🔗'; // link
|
|
672
|
+
else if (eventData.event_type === 'tool_call') eventEmoji = '🔨'; // hammer
|
|
673
|
+
else if (eventData.event_type === 'completion') eventEmoji = '🎉'; // party
|
|
674
|
+
else if (eventData.event_type === 'tool_result') eventEmoji = '✅'; // check
|
|
675
|
+
else if (eventData.event_type === 'error') eventEmoji = '❌'; // x
|
|
676
|
+
|
|
677
|
+
const inputSummary = eventData.input_summary ? eventData.input_summary.substring(0, 150) : '';
|
|
678
|
+
const inputTruncated = eventData.input_summary && eventData.input_summary.length > 150 ? '...' : '';
|
|
679
|
+
const outputSummary = eventData.output_summary ? eventData.output_summary.substring(0, 150) : '';
|
|
680
|
+
const outputTruncated = eventData.output_summary && eventData.output_summary.length > 150 ? '...' : '';
|
|
681
|
+
|
|
682
|
+
const isChild = !!eventData.parent_event_id;
|
|
683
|
+
const rowClass = isChild ? 'child-row hidden' : 'parent-row';
|
|
684
|
+
const borderStyle = isChild ? 'border-left: 4px solid var(--text-muted);' : 'border-left: 4px solid var(--accent-lime);';
|
|
685
|
+
|
|
686
|
+
// New column order: Agent | Tool | Input | Output | Status | Timestamp (no ID column)
|
|
687
|
+
const html = `
|
|
688
|
+
<tr class="activity-row ${rowClass} event-${eventData.status || 'pending'}"
|
|
689
|
+
data-event-id="${escapeHtml(eventData.event_id)}"
|
|
690
|
+
${isChild ? `data-parent="${escapeHtml(eventData.parent_event_id)}"` : ''}
|
|
691
|
+
style="${borderStyle}">
|
|
692
|
+
<td class="col-agent">
|
|
693
|
+
${isChild ? '<span class="child-indicator">↳</span>' : ''}
|
|
694
|
+
<span class="agent-badge agent-${escapeHtml(eventData.agent_id.toLowerCase())}">${escapeHtml(eventData.agent_id)}</span>
|
|
695
|
+
</td>
|
|
696
|
+
<td class="col-tool">
|
|
697
|
+
<span class="event-type-badge" title="${escapeHtml(eventData.event_type)}">
|
|
698
|
+
${eventEmoji}
|
|
699
|
+
</span>
|
|
700
|
+
${eventData.tool_name ? `<code class="tool-name">${escapeHtml(eventData.tool_name)}</code>` : '<span class="text-muted">-</span>'}
|
|
701
|
+
</td>
|
|
702
|
+
<td class="col-input">
|
|
703
|
+
${inputSummary ? `<span class="truncate" title="${escapeHtml(eventData.input_summary)}">${escapeHtml(inputSummary)}${inputTruncated}</span>` : '<span class="text-muted">-</span>'}
|
|
704
|
+
</td>
|
|
705
|
+
<td class="col-output">
|
|
706
|
+
${outputSummary ? `<span class="truncate" title="${escapeHtml(eventData.output_summary)}">${escapeHtml(outputSummary)}${outputTruncated}</span>` : '<span class="text-muted">-</span>'}
|
|
707
|
+
</td>
|
|
708
|
+
<td class="col-status">
|
|
709
|
+
<span class="status-badge status-${eventData.status || 'pending'}">${escapeHtml(eventData.status || 'pending')}</span>
|
|
710
|
+
</td>
|
|
711
|
+
<td class="col-timestamp">
|
|
712
|
+
<span class="timestamp-text" data-utc-time="${escapeHtml(eventData.timestamp)}">${escapeHtml(eventData.timestamp)}</span>
|
|
713
|
+
</td>
|
|
714
|
+
</tr>
|
|
715
|
+
`;
|
|
716
|
+
return html;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function convertTimestampsToLocal() {
|
|
720
|
+
const timestampElements = document.querySelectorAll('[data-utc-time]');
|
|
721
|
+
timestampElements.forEach(element => {
|
|
722
|
+
const utcTime = element.getAttribute('data-utc-time');
|
|
723
|
+
if (utcTime) {
|
|
724
|
+
try {
|
|
725
|
+
const date = new Date(utcTime.replace(' ', 'T') + 'Z');
|
|
726
|
+
const localTime = new Intl.DateTimeFormat('en-US', {
|
|
727
|
+
year: 'numeric',
|
|
728
|
+
month: '2-digit',
|
|
729
|
+
day: '2-digit',
|
|
730
|
+
hour: '2-digit',
|
|
731
|
+
minute: '2-digit',
|
|
732
|
+
second: '2-digit',
|
|
733
|
+
hour12: false,
|
|
734
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
735
|
+
}).format(date);
|
|
736
|
+
element.textContent = localTime;
|
|
737
|
+
element.setAttribute('title', `UTC: ${utcTime} | Local: ${localTime}`);
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.warn('Failed to convert timestamp:', utcTime, err);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Convert timestamps after HTMX loads content
|
|
746
|
+
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
|
747
|
+
if (evt.detail.target.id === 'content-area') {
|
|
748
|
+
if (typeof convertTimestampsToLocal === 'function') {
|
|
749
|
+
convertTimestampsToLocal();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Toggle child rows visibility (for expandable tracing)
|
|
755
|
+
function toggleChildren(parentRow) {
|
|
756
|
+
const eventId = parentRow.dataset.eventId;
|
|
757
|
+
const children = document.querySelectorAll(`[data-parent="${eventId}"]`);
|
|
758
|
+
const expandIcon = parentRow.querySelector('.expand-icon');
|
|
759
|
+
|
|
760
|
+
children.forEach(child => {
|
|
761
|
+
child.classList.toggle('hidden');
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
if (expandIcon) {
|
|
765
|
+
expandIcon.classList.toggle('expanded');
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Expand all parent rows
|
|
770
|
+
function expandAll() {
|
|
771
|
+
document.querySelectorAll('.parent-row.has-children').forEach(row => {
|
|
772
|
+
const eventId = row.dataset.eventId;
|
|
773
|
+
const children = document.querySelectorAll(`[data-parent="${eventId}"]`);
|
|
774
|
+
const expandIcon = row.querySelector('.expand-icon');
|
|
775
|
+
|
|
776
|
+
children.forEach(child => {
|
|
777
|
+
child.classList.remove('hidden');
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
if (expandIcon) {
|
|
781
|
+
expandIcon.classList.add('expanded');
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Collapse all parent rows
|
|
787
|
+
function collapseAll() {
|
|
788
|
+
document.querySelectorAll('.child-row').forEach(child => {
|
|
789
|
+
child.classList.add('hidden');
|
|
790
|
+
});
|
|
791
|
+
document.querySelectorAll('.expand-icon').forEach(icon => {
|
|
792
|
+
icon.classList.remove('expanded');
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ============================================
|
|
797
|
+
// Jaeger-Style Trace Interactivity Functions
|
|
798
|
+
// (Must be global for HTMX-loaded partials)
|
|
799
|
+
// ============================================
|
|
800
|
+
|
|
801
|
+
// Toggle expand/collapse with animation
|
|
802
|
+
function toggleTrace(id, event) {
|
|
803
|
+
if (event) event.stopPropagation();
|
|
804
|
+
|
|
805
|
+
const children = document.querySelectorAll(`[data-parent="${id}"]`);
|
|
806
|
+
const toggle = document.querySelector(`[data-id="${id}"] .expand-toggle`);
|
|
807
|
+
|
|
808
|
+
children.forEach(child => {
|
|
809
|
+
child.classList.toggle('collapsed');
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
if (toggle) {
|
|
813
|
+
toggle.classList.toggle('expanded');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Update breadcrumbs if drilling into a trace
|
|
817
|
+
updateBreadcrumbs(id);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Highlight ancestor path on hover (Jaeger pattern)
|
|
821
|
+
function highlightAncestors(row) {
|
|
822
|
+
clearAncestorHighlight();
|
|
823
|
+
|
|
824
|
+
let parentId = row.dataset.parent;
|
|
825
|
+
while (parentId) {
|
|
826
|
+
const parent = document.querySelector(`[data-id="${parentId}"]`);
|
|
827
|
+
if (parent) {
|
|
828
|
+
parent.classList.add('ancestor-highlight');
|
|
829
|
+
parentId = parent.dataset.parent;
|
|
830
|
+
} else {
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Clear all ancestor highlights
|
|
837
|
+
function clearAncestorHighlight() {
|
|
838
|
+
document.querySelectorAll('.ancestor-highlight').forEach(el => {
|
|
839
|
+
el.classList.remove('ancestor-highlight');
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Expand all traces
|
|
844
|
+
function expandAllTraces() {
|
|
845
|
+
document.querySelectorAll('.child-row').forEach(child => {
|
|
846
|
+
child.classList.remove('collapsed');
|
|
847
|
+
});
|
|
848
|
+
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
849
|
+
toggle.classList.add('expanded');
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Collapse all traces
|
|
854
|
+
function collapseAllTraces() {
|
|
855
|
+
document.querySelectorAll('.child-row').forEach(child => {
|
|
856
|
+
child.classList.add('collapsed');
|
|
857
|
+
});
|
|
858
|
+
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
859
|
+
toggle.classList.remove('expanded');
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Breadcrumb management
|
|
864
|
+
let breadcrumbStack = ['root'];
|
|
865
|
+
|
|
866
|
+
function updateBreadcrumbs(id) {
|
|
867
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
868
|
+
if (!breadcrumbsContainer) return;
|
|
869
|
+
|
|
870
|
+
const row = document.querySelector(`[data-id="${id}"]`);
|
|
871
|
+
if (!row) return;
|
|
872
|
+
|
|
873
|
+
// Only show breadcrumbs when we have nested navigation
|
|
874
|
+
const depth = parseInt(row.dataset.depth || '0');
|
|
875
|
+
if (depth > 0 || breadcrumbStack.length > 1) {
|
|
876
|
+
breadcrumbsContainer.style.display = 'flex';
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Get tool name or operation for breadcrumb label
|
|
880
|
+
const toolName = row.querySelector('.tool-name');
|
|
881
|
+
const label = toolName ? toolName.textContent : `Trace ${id.substring(0, 8)}`;
|
|
882
|
+
|
|
883
|
+
// Add to breadcrumb stack if not already present
|
|
884
|
+
if (!breadcrumbStack.includes(id)) {
|
|
885
|
+
breadcrumbStack.push(id);
|
|
886
|
+
|
|
887
|
+
const separator = document.createElement('span');
|
|
888
|
+
separator.className = 'separator';
|
|
889
|
+
separator.textContent = '>';
|
|
890
|
+
|
|
891
|
+
const crumb = document.createElement('span');
|
|
892
|
+
crumb.className = 'breadcrumb active';
|
|
893
|
+
crumb.dataset.id = id;
|
|
894
|
+
crumb.textContent = label;
|
|
895
|
+
crumb.onclick = () => navigateToBreadcrumb(id);
|
|
896
|
+
|
|
897
|
+
// Remove active class from previous breadcrumbs
|
|
898
|
+
breadcrumbsContainer.querySelectorAll('.breadcrumb').forEach(b => {
|
|
899
|
+
b.classList.remove('active');
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
breadcrumbsContainer.appendChild(separator);
|
|
903
|
+
breadcrumbsContainer.appendChild(crumb);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function navigateToBreadcrumb(id) {
|
|
908
|
+
// Find position in stack and remove everything after
|
|
909
|
+
const index = breadcrumbStack.indexOf(id);
|
|
910
|
+
if (index === -1) return;
|
|
911
|
+
|
|
912
|
+
// Remove breadcrumbs after this one
|
|
913
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
914
|
+
if (!breadcrumbsContainer) return;
|
|
915
|
+
|
|
916
|
+
const allCrumbs = breadcrumbsContainer.querySelectorAll('.breadcrumb, .separator');
|
|
917
|
+
|
|
918
|
+
let removing = false;
|
|
919
|
+
allCrumbs.forEach(el => {
|
|
920
|
+
if (removing) {
|
|
921
|
+
el.remove();
|
|
922
|
+
}
|
|
923
|
+
if (el.dataset && el.dataset.id === id) {
|
|
924
|
+
el.classList.add('active');
|
|
925
|
+
removing = true;
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// Update stack
|
|
930
|
+
breadcrumbStack = breadcrumbStack.slice(0, index + 1);
|
|
931
|
+
|
|
932
|
+
// Hide breadcrumbs if back to root
|
|
933
|
+
if (breadcrumbStack.length <= 1) {
|
|
934
|
+
breadcrumbsContainer.style.display = 'none';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function resetBreadcrumbs() {
|
|
939
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
940
|
+
if (!breadcrumbsContainer) return;
|
|
941
|
+
|
|
942
|
+
breadcrumbsContainer.innerHTML = '<span class="breadcrumb" data-id="root" onclick="resetBreadcrumbs()">Session</span>';
|
|
943
|
+
breadcrumbsContainer.style.display = 'none';
|
|
944
|
+
breadcrumbStack = ['root'];
|
|
945
|
+
|
|
946
|
+
// Collapse all traces when resetting
|
|
947
|
+
collapseAllTraces();
|
|
948
|
+
}
|
|
949
|
+
</script>
|
|
950
|
+
|
|
951
|
+
<style>
|
|
952
|
+
.new-event-highlight {
|
|
953
|
+
animation: highlightPulse 2s ease-out;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
@keyframes highlightPulse {
|
|
957
|
+
0% {
|
|
958
|
+
background-color: rgba(205, 255, 0, 0.1);
|
|
959
|
+
border-left-color: #CDFF00 !important;
|
|
960
|
+
}
|
|
961
|
+
100% {
|
|
962
|
+
background-color: transparent;
|
|
963
|
+
border-left-color: transparent !important;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* Activity Feed Table Styles */
|
|
968
|
+
.activity-table {
|
|
969
|
+
width: 100%;
|
|
970
|
+
border-collapse: separate;
|
|
971
|
+
border-spacing: 0;
|
|
972
|
+
background: var(--bg-card);
|
|
973
|
+
border: 1px solid var(--border-subtle);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.activity-table thead {
|
|
977
|
+
background: var(--bg-darker);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.activity-table th {
|
|
981
|
+
padding: var(--spacing-lg);
|
|
982
|
+
text-align: left;
|
|
983
|
+
color: var(--accent-lime);
|
|
984
|
+
font-weight: 700;
|
|
985
|
+
font-size: 0.85rem;
|
|
986
|
+
text-transform: uppercase;
|
|
987
|
+
letter-spacing: 0.05em;
|
|
988
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.activity-table td {
|
|
992
|
+
padding: var(--spacing-md) var(--spacing-lg);
|
|
993
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
994
|
+
color: var(--text-primary);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.activity-row:hover {
|
|
998
|
+
background: var(--bg-hover);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.activity-row.parent-row {
|
|
1002
|
+
font-weight: 500;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.activity-row.child-row {
|
|
1006
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1007
|
+
font-size: 0.95rem;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.event-type-badge {
|
|
1011
|
+
margin-right: var(--spacing-sm);
|
|
1012
|
+
font-size: 1.1rem;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
.agent-badge {
|
|
1016
|
+
display: inline-flex;
|
|
1017
|
+
align-items: center;
|
|
1018
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
1019
|
+
background: rgba(205, 255, 0, 0.1);
|
|
1020
|
+
border: 1px solid rgba(205, 255, 0, 0.3);
|
|
1021
|
+
border-radius: 2px;
|
|
1022
|
+
font-size: 0.8rem;
|
|
1023
|
+
font-weight: 600;
|
|
1024
|
+
text-transform: uppercase;
|
|
1025
|
+
letter-spacing: 0.05em;
|
|
1026
|
+
color: var(--accent-lime);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.agent-badge.agent-claude {
|
|
1030
|
+
background: rgba(139, 92, 246, 0.1);
|
|
1031
|
+
border-color: rgba(139, 92, 246, 0.3);
|
|
1032
|
+
color: var(--agent-claude);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.agent-badge.agent-gemini {
|
|
1036
|
+
background: rgba(59, 130, 246, 0.1);
|
|
1037
|
+
border-color: rgba(59, 130, 246, 0.3);
|
|
1038
|
+
color: var(--agent-gemini);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.child-indicator {
|
|
1042
|
+
margin-left: var(--spacing-md);
|
|
1043
|
+
color: var(--text-muted);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.tool-name {
|
|
1047
|
+
color: var(--accent-lime);
|
|
1048
|
+
font-size: 0.9rem;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.status-badge {
|
|
1052
|
+
display: inline-block;
|
|
1053
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
1054
|
+
border-radius: 2px;
|
|
1055
|
+
font-size: 0.75rem;
|
|
1056
|
+
font-weight: 600;
|
|
1057
|
+
text-transform: uppercase;
|
|
1058
|
+
letter-spacing: 0.05em;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.status-badge.success {
|
|
1062
|
+
background: rgba(16, 185, 129, 0.15);
|
|
1063
|
+
color: var(--status-success);
|
|
1064
|
+
border: 1px solid var(--status-success);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
.status-badge.progress {
|
|
1068
|
+
background: rgba(59, 130, 246, 0.15);
|
|
1069
|
+
color: var(--status-progress);
|
|
1070
|
+
border: 1px solid var(--status-progress);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.status-badge.blocked {
|
|
1074
|
+
background: rgba(239, 68, 68, 0.15);
|
|
1075
|
+
color: var(--status-blocked);
|
|
1076
|
+
border: 1px solid var(--status-blocked);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
.status-badge.todo {
|
|
1080
|
+
background: rgba(107, 114, 128, 0.15);
|
|
1081
|
+
color: var(--status-todo);
|
|
1082
|
+
border: 1px solid var(--status-todo);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.status-badge.pending {
|
|
1086
|
+
background: rgba(99, 102, 241, 0.15);
|
|
1087
|
+
color: #6366F1;
|
|
1088
|
+
border: 1px solid #6366F1;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
.status-badge.done {
|
|
1092
|
+
background: rgba(139, 92, 246, 0.15);
|
|
1093
|
+
color: var(--status-done);
|
|
1094
|
+
border: 1px solid var(--status-done);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.event-id-code {
|
|
1098
|
+
color: var(--accent-lime);
|
|
1099
|
+
font-size: 0.8rem;
|
|
1100
|
+
font-weight: 600;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.text-muted {
|
|
1104
|
+
color: var(--text-muted);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.truncate {
|
|
1108
|
+
overflow: hidden;
|
|
1109
|
+
text-overflow: ellipsis;
|
|
1110
|
+
white-space: nowrap;
|
|
1111
|
+
display: inline-block;
|
|
1112
|
+
max-width: 200px;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.empty-state {
|
|
1116
|
+
display: flex;
|
|
1117
|
+
flex-direction: column;
|
|
1118
|
+
align-items: center;
|
|
1119
|
+
justify-content: center;
|
|
1120
|
+
height: 300px;
|
|
1121
|
+
color: var(--text-muted);
|
|
1122
|
+
text-align: center;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.empty-state p {
|
|
1126
|
+
font-size: 1.1rem;
|
|
1127
|
+
margin-bottom: var(--spacing-md);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.empty-state small {
|
|
1131
|
+
color: var(--text-secondary);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/* New column widths - Input/Output are flexible, others fixed */
|
|
1135
|
+
.col-agent { width: 120px; }
|
|
1136
|
+
.col-tool { width: 100px; }
|
|
1137
|
+
.col-input { width: auto; }
|
|
1138
|
+
.col-output { width: auto; }
|
|
1139
|
+
.col-status { width: 100px; }
|
|
1140
|
+
.col-timestamp { width: 140px; }
|
|
1141
|
+
|
|
1142
|
+
/* Make table use fixed layout for predictable column widths */
|
|
1143
|
+
.activity-table {
|
|
1144
|
+
table-layout: fixed;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/* Expandable tracing structure */
|
|
1148
|
+
.expand-icon {
|
|
1149
|
+
cursor: pointer;
|
|
1150
|
+
display: inline-block;
|
|
1151
|
+
margin-right: 0.5rem;
|
|
1152
|
+
transition: transform 0.2s ease;
|
|
1153
|
+
font-size: 0.7rem;
|
|
1154
|
+
color: var(--text-muted);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.expand-icon.expanded {
|
|
1158
|
+
transform: rotate(90deg);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.parent-row.has-children {
|
|
1162
|
+
cursor: pointer;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.child-count {
|
|
1166
|
+
font-size: 0.75rem;
|
|
1167
|
+
color: var(--text-muted);
|
|
1168
|
+
margin-left: 0.5rem;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.child-row.hidden {
|
|
1172
|
+
display: none;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* Nested event children container styles */
|
|
1176
|
+
.event-children {
|
|
1177
|
+
display: flex;
|
|
1178
|
+
flex-direction: column;
|
|
1179
|
+
margin-top: 0;
|
|
1180
|
+
border-left: 1px solid var(--border-subtle);
|
|
1181
|
+
padding-left: var(--spacing-sm);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.child-event-row {
|
|
1185
|
+
display: flex;
|
|
1186
|
+
align-items: center;
|
|
1187
|
+
gap: var(--spacing-sm);
|
|
1188
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
1189
|
+
border-radius: 4px;
|
|
1190
|
+
background: rgba(0, 0, 0, 0.1);
|
|
1191
|
+
font-size: 0.9rem;
|
|
1192
|
+
color: var(--text-primary);
|
|
1193
|
+
border: 1px solid transparent;
|
|
1194
|
+
transition: all 0.2s ease;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.child-event-row:hover {
|
|
1198
|
+
background: rgba(0, 0, 0, 0.15);
|
|
1199
|
+
border-color: var(--border-subtle);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.child-event-row.depth-0 {
|
|
1203
|
+
margin-left: 0;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.child-event-row.depth-1 {
|
|
1207
|
+
margin-left: 20px;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.child-event-row.depth-2 {
|
|
1211
|
+
margin-left: 40px;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.child-event-row.depth-3 {
|
|
1215
|
+
margin-left: 60px;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.child-event-row.depth-4 {
|
|
1219
|
+
margin-left: 80px;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.child-event-row.depth-5 {
|
|
1223
|
+
margin-left: 100px;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.tree-connector {
|
|
1227
|
+
color: var(--text-muted);
|
|
1228
|
+
font-size: 0.85rem;
|
|
1229
|
+
font-family: monospace;
|
|
1230
|
+
flex-shrink: 0;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.child-tool-name {
|
|
1234
|
+
font-weight: 600;
|
|
1235
|
+
color: var(--accent-lime);
|
|
1236
|
+
flex-shrink: 0;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.child-summary {
|
|
1240
|
+
flex: 1;
|
|
1241
|
+
overflow: hidden;
|
|
1242
|
+
text-overflow: ellipsis;
|
|
1243
|
+
white-space: nowrap;
|
|
1244
|
+
color: var(--text-secondary);
|
|
1245
|
+
font-size: 0.85rem;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.child-agent-badge {
|
|
1249
|
+
display: inline-flex;
|
|
1250
|
+
align-items: center;
|
|
1251
|
+
gap: var(--spacing-xs);
|
|
1252
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
1253
|
+
background: rgba(205, 255, 0, 0.1);
|
|
1254
|
+
border: 1px solid rgba(205, 255, 0, 0.3);
|
|
1255
|
+
border-radius: 3px;
|
|
1256
|
+
font-size: 0.75rem;
|
|
1257
|
+
font-weight: 600;
|
|
1258
|
+
color: var(--accent-lime);
|
|
1259
|
+
flex-shrink: 0;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
.child-agent-badge.agent-claude {
|
|
1263
|
+
background: rgba(139, 92, 246, 0.1);
|
|
1264
|
+
border-color: rgba(139, 92, 246, 0.3);
|
|
1265
|
+
color: var(--agent-claude);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.child-agent-badge.agent-gemini {
|
|
1269
|
+
background: rgba(59, 130, 246, 0.1);
|
|
1270
|
+
border-color: rgba(59, 130, 246, 0.3);
|
|
1271
|
+
color: var(--agent-gemini);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.model-indicator {
|
|
1275
|
+
font-size: 0.7rem;
|
|
1276
|
+
opacity: 0.8;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.delegation-arrow {
|
|
1280
|
+
color: var(--text-muted);
|
|
1281
|
+
font-weight: bold;
|
|
1282
|
+
flex-shrink: 0;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.spawner-badge {
|
|
1286
|
+
display: inline-flex;
|
|
1287
|
+
align-items: center;
|
|
1288
|
+
gap: var(--spacing-xs);
|
|
1289
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
1290
|
+
background: rgba(139, 92, 246, 0.15);
|
|
1291
|
+
border: 1px solid rgba(139, 92, 246, 0.4);
|
|
1292
|
+
border-radius: 3px;
|
|
1293
|
+
font-size: 0.75rem;
|
|
1294
|
+
font-weight: 600;
|
|
1295
|
+
color: #8B5CF6;
|
|
1296
|
+
flex-shrink: 0;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.spawner-badge.spawner-gemini {
|
|
1300
|
+
background: rgba(59, 130, 246, 0.15);
|
|
1301
|
+
border-color: rgba(59, 130, 246, 0.4);
|
|
1302
|
+
color: #3B82F6;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.spawner-badge.spawner-codex {
|
|
1306
|
+
background: rgba(34, 197, 94, 0.15);
|
|
1307
|
+
border-color: rgba(34, 197, 94, 0.4);
|
|
1308
|
+
color: #22C55E;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.spawner-badge.spawner-copilot {
|
|
1312
|
+
background: rgba(251, 146, 60, 0.15);
|
|
1313
|
+
border-color: rgba(251, 146, 60, 0.4);
|
|
1314
|
+
color: #FB923C;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
.cost-badge {
|
|
1318
|
+
font-size: 0.7rem;
|
|
1319
|
+
opacity: 0.9;
|
|
1320
|
+
margin-left: 2px;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
.child-duration {
|
|
1324
|
+
font-size: 0.8rem;
|
|
1325
|
+
color: var(--text-secondary);
|
|
1326
|
+
flex-shrink: 0;
|
|
1327
|
+
font-family: monospace;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
.child-timestamp {
|
|
1331
|
+
font-size: 0.8rem;
|
|
1332
|
+
color: var(--text-muted);
|
|
1333
|
+
flex-shrink: 0;
|
|
1334
|
+
font-family: monospace;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
@media (max-width: 1024px) {
|
|
1338
|
+
.col-agent { width: 100px; }
|
|
1339
|
+
.col-tool { width: 90px; }
|
|
1340
|
+
.col-status { width: 90px; }
|
|
1341
|
+
.col-timestamp { width: 120px; }
|
|
1342
|
+
|
|
1343
|
+
.activity-table th,
|
|
1344
|
+
.activity-table td {
|
|
1345
|
+
padding: var(--spacing-md) var(--spacing-sm);
|
|
1346
|
+
font-size: 0.9rem;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.truncate {
|
|
1350
|
+
max-width: 100%;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.child-event-row {
|
|
1354
|
+
flex-wrap: wrap;
|
|
1355
|
+
gap: var(--spacing-xs);
|
|
1356
|
+
padding: var(--spacing-sm);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.child-summary {
|
|
1360
|
+
flex-basis: 100%;
|
|
1361
|
+
order: 3;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
</style>
|
|
1365
|
+
</body>
|
|
1366
|
+
</html>
|