htmlgraph 0.26.5__py3-none-any.whl → 0.26.6__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 +1 -1
- htmlgraph/__init__.py +1 -1
- htmlgraph/api/main.py +50 -10
- htmlgraph/api/templates/dashboard-redesign.html +608 -54
- htmlgraph/api/templates/partials/activity-feed.html +21 -0
- htmlgraph/api/templates/partials/features.html +81 -12
- htmlgraph/api/templates/partials/orchestration.html +35 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +939 -0
- htmlgraph/cli/base.py +660 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +856 -0
- htmlgraph/cli/main.py +143 -0
- htmlgraph/cli/models.py +462 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +398 -0
- htmlgraph/cli/work/__init__.py +159 -0
- htmlgraph/cli/work/features.py +567 -0
- htmlgraph/cli/work/orchestration.py +675 -0
- htmlgraph/cli/work/sessions.py +465 -0
- htmlgraph/cli/work/tracks.py +485 -0
- htmlgraph/dashboard.html +6414 -634
- htmlgraph/db/schema.py +8 -3
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
- htmlgraph/docs/README.md +2 -3
- htmlgraph/hooks/event_tracker.py +157 -25
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/orchestrator.py +137 -71
- htmlgraph/hooks/orchestrator_reflector.py +23 -0
- htmlgraph/hooks/pretooluse.py +29 -6
- htmlgraph/hooks/session_handler.py +28 -0
- htmlgraph/hooks/session_summary.py +391 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/validator.py +192 -79
- htmlgraph/operations/__init__.py +18 -0
- htmlgraph/operations/initialization.py +596 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/orchestration/__init__.py +16 -1
- htmlgraph/orchestration/claude_launcher.py +185 -0
- htmlgraph/orchestration/command_builder.py +71 -0
- htmlgraph/orchestration/headless_spawner.py +72 -1332
- htmlgraph/orchestration/plugin_manager.py +136 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +170 -0
- htmlgraph/orchestration/spawners/codex.py +442 -0
- htmlgraph/orchestration/spawners/copilot.py +299 -0
- htmlgraph/orchestration/spawners/gemini.py +478 -0
- htmlgraph/orchestration/subprocess_runner.py +33 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +45 -12
- htmlgraph/transcript.py +16 -4
- htmlgraph-0.26.6.data/data/htmlgraph/dashboard.html +6592 -0
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/METADATA +1 -1
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/RECORD +67 -33
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -7256
- htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
- {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/WHEEL +0 -0
|
@@ -1,812 +0,0 @@
|
|
|
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="features"
|
|
57
|
-
hx-get="/views/features"
|
|
58
|
-
hx-target="#content-area"
|
|
59
|
-
hx-trigger="click">
|
|
60
|
-
<span class="tab-icon">█</span>
|
|
61
|
-
FEATURES
|
|
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
|
-
function insertNewEventIntoActivityFeed(eventData) {
|
|
202
|
-
const activityFeed = document.querySelector('.activity-feed-view');
|
|
203
|
-
if (!activityFeed) return;
|
|
204
|
-
|
|
205
|
-
const activityList = activityFeed.querySelector('.activity-list');
|
|
206
|
-
if (!activityList) return;
|
|
207
|
-
|
|
208
|
-
const emptyState = activityList.querySelector('.empty-state');
|
|
209
|
-
if (emptyState) {
|
|
210
|
-
emptyState.remove();
|
|
211
|
-
if (!activityList.querySelector('.activity-table')) {
|
|
212
|
-
// New column order: Agent | Tool | Input | Output | Status | Timestamp (no ID)
|
|
213
|
-
const table = `
|
|
214
|
-
<table class="activity-table">
|
|
215
|
-
<thead>
|
|
216
|
-
<tr>
|
|
217
|
-
<th class="col-agent">Agent</th>
|
|
218
|
-
<th class="col-tool">Tool</th>
|
|
219
|
-
<th class="col-input">Input</th>
|
|
220
|
-
<th class="col-output">Output</th>
|
|
221
|
-
<th class="col-status">Status</th>
|
|
222
|
-
<th class="col-timestamp">Timestamp</th>
|
|
223
|
-
</tr>
|
|
224
|
-
</thead>
|
|
225
|
-
<tbody></tbody>
|
|
226
|
-
</table>
|
|
227
|
-
`;
|
|
228
|
-
activityList.insertAdjacentHTML('afterbegin', table);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const table = activityList.querySelector('.activity-table');
|
|
233
|
-
if (!table) return;
|
|
234
|
-
|
|
235
|
-
const tbody = table.querySelector('tbody');
|
|
236
|
-
if (!tbody) return;
|
|
237
|
-
|
|
238
|
-
const eventRow = createActivityRowHTML(eventData);
|
|
239
|
-
|
|
240
|
-
if (eventData.parent_event_id) {
|
|
241
|
-
const parentRow = tbody.querySelector(`tr[data-event-id="${eventData.parent_event_id}"]`);
|
|
242
|
-
if (parentRow) {
|
|
243
|
-
parentRow.insertAdjacentHTML('afterend', eventRow);
|
|
244
|
-
highlightRow(tbody.querySelector(`tr[data-event-id="${eventData.event_id}"]`));
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const firstRow = tbody.querySelector('tr');
|
|
250
|
-
if (firstRow) {
|
|
251
|
-
firstRow.insertAdjacentHTML('beforebegin', eventRow);
|
|
252
|
-
} else {
|
|
253
|
-
tbody.insertAdjacentHTML('afterbegin', eventRow);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
highlightRow(tbody.querySelector('tr:first-child'));
|
|
257
|
-
|
|
258
|
-
if (typeof convertTimestampsToLocal === 'function') {
|
|
259
|
-
convertTimestampsToLocal();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const allRows = tbody.querySelectorAll('tr');
|
|
263
|
-
if (allRows.length > 100) {
|
|
264
|
-
const itemsToRemove = allRows.length - 100;
|
|
265
|
-
for (let i = 0; i < itemsToRemove; i++) {
|
|
266
|
-
allRows[allRows.length - 1 - i].remove();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function highlightRow(row) {
|
|
272
|
-
if (row) {
|
|
273
|
-
row.classList.add('new-event-highlight');
|
|
274
|
-
setTimeout(() => {
|
|
275
|
-
row.classList.remove('new-event-highlight');
|
|
276
|
-
}, 2000);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function createActivityRowHTML(eventData) {
|
|
281
|
-
// Event type emoji mapping
|
|
282
|
-
let eventEmoji = '📋'; // clipboard
|
|
283
|
-
if (eventData.event_type === 'delegation') eventEmoji = '🔗'; // link
|
|
284
|
-
else if (eventData.event_type === 'tool_call') eventEmoji = '🔨'; // hammer
|
|
285
|
-
else if (eventData.event_type === 'completion') eventEmoji = '🎉'; // party
|
|
286
|
-
else if (eventData.event_type === 'tool_result') eventEmoji = '✅'; // check
|
|
287
|
-
else if (eventData.event_type === 'error') eventEmoji = '❌'; // x
|
|
288
|
-
|
|
289
|
-
const inputSummary = eventData.input_summary ? eventData.input_summary.substring(0, 150) : '';
|
|
290
|
-
const inputTruncated = eventData.input_summary && eventData.input_summary.length > 150 ? '...' : '';
|
|
291
|
-
const outputSummary = eventData.output_summary ? eventData.output_summary.substring(0, 150) : '';
|
|
292
|
-
const outputTruncated = eventData.output_summary && eventData.output_summary.length > 150 ? '...' : '';
|
|
293
|
-
|
|
294
|
-
const isChild = !!eventData.parent_event_id;
|
|
295
|
-
const rowClass = isChild ? 'child-row hidden' : 'parent-row';
|
|
296
|
-
const borderStyle = isChild ? 'border-left: 4px solid var(--text-muted);' : 'border-left: 4px solid var(--accent-lime);';
|
|
297
|
-
|
|
298
|
-
// New column order: Agent | Tool | Input | Output | Status | Timestamp (no ID column)
|
|
299
|
-
const html = `
|
|
300
|
-
<tr class="activity-row ${rowClass} event-${eventData.status || 'pending'}"
|
|
301
|
-
data-event-id="${escapeHtml(eventData.event_id)}"
|
|
302
|
-
${isChild ? `data-parent="${escapeHtml(eventData.parent_event_id)}"` : ''}
|
|
303
|
-
style="${borderStyle}">
|
|
304
|
-
<td class="col-agent">
|
|
305
|
-
${isChild ? '<span class="child-indicator">↳</span>' : ''}
|
|
306
|
-
<span class="agent-badge agent-${escapeHtml(eventData.agent_id.toLowerCase())}">${escapeHtml(eventData.agent_id)}</span>
|
|
307
|
-
</td>
|
|
308
|
-
<td class="col-tool">
|
|
309
|
-
<span class="event-type-badge" title="${escapeHtml(eventData.event_type)}">
|
|
310
|
-
${eventEmoji}
|
|
311
|
-
</span>
|
|
312
|
-
${eventData.tool_name ? `<code class="tool-name">${escapeHtml(eventData.tool_name)}</code>` : '<span class="text-muted">-</span>'}
|
|
313
|
-
</td>
|
|
314
|
-
<td class="col-input">
|
|
315
|
-
${inputSummary ? `<span class="truncate" title="${escapeHtml(eventData.input_summary)}">${escapeHtml(inputSummary)}${inputTruncated}</span>` : '<span class="text-muted">-</span>'}
|
|
316
|
-
</td>
|
|
317
|
-
<td class="col-output">
|
|
318
|
-
${outputSummary ? `<span class="truncate" title="${escapeHtml(eventData.output_summary)}">${escapeHtml(outputSummary)}${outputTruncated}</span>` : '<span class="text-muted">-</span>'}
|
|
319
|
-
</td>
|
|
320
|
-
<td class="col-status">
|
|
321
|
-
<span class="status-badge status-${eventData.status || 'pending'}">${escapeHtml(eventData.status || 'pending')}</span>
|
|
322
|
-
</td>
|
|
323
|
-
<td class="col-timestamp">
|
|
324
|
-
<span class="timestamp-text" data-utc-time="${escapeHtml(eventData.timestamp)}">${escapeHtml(eventData.timestamp)}</span>
|
|
325
|
-
</td>
|
|
326
|
-
</tr>
|
|
327
|
-
`;
|
|
328
|
-
return html;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function escapeHtml(text) {
|
|
332
|
-
if (!text) return '';
|
|
333
|
-
const div = document.createElement('div');
|
|
334
|
-
div.textContent = text;
|
|
335
|
-
return div.innerHTML;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function convertTimestampsToLocal() {
|
|
339
|
-
const timestampElements = document.querySelectorAll('[data-utc-time]');
|
|
340
|
-
timestampElements.forEach(element => {
|
|
341
|
-
const utcTime = element.getAttribute('data-utc-time');
|
|
342
|
-
if (utcTime) {
|
|
343
|
-
try {
|
|
344
|
-
const date = new Date(utcTime.replace(' ', 'T') + 'Z');
|
|
345
|
-
const localTime = new Intl.DateTimeFormat('en-US', {
|
|
346
|
-
year: 'numeric',
|
|
347
|
-
month: '2-digit',
|
|
348
|
-
day: '2-digit',
|
|
349
|
-
hour: '2-digit',
|
|
350
|
-
minute: '2-digit',
|
|
351
|
-
second: '2-digit',
|
|
352
|
-
hour12: false,
|
|
353
|
-
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
354
|
-
}).format(date);
|
|
355
|
-
element.textContent = localTime;
|
|
356
|
-
element.setAttribute('title', `UTC: ${utcTime} | Local: ${localTime}`);
|
|
357
|
-
} catch (err) {
|
|
358
|
-
console.warn('Failed to convert timestamp:', utcTime, err);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Convert timestamps after HTMX loads content
|
|
365
|
-
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
|
366
|
-
if (evt.detail.target.id === 'content-area') {
|
|
367
|
-
if (typeof convertTimestampsToLocal === 'function') {
|
|
368
|
-
convertTimestampsToLocal();
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// Toggle child rows visibility (for expandable tracing)
|
|
374
|
-
function toggleChildren(parentRow) {
|
|
375
|
-
const eventId = parentRow.dataset.eventId;
|
|
376
|
-
const children = document.querySelectorAll(`[data-parent="${eventId}"]`);
|
|
377
|
-
const expandIcon = parentRow.querySelector('.expand-icon');
|
|
378
|
-
|
|
379
|
-
children.forEach(child => {
|
|
380
|
-
child.classList.toggle('hidden');
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
if (expandIcon) {
|
|
384
|
-
expandIcon.classList.toggle('expanded');
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Expand all parent rows
|
|
389
|
-
function expandAll() {
|
|
390
|
-
document.querySelectorAll('.parent-row.has-children').forEach(row => {
|
|
391
|
-
const eventId = row.dataset.eventId;
|
|
392
|
-
const children = document.querySelectorAll(`[data-parent="${eventId}"]`);
|
|
393
|
-
const expandIcon = row.querySelector('.expand-icon');
|
|
394
|
-
|
|
395
|
-
children.forEach(child => {
|
|
396
|
-
child.classList.remove('hidden');
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
if (expandIcon) {
|
|
400
|
-
expandIcon.classList.add('expanded');
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Collapse all parent rows
|
|
406
|
-
function collapseAll() {
|
|
407
|
-
document.querySelectorAll('.child-row').forEach(child => {
|
|
408
|
-
child.classList.add('hidden');
|
|
409
|
-
});
|
|
410
|
-
document.querySelectorAll('.expand-icon').forEach(icon => {
|
|
411
|
-
icon.classList.remove('expanded');
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ============================================
|
|
416
|
-
// Jaeger-Style Trace Interactivity Functions
|
|
417
|
-
// (Must be global for HTMX-loaded partials)
|
|
418
|
-
// ============================================
|
|
419
|
-
|
|
420
|
-
// Toggle expand/collapse with animation
|
|
421
|
-
function toggleTrace(id, event) {
|
|
422
|
-
if (event) event.stopPropagation();
|
|
423
|
-
|
|
424
|
-
const children = document.querySelectorAll(`[data-parent="${id}"]`);
|
|
425
|
-
const toggle = document.querySelector(`[data-id="${id}"] .expand-toggle`);
|
|
426
|
-
|
|
427
|
-
children.forEach(child => {
|
|
428
|
-
child.classList.toggle('collapsed');
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
if (toggle) {
|
|
432
|
-
toggle.classList.toggle('expanded');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Update breadcrumbs if drilling into a trace
|
|
436
|
-
updateBreadcrumbs(id);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Highlight ancestor path on hover (Jaeger pattern)
|
|
440
|
-
function highlightAncestors(row) {
|
|
441
|
-
clearAncestorHighlight();
|
|
442
|
-
|
|
443
|
-
let parentId = row.dataset.parent;
|
|
444
|
-
while (parentId) {
|
|
445
|
-
const parent = document.querySelector(`[data-id="${parentId}"]`);
|
|
446
|
-
if (parent) {
|
|
447
|
-
parent.classList.add('ancestor-highlight');
|
|
448
|
-
parentId = parent.dataset.parent;
|
|
449
|
-
} else {
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Clear all ancestor highlights
|
|
456
|
-
function clearAncestorHighlight() {
|
|
457
|
-
document.querySelectorAll('.ancestor-highlight').forEach(el => {
|
|
458
|
-
el.classList.remove('ancestor-highlight');
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Expand all traces
|
|
463
|
-
function expandAllTraces() {
|
|
464
|
-
document.querySelectorAll('.child-row').forEach(child => {
|
|
465
|
-
child.classList.remove('collapsed');
|
|
466
|
-
});
|
|
467
|
-
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
468
|
-
toggle.classList.add('expanded');
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Collapse all traces
|
|
473
|
-
function collapseAllTraces() {
|
|
474
|
-
document.querySelectorAll('.child-row').forEach(child => {
|
|
475
|
-
child.classList.add('collapsed');
|
|
476
|
-
});
|
|
477
|
-
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
478
|
-
toggle.classList.remove('expanded');
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Breadcrumb management
|
|
483
|
-
let breadcrumbStack = ['root'];
|
|
484
|
-
|
|
485
|
-
function updateBreadcrumbs(id) {
|
|
486
|
-
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
487
|
-
if (!breadcrumbsContainer) return;
|
|
488
|
-
|
|
489
|
-
const row = document.querySelector(`[data-id="${id}"]`);
|
|
490
|
-
if (!row) return;
|
|
491
|
-
|
|
492
|
-
// Only show breadcrumbs when we have nested navigation
|
|
493
|
-
const depth = parseInt(row.dataset.depth || '0');
|
|
494
|
-
if (depth > 0 || breadcrumbStack.length > 1) {
|
|
495
|
-
breadcrumbsContainer.style.display = 'flex';
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Get tool name or operation for breadcrumb label
|
|
499
|
-
const toolName = row.querySelector('.tool-name');
|
|
500
|
-
const label = toolName ? toolName.textContent : `Trace ${id.substring(0, 8)}`;
|
|
501
|
-
|
|
502
|
-
// Add to breadcrumb stack if not already present
|
|
503
|
-
if (!breadcrumbStack.includes(id)) {
|
|
504
|
-
breadcrumbStack.push(id);
|
|
505
|
-
|
|
506
|
-
const separator = document.createElement('span');
|
|
507
|
-
separator.className = 'separator';
|
|
508
|
-
separator.textContent = '>';
|
|
509
|
-
|
|
510
|
-
const crumb = document.createElement('span');
|
|
511
|
-
crumb.className = 'breadcrumb active';
|
|
512
|
-
crumb.dataset.id = id;
|
|
513
|
-
crumb.textContent = label;
|
|
514
|
-
crumb.onclick = () => navigateToBreadcrumb(id);
|
|
515
|
-
|
|
516
|
-
// Remove active class from previous breadcrumbs
|
|
517
|
-
breadcrumbsContainer.querySelectorAll('.breadcrumb').forEach(b => {
|
|
518
|
-
b.classList.remove('active');
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
breadcrumbsContainer.appendChild(separator);
|
|
522
|
-
breadcrumbsContainer.appendChild(crumb);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function navigateToBreadcrumb(id) {
|
|
527
|
-
// Find position in stack and remove everything after
|
|
528
|
-
const index = breadcrumbStack.indexOf(id);
|
|
529
|
-
if (index === -1) return;
|
|
530
|
-
|
|
531
|
-
// Remove breadcrumbs after this one
|
|
532
|
-
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
533
|
-
if (!breadcrumbsContainer) return;
|
|
534
|
-
|
|
535
|
-
const allCrumbs = breadcrumbsContainer.querySelectorAll('.breadcrumb, .separator');
|
|
536
|
-
|
|
537
|
-
let removing = false;
|
|
538
|
-
allCrumbs.forEach(el => {
|
|
539
|
-
if (removing) {
|
|
540
|
-
el.remove();
|
|
541
|
-
}
|
|
542
|
-
if (el.dataset && el.dataset.id === id) {
|
|
543
|
-
el.classList.add('active');
|
|
544
|
-
removing = true;
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
// Update stack
|
|
549
|
-
breadcrumbStack = breadcrumbStack.slice(0, index + 1);
|
|
550
|
-
|
|
551
|
-
// Hide breadcrumbs if back to root
|
|
552
|
-
if (breadcrumbStack.length <= 1) {
|
|
553
|
-
breadcrumbsContainer.style.display = 'none';
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function resetBreadcrumbs() {
|
|
558
|
-
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
559
|
-
if (!breadcrumbsContainer) return;
|
|
560
|
-
|
|
561
|
-
breadcrumbsContainer.innerHTML = '<span class="breadcrumb" data-id="root" onclick="resetBreadcrumbs()">Session</span>';
|
|
562
|
-
breadcrumbsContainer.style.display = 'none';
|
|
563
|
-
breadcrumbStack = ['root'];
|
|
564
|
-
|
|
565
|
-
// Collapse all traces when resetting
|
|
566
|
-
collapseAllTraces();
|
|
567
|
-
}
|
|
568
|
-
</script>
|
|
569
|
-
|
|
570
|
-
<style>
|
|
571
|
-
.new-event-highlight {
|
|
572
|
-
animation: highlightPulse 2s ease-out;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
@keyframes highlightPulse {
|
|
576
|
-
0% {
|
|
577
|
-
background-color: rgba(205, 255, 0, 0.1);
|
|
578
|
-
border-left-color: #CDFF00 !important;
|
|
579
|
-
}
|
|
580
|
-
100% {
|
|
581
|
-
background-color: transparent;
|
|
582
|
-
border-left-color: transparent !important;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/* Activity Feed Table Styles */
|
|
587
|
-
.activity-table {
|
|
588
|
-
width: 100%;
|
|
589
|
-
border-collapse: separate;
|
|
590
|
-
border-spacing: 0;
|
|
591
|
-
background: var(--bg-card);
|
|
592
|
-
border: 1px solid var(--border-subtle);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
.activity-table thead {
|
|
596
|
-
background: var(--bg-darker);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
.activity-table th {
|
|
600
|
-
padding: var(--spacing-lg);
|
|
601
|
-
text-align: left;
|
|
602
|
-
color: var(--accent-lime);
|
|
603
|
-
font-weight: 700;
|
|
604
|
-
font-size: 0.85rem;
|
|
605
|
-
text-transform: uppercase;
|
|
606
|
-
letter-spacing: 0.05em;
|
|
607
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
.activity-table td {
|
|
611
|
-
padding: var(--spacing-md) var(--spacing-lg);
|
|
612
|
-
border-bottom: 1px solid var(--border-subtle);
|
|
613
|
-
color: var(--text-primary);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
.activity-row:hover {
|
|
617
|
-
background: var(--bg-hover);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
.activity-row.parent-row {
|
|
621
|
-
font-weight: 500;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
.activity-row.child-row {
|
|
625
|
-
background: rgba(0, 0, 0, 0.2);
|
|
626
|
-
font-size: 0.95rem;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
.event-type-badge {
|
|
630
|
-
margin-right: var(--spacing-sm);
|
|
631
|
-
font-size: 1.1rem;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
.agent-badge {
|
|
635
|
-
display: inline-flex;
|
|
636
|
-
align-items: center;
|
|
637
|
-
padding: var(--spacing-xs) var(--spacing-sm);
|
|
638
|
-
background: rgba(205, 255, 0, 0.1);
|
|
639
|
-
border: 1px solid rgba(205, 255, 0, 0.3);
|
|
640
|
-
border-radius: 2px;
|
|
641
|
-
font-size: 0.8rem;
|
|
642
|
-
font-weight: 600;
|
|
643
|
-
text-transform: uppercase;
|
|
644
|
-
letter-spacing: 0.05em;
|
|
645
|
-
color: var(--accent-lime);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
.agent-badge.agent-claude {
|
|
649
|
-
background: rgba(139, 92, 246, 0.1);
|
|
650
|
-
border-color: rgba(139, 92, 246, 0.3);
|
|
651
|
-
color: var(--agent-claude);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
.agent-badge.agent-gemini {
|
|
655
|
-
background: rgba(59, 130, 246, 0.1);
|
|
656
|
-
border-color: rgba(59, 130, 246, 0.3);
|
|
657
|
-
color: var(--agent-gemini);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
.child-indicator {
|
|
661
|
-
margin-left: var(--spacing-md);
|
|
662
|
-
color: var(--text-muted);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
.tool-name {
|
|
666
|
-
color: var(--accent-lime);
|
|
667
|
-
font-size: 0.9rem;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
.status-badge {
|
|
671
|
-
display: inline-block;
|
|
672
|
-
padding: var(--spacing-xs) var(--spacing-sm);
|
|
673
|
-
border-radius: 2px;
|
|
674
|
-
font-size: 0.75rem;
|
|
675
|
-
font-weight: 600;
|
|
676
|
-
text-transform: uppercase;
|
|
677
|
-
letter-spacing: 0.05em;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
.status-badge.success {
|
|
681
|
-
background: rgba(16, 185, 129, 0.15);
|
|
682
|
-
color: var(--status-success);
|
|
683
|
-
border: 1px solid var(--status-success);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
.status-badge.progress {
|
|
687
|
-
background: rgba(59, 130, 246, 0.15);
|
|
688
|
-
color: var(--status-progress);
|
|
689
|
-
border: 1px solid var(--status-progress);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
.status-badge.blocked {
|
|
693
|
-
background: rgba(239, 68, 68, 0.15);
|
|
694
|
-
color: var(--status-blocked);
|
|
695
|
-
border: 1px solid var(--status-blocked);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
.status-badge.todo {
|
|
699
|
-
background: rgba(107, 114, 128, 0.15);
|
|
700
|
-
color: var(--status-todo);
|
|
701
|
-
border: 1px solid var(--status-todo);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
.status-badge.pending {
|
|
705
|
-
background: rgba(99, 102, 241, 0.15);
|
|
706
|
-
color: #6366F1;
|
|
707
|
-
border: 1px solid #6366F1;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
.status-badge.done {
|
|
711
|
-
background: rgba(139, 92, 246, 0.15);
|
|
712
|
-
color: var(--status-done);
|
|
713
|
-
border: 1px solid var(--status-done);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.event-id-code {
|
|
717
|
-
color: var(--accent-lime);
|
|
718
|
-
font-size: 0.8rem;
|
|
719
|
-
font-weight: 600;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
.text-muted {
|
|
723
|
-
color: var(--text-muted);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
.truncate {
|
|
727
|
-
overflow: hidden;
|
|
728
|
-
text-overflow: ellipsis;
|
|
729
|
-
white-space: nowrap;
|
|
730
|
-
display: inline-block;
|
|
731
|
-
max-width: 200px;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
.empty-state {
|
|
735
|
-
display: flex;
|
|
736
|
-
flex-direction: column;
|
|
737
|
-
align-items: center;
|
|
738
|
-
justify-content: center;
|
|
739
|
-
height: 300px;
|
|
740
|
-
color: var(--text-muted);
|
|
741
|
-
text-align: center;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
.empty-state p {
|
|
745
|
-
font-size: 1.1rem;
|
|
746
|
-
margin-bottom: var(--spacing-md);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
.empty-state small {
|
|
750
|
-
color: var(--text-secondary);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/* New column widths - Input/Output are flexible, others fixed */
|
|
754
|
-
.col-agent { width: 120px; }
|
|
755
|
-
.col-tool { width: 100px; }
|
|
756
|
-
.col-input { width: auto; }
|
|
757
|
-
.col-output { width: auto; }
|
|
758
|
-
.col-status { width: 100px; }
|
|
759
|
-
.col-timestamp { width: 140px; }
|
|
760
|
-
|
|
761
|
-
/* Make table use fixed layout for predictable column widths */
|
|
762
|
-
.activity-table {
|
|
763
|
-
table-layout: fixed;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/* Expandable tracing structure */
|
|
767
|
-
.expand-icon {
|
|
768
|
-
cursor: pointer;
|
|
769
|
-
display: inline-block;
|
|
770
|
-
margin-right: 0.5rem;
|
|
771
|
-
transition: transform 0.2s ease;
|
|
772
|
-
font-size: 0.7rem;
|
|
773
|
-
color: var(--text-muted);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
.expand-icon.expanded {
|
|
777
|
-
transform: rotate(90deg);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
.parent-row.has-children {
|
|
781
|
-
cursor: pointer;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
.child-count {
|
|
785
|
-
font-size: 0.75rem;
|
|
786
|
-
color: var(--text-muted);
|
|
787
|
-
margin-left: 0.5rem;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
.child-row.hidden {
|
|
791
|
-
display: none;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
@media (max-width: 1024px) {
|
|
795
|
-
.col-agent { width: 100px; }
|
|
796
|
-
.col-tool { width: 90px; }
|
|
797
|
-
.col-status { width: 90px; }
|
|
798
|
-
.col-timestamp { width: 120px; }
|
|
799
|
-
|
|
800
|
-
.activity-table th,
|
|
801
|
-
.activity-table td {
|
|
802
|
-
padding: var(--spacing-md) var(--spacing-sm);
|
|
803
|
-
font-size: 0.9rem;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
.truncate {
|
|
807
|
-
max-width: 100%;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
</style>
|
|
811
|
-
</body>
|
|
812
|
-
</html>
|