agentflow-dashboard 0.4.1 → 0.5.0

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.
@@ -1667,6 +1667,75 @@ var DashboardServer = class {
1667
1667
  res.status(500).json({ error: "Failed to load statistics" });
1668
1668
  }
1669
1669
  });
1670
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1671
+ try {
1672
+ const agentId = req.params.agentId;
1673
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1674
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1675
+ if (rawTraces.length === 0) {
1676
+ return res.status(404).json({ error: "No traces for agent" });
1677
+ }
1678
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1679
+ const executions = traces.map((t) => {
1680
+ const serialized = serializeTrace(t);
1681
+ const nodes = serialized.nodes || {};
1682
+ const events = serialized.sessionEvents || [];
1683
+ const activities = [];
1684
+ if (events.length > 0) {
1685
+ for (let i = 0; i < events.length; i++) {
1686
+ const evt = events[i];
1687
+ if (evt.type === "system" || evt.type === "model_change") continue;
1688
+ const dur = evt.duration || 0;
1689
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1690
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1691
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1692
+ activities.push({
1693
+ id: evt.id || `evt-${i}`,
1694
+ name: evt.toolName || evt.name || evt.type,
1695
+ type: evt.type,
1696
+ status: evt.toolError ? "failed" : "completed",
1697
+ startTime: startTs,
1698
+ endTime: endTs,
1699
+ parentId: evt.parentId
1700
+ });
1701
+ }
1702
+ } else {
1703
+ const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1704
+ for (const node of sorted) {
1705
+ activities.push({
1706
+ id: node.id,
1707
+ name: node.name || node.type || node.id,
1708
+ type: node.type || "unknown",
1709
+ status: node.status || "completed",
1710
+ startTime: node.startTime || t.startTime,
1711
+ endTime: node.endTime || node.startTime || t.startTime,
1712
+ parentId: node.parentId
1713
+ });
1714
+ }
1715
+ }
1716
+ return {
1717
+ id: serialized.id || serialized.filename,
1718
+ filename: serialized.filename,
1719
+ name: serialized.name || serialized.filename,
1720
+ agentId: serialized.agentId,
1721
+ trigger: serialized.trigger,
1722
+ status: serialized.status || "completed",
1723
+ sourceType: serialized.sourceType,
1724
+ startTime: serialized.startTime,
1725
+ endTime: serialized.endTime || serialized.startTime,
1726
+ tokenUsage: serialized.tokenUsage,
1727
+ activities
1728
+ };
1729
+ });
1730
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1731
+ const minTime = Math.min(...allTimes);
1732
+ const maxTime = Math.max(...allTimes);
1733
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1734
+ } catch (error) {
1735
+ console.error("Agent timeline error:", error);
1736
+ res.status(500).json({ error: "Failed to build agent timeline" });
1737
+ }
1738
+ });
1670
1739
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1671
1740
  try {
1672
1741
  const agentId = req.params.agentId;
package/dist/cli.cjs CHANGED
@@ -1561,6 +1561,75 @@ var DashboardServer = class {
1561
1561
  res.status(500).json({ error: "Failed to load statistics" });
1562
1562
  }
1563
1563
  });
