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.
Files changed (69) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +1 -1
  2. htmlgraph/__init__.py +1 -1
  3. htmlgraph/api/main.py +50 -10
  4. htmlgraph/api/templates/dashboard-redesign.html +608 -54
  5. htmlgraph/api/templates/partials/activity-feed.html +21 -0
  6. htmlgraph/api/templates/partials/features.html +81 -12
  7. htmlgraph/api/templates/partials/orchestration.html +35 -0
  8. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  9. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  10. htmlgraph/cli/__init__.py +42 -0
  11. htmlgraph/cli/__main__.py +6 -0
  12. htmlgraph/cli/analytics.py +939 -0
  13. htmlgraph/cli/base.py +660 -0
  14. htmlgraph/cli/constants.py +206 -0
  15. htmlgraph/cli/core.py +856 -0
  16. htmlgraph/cli/main.py +143 -0
  17. htmlgraph/cli/models.py +462 -0
  18. htmlgraph/cli/templates/__init__.py +1 -0
  19. htmlgraph/cli/templates/cost_dashboard.py +398 -0
  20. htmlgraph/cli/work/__init__.py +159 -0
  21. htmlgraph/cli/work/features.py +567 -0
  22. htmlgraph/cli/work/orchestration.py +675 -0
  23. htmlgraph/cli/work/sessions.py +465 -0
  24. htmlgraph/cli/work/tracks.py +485 -0
  25. htmlgraph/dashboard.html +6414 -634
  26. htmlgraph/db/schema.py +8 -3
  27. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
  28. htmlgraph/docs/README.md +2 -3
  29. htmlgraph/hooks/event_tracker.py +157 -25
  30. htmlgraph/hooks/git_commands.py +175 -0
  31. htmlgraph/hooks/orchestrator.py +137 -71
  32. htmlgraph/hooks/orchestrator_reflector.py +23 -0
  33. htmlgraph/hooks/pretooluse.py +29 -6
  34. htmlgraph/hooks/session_handler.py +28 -0
  35. htmlgraph/hooks/session_summary.py +391 -0
  36. htmlgraph/hooks/subagent_detection.py +202 -0
  37. htmlgraph/hooks/validator.py +192 -79
  38. htmlgraph/operations/__init__.py +18 -0
  39. htmlgraph/operations/initialization.py +596 -0
  40. htmlgraph/operations/initialization.py.backup +228 -0
  41. htmlgraph/orchestration/__init__.py +16 -1
  42. htmlgraph/orchestration/claude_launcher.py +185 -0
  43. htmlgraph/orchestration/command_builder.py +71 -0
  44. htmlgraph/orchestration/headless_spawner.py +72 -1332
  45. htmlgraph/orchestration/plugin_manager.py +136 -0
  46. htmlgraph/orchestration/prompts.py +137 -0
  47. htmlgraph/orchestration/spawners/__init__.py +16 -0
  48. htmlgraph/orchestration/spawners/base.py +194 -0
  49. htmlgraph/orchestration/spawners/claude.py +170 -0
  50. htmlgraph/orchestration/spawners/codex.py +442 -0
  51. htmlgraph/orchestration/spawners/copilot.py +299 -0
  52. htmlgraph/orchestration/spawners/gemini.py +478 -0
  53. htmlgraph/orchestration/subprocess_runner.py +33 -0
  54. htmlgraph/orchestration.md +563 -0
  55. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  56. htmlgraph/orchestrator_config.py +357 -0
  57. htmlgraph/orchestrator_mode.py +45 -12
  58. htmlgraph/transcript.py +16 -4
  59. htmlgraph-0.26.6.data/data/htmlgraph/dashboard.html +6592 -0
  60. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/METADATA +1 -1
  61. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/RECORD +67 -33
  62. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.6.dist-info}/entry_points.txt +1 -1
  63. htmlgraph/cli.py +0 -7256
  64. htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
  65. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/styles.css +0 -0
  66. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  67. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  68. {htmlgraph-0.26.5.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  69. {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 = '&#128203;'; // clipboard
283
- if (eventData.event_type === 'delegation') eventEmoji = '&#128279;'; // link
284
- else if (eventData.event_type === 'tool_call') eventEmoji = '&#128296;'; // hammer
285
- else if (eventData.event_type === 'completion') eventEmoji = '&#127881;'; // party
286
- else if (eventData.event_type === 'tool_result') eventEmoji = '&#9989;'; // check
287
- else if (eventData.event_type === 'error') eventEmoji = '&#10060;'; // 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">&#8627;</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>