1564
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1565
+ try {
1566
+ const agentId = req.params.agentId;
1567
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1568
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1569
+ if (rawTraces.length === 0) {
1570
+ return res.status(404).json({ error: "No traces for agent" });
1571
+ }
1572
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1573
+ const executions = traces.map((t) => {
1574
+ const serialized = serializeTrace(t);
1575
+ const nodes = serialized.nodes || {};
1576
+ const events = serialized.sessionEvents || [];
1577
+ const activities = [];
1578
+ if (events.length > 0) {
1579
+ for (let i = 0; i < events.length; i++) {
1580
+ const evt = events[i];
1581
+ if (evt.type === "system" || evt.type === "model_change") continue;
1582
+ const dur = evt.duration || 0;
1583
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1584
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1585
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1586
+ activities.push({
1587
+ id: evt.id || `evt-${i}`,
1588
+ name: evt.toolName || evt.name || evt.type,
1589
+ type: evt.type,
1590
+ status: evt.toolError ? "failed" : "completed",
1591
+ startTime: startTs,
1592
+ endTime: endTs,
1593
+ parentId: evt.parentId
1594
+ });
1595
+ }
1596
+ } else {
1597
+ const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1598
+ for (const node of sorted) {
1599
+ activities.push({
1600
+ id: node.id,
1601
+ name: node.name || node.type || node.id,
1602
+ type: node.type || "unknown",
1603
+ status: node.status || "completed",
1604
+ startTime: node.startTime || t.startTime,
1605
+ endTime: node.endTime || node.startTime || t.startTime,
1606
+ parentId: node.parentId
1607
+ });
1608
+ }
1609
+ }
1610
+ return {
1611
+ id: serialized.id || serialized.filename,
1612
+ filename: serialized.filename,
1613
+ name: serialized.name || serialized.filename,
1614
+ agentId: serialized.agentId,
1615
+ trigger: serialized.trigger,
1616
+ status: serialized.status || "completed",
1617
+ sourceType: serialized.sourceType,
1618
+ startTime: serialized.startTime,
1619
+ endTime: serialized.endTime || serialized.startTime,
1620
+ tokenUsage: serialized.tokenUsage,
1621
+ activities
1622
+ };
1623
+ });
1624
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1625
+ const minTime = Math.min(...allTimes);
1626
+ const maxTime = Math.max(...allTimes);
1627
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1628
+ } catch (error) {
1629
+ console.error("Agent timeline error:", error);
1630
+ res.status(500).json({ error: "Failed to build agent timeline" });
1631
+ }
1632
+ });
1564
1633
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1565
1634
  try {
1566
1635
  const agentId = req.params.agentId;
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startDashboard
3
- } from "./chunk-YDFLDRWO.js";
3
+ } from "./chunk-RWNVLZU7.js";
4
4
  export {
5
5
  startDashboard
6
6
  };
package/dist/index.cjs CHANGED
@@ -1706,6 +1706,75 @@ var DashboardServer = class {
1706
1706
  res.status(500).json({ error: "Failed to load statistics" });
1707
1707
  }
1708
1708
  });
1709
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1710
+ try {
1711
+ const agentId = req.params.agentId;
1712
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1713
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1714
+ if (rawTraces.length === 0) {
1715
+ return res.status(404).json({ error: "No traces for agent" });
1716
+ }
1717
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1718
+ const executions = traces.map((t) => {
1719
+ const serialized = serializeTrace(t);
1720
+ const nodes = serialized.nodes || {};
1721
+ const events = serialized.sessionEvents || [];
1722
+ const activities = [];
1723
+ if (events.length > 0) {
1724
+ for (let i = 0; i < events.length; i++) {
1725
+ const evt = events[i];
1726
+ if (evt.type === "system" || evt.type === "model_change") continue;
1727
+ const dur = evt.duration || 0;
1728
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1729
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1730
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1731
+ activities.push({
1732
+ id: evt.id || `evt-${i}`,
1733
+ name: evt.toolName || evt.name || evt.type,
1734
+ type: evt.type,
1735
+ status: evt.toolError ? "failed" : "completed",
1736
+ startTime: startTs,
1737
+ endTime: endTs,
1738
+ parentId: evt.parentId
1739
+ });
1740
+ }
1741
+ } else {
1742
+ const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1743
+ for (const node of sorted) {
1744
+ activities.push({
1745
+ id: node.id,
1746
+ name: node.name || node.type || node.id,
1747
+ type: node.type || "unknown",
1748
+ status: node.status || "completed",
1749
+ startTime: node.startTime || t.startTime,
1750
+ endTime: node.endTime || node.startTime || t.startTime,
1751
+ parentId: node.parentId
1752
+ });
1753
+ }
1754
+ }
1755
+ return {
1756
+ id: serialized.id || serialized.filename,
1757
+ filename: serialized.filename,
1758
+ name: serialized.name || serialized.filename,
1759
+ agentId: serialized.agentId,
1760
+ trigger: serialized.trigger,
1761
+ status: serialized.status || "completed",
1762
+ sourceType: serialized.sourceType,
1763
+ startTime: serialized.startTime,
1764
+ endTime: serialized.endTime || serialized.startTime,
1765
+ tokenUsage: serialized.tokenUsage,
1766
+ activities
1767
+ };
1768
+ });
1769
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1770
+ const minTime = Math.min(...allTimes);
1771
+ const maxTime = Math.max(...allTimes);
1772
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1773
+ } catch (error) {
1774
+ console.error("Agent timeline error:", error);
1775
+ res.status(500).json({ error: "Failed to build agent timeline" });
1776
+ }
1777
+ });
1709
1778
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1710
1779
  try {
1711
1780
  const agentId = req.params.agentId;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  AgentStats,
4
4
  DashboardServer,
5
5
  TraceWatcher
6
- } from "./chunk-YDFLDRWO.js";
6
+ } from "./chunk-RWNVLZU7.js";
7
7
  export {
8
8
  AgentStats,
9
9
  DashboardServer,
@@ -307,11 +307,15 @@ class AgentFlowDashboard {
307
307
  this.selectedTrace = trace;
308
308
  this.selectedTraceData = trace;
309
309
 
310
- // Reset process map cache when agent changes
310
+ // Reset agent-level caches when agent changes
311
311
  if (this._processMapAgent !== trace.agentId) {
312
312
  this._processMapAgent = null;
313
313
  if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
314
314
  }
315
+ if (this._agentTimelineAgent !== trace.agentId) {
316
+ this._agentTimelineAgent = null;
317
+ this._agentTimelineRendered = false;
318
+ }
315
319
 
316
320
  // Update sidebar selection
317
321
  document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
@@ -856,6 +860,7 @@ class AgentFlowDashboard {
856
860
  case 'state': this.renderStateMachine(); break;
857
861
  case 'summary': this.renderSummary(); break;
858
862
  case 'transcript': this.renderTranscript(); break;
863
+ case 'agenttimeline': this.renderAgentTimeline(); break;
859
864
  case 'processmap': this.renderProcessMap(); break;
860
865
  }
861
866
  this.updateToolbarInfo();
@@ -1915,7 +1920,164 @@ class AgentFlowDashboard {
1915
1920
  }
1916
1921
 
1917
1922
  // ---------------------------------------------------------------------------
1918
- // Tab 8: Process Map (Process Mining Graph)
1923
+ // Tab 8: Agent Timeline (Gantt Chart)
1924
+ // ---------------------------------------------------------------------------
1925
+ renderAgentTimeline() {
1926
+ var trace = this.selectedTraceData || this.selectedTrace;
1927
+ if (!trace || !trace.agentId) {
1928
+ document.getElementById('agentTimelineEmpty').style.display = '';
1929
+ return;
1930
+ }
1931
+
1932
+ var agentId = trace.agentId;
1933
+ var self = this;
1934
+
1935
+ if (this._agentTimelineAgent === agentId && this._agentTimelineRendered) return;
1936
+ this._agentTimelineAgent = agentId;
1937
+
1938
+ var container = document.getElementById('agentTimelineContent');
1939
+ container.innerHTML =
1940
+ '<div class="empty-state"><div class="empty-state-icon" style="animation:spin 1s linear infinite">&#9881;</div>' +
1941
+ '<div class="empty-state-text">Loading timeline for ' + escapeHtml(agentId) + '...</div></div>';
1942
+
1943
+ fetch('/api/agents/' + encodeURIComponent(agentId) + '/timeline?limit=50')
1944
+ .then(function(r) { return r.json(); })
1945
+ .then(function(data) {
1946
+ if (data.error || !data.executions || data.executions.length === 0) {
1947
+ container.innerHTML =
1948
+ '<div class="empty-state"><div class="empty-state-text">No timeline data for ' + escapeHtml(agentId) + '</div></div>';
1949
+ return;
1950
+ }
1951
+ self._agentTimelineRendered = true;
1952
+ self._renderGantt(container, data);
1953
+ })
1954
+ .catch(function() {
1955
+ container.innerHTML =
1956
+ '<div class="empty-state"><div class="empty-state-text">Failed to load agent timeline.</div></div>';
1957
+ });
1958
+ }
1959
+
1960
+ _renderGantt(container, data) {
1961
+ var execs = data.executions;
1962
+ var minTime = data.minTime;
1963
+ var maxTime = data.maxTime;
1964
+ var timeSpan = maxTime - minTime || 1;
1965
+ var self = this;
1966
+
1967
+ // Layout constants
1968
+ var labelW = 220;
1969
+ var chartW = 900;
1970
+ var rowH = 28;
1971
+ var subRowH = 20;
1972
+ var headerH = 36;
1973
+ var totalW = labelW + chartW + 20;
1974
+
1975
+ // Build HTML
1976
+ var html = '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' + totalW + 'px;">';
1977
+
1978
+ // Header with time axis
1979
+ html += '<div class="gantt-header" style="display:flex;height:' + headerH + 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
1980
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
1981
+ html += '<div style="flex:1;position:relative;">';
1982
+ // Time ticks
1983
+ var tickCount = 6;
1984
+ for (var t = 0; t <= tickCount; t++) {
1985
+ var pct = (t / tickCount) * 100;
1986
+ var tickTime = minTime + (t / tickCount) * timeSpan;
1987
+ var d = new Date(tickTime);
1988
+ var label = d.getMonth() + 1 + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
1989
+ html += '<div style="position:absolute;left:' + pct + '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' + label + '</div>';
1990
+ }
1991
+ html += '</div></div>';
1992
+
1993
+ // Rows
1994
+ html += '<div class="gantt-body">';
1995
+ for (var i = 0; i < execs.length; i++) {
1996
+ var exec = execs[i];
1997
+ var execStart = ((exec.startTime - minTime) / timeSpan) * 100;
1998
+ var execWidth = Math.max(0.3, ((exec.endTime - exec.startTime) / timeSpan) * 100);
1999
+ var statusColor = exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
2000
+ var hasActivities = exec.activities && exec.activities.length > 0;
2001
+ var execId = 'gantt-exec-' + i;
2002
+
2003
+ // Main execution row
2004
+ html += '<div class="gantt-row" style="display:flex;height:' + rowH + 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
2005
+ 'onclick="(function(){var el=document.getElementById(\'' + execId + '\');if(el)el.style.display=el.style.display===\'none\'?\'block\':\'none\';})()" ' +
2006
+ 'title="Click to ' + (hasActivities ? 'expand' : 'view') + '">';
2007
+
2008
+ // Label
2009
+ var execName = exec.name || exec.filename || exec.id;
2010
+ if (execName.length > 28) execName = execName.slice(0, 28) + '...';
2011
+ var dur = this.computeDuration(exec.startTime, exec.endTime);
2012
+ var triggerBadge = exec.trigger ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' + escapeHtml(exec.trigger) + '</span>' : '';
2013
+ var expandIcon = hasActivities ? '<span style="color:#6b7280;margin-right:4px;">&#9654;</span>' : '<span style="width:14px;display:inline-block;"></span>';
2014
+
2015
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' + (rowH - 8) + 'px;">' +
2016
+ expandIcon + escapeHtml(execName) + triggerBadge + '</div>';
2017
+
2018
+ // Bar
2019
+ html += '<div style="flex:1;position:relative;padding:4px 0;">';
2020
+ html += '<div style="position:absolute;left:' + execStart + '%;width:' + execWidth + '%;top:4px;height:' + (rowH - 12) + 'px;' +
2021
+ 'background:' + statusColor + ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
2022
+ 'title="' + escapeHtml(exec.name || '') + ' | ' + dur + ' | ' + escapeHtml(exec.status) + '"></div>';
2023
+ html += '</div></div>';
2024
+
2025
+ // Sub-activities (collapsed by default)
2026
+ if (hasActivities) {
2027
+ html += '<div id="' + execId + '" style="display:none;background:#0a0e14;">';
2028
+ // Filter to top-level activities (no parentId or parentId is root)
2029
+ var rootIds = new Set();
2030
+ if (exec.activities.length > 0) {
2031
+ var firstAct = exec.activities[0];
2032
+ rootIds.add(firstAct.id);
2033
+ }
2034
+
2035
+ for (var j = 0; j < exec.activities.length; j++) {
2036
+ var act = exec.activities[j];
2037
+ var actStart = ((Math.max(act.startTime, exec.startTime) - minTime) / timeSpan) * 100;
2038
+ var actEnd = act.endTime || act.startTime;
2039
+ var actWidth = Math.max(0.2, ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100);
2040
+ var actColor = act.status === 'failed' ? '#f87171' :
2041
+ act.type === 'user' ? '#60a5fa' :
2042
+ act.type === 'assistant' ? '#34d399' :
2043
+ act.type === 'thinking' ? '#a78bfa' :
2044
+ act.type === 'tool_call' ? '#fb923c' :
2045
+ act.type === 'tool_result' ? '#4ade80' :
2046
+ act.type === 'agent' ? '#38bdf8' :
2047
+ '#6b7280';
2048
+ var actName = act.name || act.type;
2049
+ if (actName.length > 30) actName = actName.slice(0, 30) + '...';
2050
+ var isChild = act.parentId && !rootIds.has(act.id);
2051
+
2052
+ html += '<div style="display:flex;height:' + subRowH + 'px;border-bottom:1px solid #0d1117;">';
2053
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:2px 10px 2px ' + (isChild ? '30' : '20') + 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' + (subRowH - 4) + 'px;">' +
2054
+ '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + actColor + ';margin-right:6px;vertical-align:middle;"></span>' +
2055
+ escapeHtml(actName) + '</div>';
2056
+ html += '<div style="flex:1;position:relative;">';
2057
+ html += '<div style="position:absolute;left:' + actStart + '%;width:' + actWidth + '%;top:3px;height:' + (subRowH - 8) + 'px;' +
2058
+ 'background:' + actColor + ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
2059
+ 'title="' + escapeHtml(act.name || act.type) + ' | ' + escapeHtml(act.status) + '"></div>';
2060
+ html += '</div></div>';
2061
+ }
2062
+ html += '</div>';
2063
+ }
2064
+ }
2065
+
2066
+ html += '</div>';
2067
+
2068
+ // Summary bar
2069
+ html += '<div style="padding:10px;border-top:1px solid #30363d;color:#8b949e;font-size:10px;">';
2070
+ html += escapeHtml(data.agentId) + ' &mdash; ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
2071
+ var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
2072
+ html += ' &mdash; ' + timeRange;
2073
+ html += '</div>';
2074
+
2075
+ html += '</div>';
2076
+ container.innerHTML = html;
2077
+ }
2078
+
2079
+ // ---------------------------------------------------------------------------
2080
+ // Tab 9: Process Map (Process Mining Graph)
1919
2081
  // ---------------------------------------------------------------------------
1920
2082
  renderProcessMap() {
1921
2083
  var trace = this.selectedTraceData || this.selectedTrace;
@@ -1235,6 +1235,7 @@
1235
1235
  <div class="tab" data-tab="state">State Machine</div>
1236
1236
  <div class="tab" data-tab="summary">Summary</div>
1237
1237
  <div class="tab" data-tab="transcript">Transcript</div>
1238
+ <div class="tab" data-tab="agenttimeline">Agent Timeline</div>
1238
1239
  <div class="tab" data-tab="processmap">Process Map</div>
1239
1240
  </div>
1240
1241
 
@@ -1334,6 +1335,17 @@
1334
1335
  </div>
1335
1336
  </div>
1336
1337
 
1338
+ <!-- Agent Timeline (Gantt) tab -->
1339
+ <div class="tab-panel" id="panel-agenttimeline">
1340
+ <div class="timeline-container" id="agentTimelineContent" style="overflow:auto;">
1341
+ <div class="empty-state" id="agentTimelineEmpty">
1342
+ <div class="empty-state-icon">&#9776;</div>
1343
+ <div class="empty-state-title">Agent Timeline</div>
1344
+ <div class="empty-state-text">Select a trace to view all executions for its agent as a Gantt chart.</div>
1345
+ </div>
1346
+ </div>
1347
+ </div>
1348
+
1337
1349
  <!-- Process Map tab -->
1338
1350
  <div class="tab-panel" id="panel-processmap">
1339
1351
  <div id="cyProcessMap" style="width:100%;height:100%;min-height:500px;">
package/dist/server.cjs CHANGED
@@ -1701,6 +1701,75 @@ var DashboardServer = class {
1701
1701
  res.status(500).json({ error: "Failed to load statistics" });
1702
1702
  }
1703
1703
  });
1704
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1705
+ try {
1706
+ const agentId = req.params.agentId;
1707
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1708
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1709
+ if (rawTraces.length === 0) {
1710
+ return res.status(404).json({ error: "No traces for agent" });
1711
+ }
1712
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1713
+ const executions = traces.map((t) => {
1714
+ const serialized = serializeTrace(t);
1715
+ const nodes = serialized.nodes || {};
1716
+ const events = serialized.sessionEvents || [];
1717
+ const activities = [];
1718
+ if (events.length > 0) {
1719
+ for (let i = 0; i < events.length; i++) {
1720
+ const evt = events[i];
1721
+ if (evt.type === "system" || evt.type === "model_change") continue;
1722
+ const dur = evt.duration || 0;
1723
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1724
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1725
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1726
+ activities.push({
1727
+ id: evt.id || `evt-${i}`,
1728
+ name: evt.toolName || evt.name || evt.type,
1729
+ type: evt.type,
1730
+ status: evt.toolError ? "failed" : "completed",
1731
+ startTime: startTs,
1732
+ endTime: endTs,
1733
+ parentId: evt.parentId
1734
+ });
1735
+ }
1736
+ } else {
1737
+ const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1738
+ for (const node of sorted) {
1739
+ activities.push({
1740
+ id: node.id,
1741
+ name: node.name || node.type || node.id,
1742
+ type: node.type || "unknown",
1743
+ status: node.status || "completed",
1744
+ startTime: node.startTime || t.startTime,
1745
+ endTime: node.endTime || node.startTime || t.startTime,
1746
+ parentId: node.parentId
1747
+ });
1748
+ }
1749
+ }
1750
+ return {
1751
+ id: serialized.id || serialized.filename,
1752
+ filename: serialized.filename,
1753
+ name: serialized.name || serialized.filename,
1754
+ agentId: serialized.agentId,
1755
+ trigger: serialized.trigger,
1756
+ status: serialized.status || "completed",
1757
+ sourceType: serialized.sourceType,
1758
+ startTime: serialized.startTime,
1759
+ endTime: serialized.endTime || serialized.startTime,
1760
+ tokenUsage: serialized.tokenUsage,
1761
+ activities
1762
+ };
1763
+ });
1764
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1765
+ const minTime = Math.min(...allTimes);
1766
+ const maxTime = Math.max(...allTimes);
1767
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1768
+ } catch (error) {
1769
+ console.error("Agent timeline error:", error);
1770
+ res.status(500).json({ error: "Failed to build agent timeline" });
1771
+ }
1772
+ });
1704
1773
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1705
1774
  try {
1706
1775
  const agentId = req.params.agentId;
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DashboardServer
3
- } from "./chunk-YDFLDRWO.js";
3
+ } from "./chunk-RWNVLZU7.js";
4
4
  export {
5
5
  DashboardServer
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-dashboard",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Real-time monitoring dashboard for AgentFlow - Visualize agent execution graphs and performance",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -307,11 +307,15 @@ class AgentFlowDashboard {
307
307
  this.selectedTrace = trace;
308
308
  this.selectedTraceData = trace;
309
309
 
310
- // Reset process map cache when agent changes
310
+ // Reset agent-level caches when agent changes
311
311
  if (this._processMapAgent !== trace.agentId) {
312
312
  this._processMapAgent = null;
313
313
  if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
314
314
  }
315
+ if (this._agentTimelineAgent !== trace.agentId) {
316
+ this._agentTimelineAgent = null;
317
+ this._agentTimelineRendered = false;
318
+ }
315
319
 
316
320
  // Update sidebar selection
317
321
  document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
@@ -856,6 +860,7 @@ class AgentFlowDashboard {
856
860
  case 'state': this.renderStateMachine(); break;
857
861
  case 'summary': this.renderSummary(); break;
858
862
  case 'transcript': this.renderTranscript(); break;
863
+ case 'agenttimeline': this.renderAgentTimeline(); break;
859
864
  case 'processmap': this.renderProcessMap(); break;
860
865
  }
861
866
  this.updateToolbarInfo();
@@ -1915,7 +1920,164 @@ class AgentFlowDashboard {
1915
1920
  }
1916
1921
 
1917
1922
  // ---------------------------------------------------------------------------
1918
- // Tab 8: Process Map (Process Mining Graph)
1923
+ // Tab 8: Agent Timeline (Gantt Chart)
1924
+ // ---------------------------------------------------------------------------
1925
+ renderAgentTimeline() {
1926
+ var trace = this.selectedTraceData || this.selectedTrace;
1927
+ if (!trace || !trace.agentId) {
1928
+ document.getElementById('agentTimelineEmpty').style.display = '';
1929
+ return;
1930
+ }
1931
+
1932
+ var agentId = trace.agentId;
1933
+ var self = this;
1934
+
1935
+ if (this._agentTimelineAgent === agentId && this._agentTimelineRendered) return;
1936
+ this._agentTimelineAgent = agentId;
1937
+
1938
+ var container = document.getElementById('agentTimelineContent');
1939
+ container.innerHTML =
1940
+ '<div class="empty-state"><div class="empty-state-icon" style="animation:spin 1s linear infinite">&#9881;</div>' +
1941
+ '<div class="empty-state-text">Loading timeline for ' + escapeHtml(agentId) + '...</div></div>';
1942
+
1943
+ fetch('/api/agents/' + encodeURIComponent(agentId) + '/timeline?limit=50')
1944
+ .then(function(r) { return r.json(); })
1945
+ .then(function(data) {
1946
+ if (data.error || !data.executions || data.executions.length === 0) {
1947
+ container.innerHTML =
1948
+ '<div class="empty-state"><div class="empty-state-text">No timeline data for ' + escapeHtml(agentId) + '</div></div>';
1949
+ return;
1950
+ }
1951
+ self._agentTimelineRendered = true;
1952
+ self._renderGantt(container, data);
1953
+ })
1954
+ .catch(function() {
1955
+ container.innerHTML =
1956
+ '<div class="empty-state"><div class="empty-state-text">Failed to load agent timeline.</div></div>';
1957
+ });
1958
+ }
1959
+
1960
+ _renderGantt(container, data) {
1961
+ var execs = data.executions;
1962
+ var minTime = data.minTime;
1963
+ var maxTime = data.maxTime;
1964
+ var timeSpan = maxTime - minTime || 1;
1965
+ var self = this;
1966
+
1967
+ // Layout constants
1968
+ var labelW = 220;
1969
+ var chartW = 900;
1970
+ var rowH = 28;
1971
+ var subRowH = 20;
1972
+ var headerH = 36;
1973
+ var totalW = labelW + chartW + 20;
1974
+
1975
+ // Build HTML
1976
+ var html = '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' + totalW + 'px;">';
1977
+
1978
+ // Header with time axis
1979
+ html += '<div class="gantt-header" style="display:flex;height:' + headerH + 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
1980
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
1981
+ html += '<div style="flex:1;position:relative;">';
1982
+ // Time ticks
1983
+ var tickCount = 6;
1984
+ for (var t = 0; t <= tickCount; t++) {
1985
+ var pct = (t / tickCount) * 100;
1986
+ var tickTime = minTime + (t / tickCount) * timeSpan;
1987
+ var d = new Date(tickTime);
1988
+ var label = d.getMonth() + 1 + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
1989
+ html += '<div style="position:absolute;left:' + pct + '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' + label + '</div>';
1990
+ }
1991
+ html += '</div></div>';
1992
+
1993
+ // Rows
1994
+ html += '<div class="gantt-body">';
1995
+ for (var i = 0; i < execs.length; i++) {
1996
+ var exec = execs[i];
1997
+ var execStart = ((exec.startTime - minTime) / timeSpan) * 100;
1998
+ var execWidth = Math.max(0.3, ((exec.endTime - exec.startTime) / timeSpan) * 100);
1999
+ var statusColor = exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
2000
+ var hasActivities = exec.activities && exec.activities.length > 0;
2001
+ var execId = 'gantt-exec-' + i;
2002
+
2003
+ // Main execution row
2004
+ html += '<div class="gantt-row" style="display:flex;height:' + rowH + 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
2005
+ 'onclick="(function(){var el=document.getElementById(\'' + execId + '\');if(el)el.style.display=el.style.display===\'none\'?\'block\':\'none\';})()" ' +
2006
+ 'title="Click to ' + (hasActivities ? 'expand' : 'view') + '">';
2007
+
2008
+ // Label
2009
+ var execName = exec.name || exec.filename || exec.id;
2010
+ if (execName.length > 28) execName = execName.slice(0, 28) + '...';
2011
+ var dur = this.computeDuration(exec.startTime, exec.endTime);
2012
+ var triggerBadge = exec.trigger ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' + escapeHtml(exec.trigger) + '</span>' : '';
2013
+ var expandIcon = hasActivities ? '<span style="color:#6b7280;margin-right:4px;">&#9654;</span>' : '<span style="width:14px;display:inline-block;"></span>';
2014
+
2015
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' + (rowH - 8) + 'px;">' +
2016
+ expandIcon + escapeHtml(execName) + triggerBadge + '</div>';
2017
+
2018
+ // Bar
2019
+ html += '<div style="flex:1;position:relative;padding:4px 0;">';
2020
+ html += '<div style="position:absolute;left:' + execStart + '%;width:' + execWidth + '%;top:4px;height:' + (rowH - 12) + 'px;' +
2021
+ 'background:' + statusColor + ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
2022
+ 'title="' + escapeHtml(exec.name || '') + ' | ' + dur + ' | ' + escapeHtml(exec.status) + '"></div>';
2023
+ html += '</div></div>';
2024
+
2025
+ // Sub-activities (collapsed by default)
2026
+ if (hasActivities) {
2027
+ html += '<div id="' + execId + '" style="display:none;background:#0a0e14;">';
2028
+ // Filter to top-level activities (no parentId or parentId is root)
2029
+ var rootIds = new Set();
2030
+ if (exec.activities.length > 0) {
2031
+ var firstAct = exec.activities[0];
2032
+ rootIds.add(firstAct.id);
2033
+ }
2034
+
2035
+ for (var j = 0; j < exec.activities.length; j++) {
2036
+ var act = exec.activities[j];
2037
+ var actStart = ((Math.max(act.startTime, exec.startTime) - minTime) / timeSpan) * 100;
2038
+ var actEnd = act.endTime || act.startTime;
2039
+ var actWidth = Math.max(0.2, ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100);
2040
+ var actColor = act.status === 'failed' ? '#f87171' :
2041
+ act.type === 'user' ? '#60a5fa' :
2042
+ act.type === 'assistant' ? '#34d399' :
2043
+ act.type === 'thinking' ? '#a78bfa' :
2044
+ act.type === 'tool_call' ? '#fb923c' :
2045
+ act.type === 'tool_result' ? '#4ade80' :
2046
+ act.type === 'agent' ? '#38bdf8' :
2047
+ '#6b7280';
2048
+ var actName = act.name || act.type;
2049
+ if (actName.length > 30) actName = actName.slice(0, 30) + '...';
2050
+ var isChild = act.parentId && !rootIds.has(act.id);
2051
+
2052
+ html += '<div style="display:flex;height:' + subRowH + 'px;border-bottom:1px solid #0d1117;">';
2053
+ html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:2px 10px 2px ' + (isChild ? '30' : '20') + 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' + (subRowH - 4) + 'px;">' +
2054
+ '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + actColor + ';margin-right:6px;vertical-align:middle;"></span>' +
2055
+ escapeHtml(actName) + '</div>';
2056
+ html += '<div style="flex:1;position:relative;">';
2057
+ html += '<div style="position:absolute;left:' + actStart + '%;width:' + actWidth + '%;top:3px;height:' + (subRowH - 8) + 'px;' +
2058
+ 'background:' + actColor + ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
2059
+ 'title="' + escapeHtml(act.name || act.type) + ' | ' + escapeHtml(act.status) + '"></div>';
2060
+ html += '</div></div>';
2061
+ }
2062
+ html += '</div>';
2063
+ }
2064
+ }
2065
+
2066
+ html += '</div>';
2067
+
2068
+ // Summary bar
2069
+ html += '<div style="padding:10px;border-top:1px solid #30363d;color:#8b949e;font-size:10px;">';
2070
+ html += escapeHtml(data.agentId) + ' &mdash; ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
2071
+ var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
2072
+ html += ' &mdash; ' + timeRange;
2073
+ html += '</div>';
2074
+
2075
+ html += '</div>';
2076
+ container.innerHTML = html;
2077
+ }
2078
+
2079
+ // ---------------------------------------------------------------------------
2080
+ // Tab 9: Process Map (Process Mining Graph)
1919
2081
  // ---------------------------------------------------------------------------
1920
2082
  renderProcessMap() {
1921
2083
  var trace = this.selectedTraceData || this.selectedTrace;
package/public/index.html CHANGED
@@ -1235,6 +1235,7 @@
1235
1235
  <div class="tab" data-tab="state">State Machine</div>
1236
1236
  <div class="tab" data-tab="summary">Summary</div>
1237
1237
  <div class="tab" data-tab="transcript">Transcript</div>
1238
+ <div class="tab" data-tab="agenttimeline">Agent Timeline</div>
1238
1239
  <div class="tab" data-tab="processmap">Process Map</div>
1239
1240
  </div>
1240
1241
 
@@ -1334,6 +1335,17 @@
1334
1335
  </div>
1335
1336
  </div>
1336
1337
 
1338
+ <!-- Agent Timeline (Gantt) tab -->
1339
+ <div class="tab-panel" id="panel-agenttimeline">
1340
+ <div class="timeline-container" id="agentTimelineContent" style="overflow:auto;">
1341
+ <div class="empty-state" id="agentTimelineEmpty">
1342
+ <div class="empty-state-icon">&#9776;</div>
1343
+ <div class="empty-state-title">Agent Timeline</div>
1344
+ <div class="empty-state-text">Select a trace to view all executions for its agent as a Gantt chart.</div>
1345
+ </div>
1346
+ </div>
1347
+ </div>
1348
+
1337
1349
  <!-- Process Map tab -->
1338
1350
  <div class="tab-panel" id="panel-processmap">
1339
1351
  <div id="cyProcessMap" style="width:100%;height:100%;min-height:500px;">