agentflow-dashboard 0.4.0 → 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.
@@ -307,6 +307,16 @@ class AgentFlowDashboard {
307
307
  this.selectedTrace = trace;
308
308
  this.selectedTraceData = trace;
309
309
 
310
+ // Reset agent-level caches when agent changes
311
+ if (this._processMapAgent !== trace.agentId) {
312
+ this._processMapAgent = null;
313
+ if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
314
+ }
315
+ if (this._agentTimelineAgent !== trace.agentId) {
316
+ this._agentTimelineAgent = null;
317
+ this._agentTimelineRendered = false;
318
+ }
319
+
310
320
  // Update sidebar selection
311
321
  document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
312
322
  var activeEl = document.querySelector('.session-item[data-filename="' + CSS.escape(filename) + '"]');
@@ -850,6 +860,8 @@ class AgentFlowDashboard {
850
860
  case 'state': this.renderStateMachine(); break;
851
861
  case 'summary': this.renderSummary(); break;
852
862
  case 'transcript': this.renderTranscript(); break;
863
+ case 'agenttimeline': this.renderAgentTimeline(); break;
864
+ case 'processmap': this.renderProcessMap(); break;
853
865
  }
854
866
  this.updateToolbarInfo();
855
867
  }
@@ -1906,6 +1918,373 @@ class AgentFlowDashboard {
1906
1918
 
1907
1919
  return 'other';
1908
1920
  }
1921
+
1922
+ // ---------------------------------------------------------------------------
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)
2081
+ // ---------------------------------------------------------------------------
2082
+ renderProcessMap() {
2083
+ var trace = this.selectedTraceData || this.selectedTrace;
2084
+ if (!trace || !trace.agentId) {
2085
+ document.getElementById('processMapEmpty').style.display = '';
2086
+ return;
2087
+ }
2088
+
2089
+ var agentId = trace.agentId;
2090
+ var self = this;
2091
+
2092
+ // Avoid re-fetching for same agent
2093
+ if (this._processMapAgent === agentId && this._cyProcessMap) return;
2094
+ this._processMapAgent = agentId;
2095
+
2096
+ document.getElementById('processMapEmpty').innerHTML =
2097
+ '<div class="empty-state-icon" style="animation:spin 1s linear infinite">&#9881;</div>' +
2098
+ '<div class="empty-state-text">Building process map for ' + escapeHtml(agentId) + '...</div>';
2099
+ document.getElementById('processMapEmpty').style.display = '';
2100
+
2101
+ fetch('/api/agents/' + encodeURIComponent(agentId) + '/process-graph')
2102
+ .then(function(r) { return r.json(); })
2103
+ .then(function(data) {
2104
+ if (data.error || !data.nodes || data.nodes.length === 0) {
2105
+ document.getElementById('processMapEmpty').innerHTML =
2106
+ '<div class="empty-state-icon">&#9881;</div>' +
2107
+ '<div class="empty-state-text">No process data for ' + escapeHtml(agentId) + '</div>';
2108
+ return;
2109
+ }
2110
+ document.getElementById('processMapEmpty').style.display = 'none';
2111
+ self._buildProcessMapGraph(data);
2112
+ })
2113
+ .catch(function() {
2114
+ document.getElementById('processMapEmpty').innerHTML =
2115
+ '<div class="empty-state-icon">&#9881;</div>' +
2116
+ '<div class="empty-state-text">Failed to load process map.</div>';
2117
+ });
2118
+ }
2119
+
2120
+ _buildProcessMapGraph(data) {
2121
+ if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
2122
+
2123
+ var elements = [];
2124
+ var maxNode = data.maxNodeCount || 1;
2125
+ var maxEdge = data.maxEdgeCount || 1;
2126
+
2127
+ // Add nodes
2128
+ for (var i = 0; i < data.nodes.length; i++) {
2129
+ var node = data.nodes[i];
2130
+ // Skip very rare activities (< 2% frequency) to reduce clutter, but keep virtual nodes
2131
+ if (!node.isVirtual && node.frequency < 0.02 && data.nodes.length > 15) continue;
2132
+
2133
+ var size = node.isVirtual ? 30 : Math.max(25, Math.min(70, 25 + 45 * (node.count / maxNode)));
2134
+ var label = node.label;
2135
+ if (!node.isVirtual && node.count > 1) label += ' (' + node.count + ')';
2136
+
2137
+ elements.push({
2138
+ group: 'nodes',
2139
+ data: {
2140
+ id: node.id,
2141
+ label: label,
2142
+ count: node.count,
2143
+ frequency: node.frequency,
2144
+ avgDuration: node.avgDuration,
2145
+ failRate: node.failRate,
2146
+ isVirtual: node.isVirtual,
2147
+ size: size,
2148
+ fullData: node
2149
+ }
2150
+ });
2151
+ }
2152
+
2153
+ // Collect valid node IDs
2154
+ var validIds = new Set(elements.map(function(e) { return e.data.id; }));
2155
+
2156
+ // Add edges (only between valid nodes)
2157
+ for (var j = 0; j < data.edges.length; j++) {
2158
+ var edge = data.edges[j];
2159
+ if (!validIds.has(edge.source) || !validIds.has(edge.target)) continue;
2160
+ // Skip very rare transitions
2161
+ if (edge.frequency < 0.02 && data.edges.length > 30) continue;
2162
+
2163
+ var width = Math.max(1, Math.min(8, 1 + 7 * (edge.count / maxEdge)));
2164
+ var opacity = Math.max(0.3, Math.min(1.0, 0.3 + 0.7 * (edge.count / maxEdge)));
2165
+
2166
+ elements.push({
2167
+ group: 'edges',
2168
+ data: {
2169
+ id: 'pe-' + edge.source + '-' + edge.target,
2170
+ source: edge.source,
2171
+ target: edge.target,
2172
+ count: edge.count,
2173
+ frequency: edge.frequency,
2174
+ width: width,
2175
+ opacity: opacity,
2176
+ label: edge.count > 1 ? String(edge.count) : ''
2177
+ }
2178
+ });
2179
+ }
2180
+
2181
+ var container = document.getElementById('cyProcessMap');
2182
+ var self = this;
2183
+
2184
+ this._cyProcessMap = cytoscape({
2185
+ container: container,
2186
+ elements: elements,
2187
+ style: [
2188
+ {
2189
+ selector: 'node',
2190
+ style: {
2191
+ 'label': 'data(label)',
2192
+ 'width': 'data(size)',
2193
+ 'height': 'data(size)',
2194
+ 'font-size': '9px',
2195
+ 'text-valign': 'bottom',
2196
+ 'text-halign': 'center',
2197
+ 'text-margin-y': 6,
2198
+ 'color': '#c9d1d9',
2199
+ 'text-outline-color': '#0d1117',
2200
+ 'text-outline-width': 2,
2201
+ 'text-wrap': 'ellipsis',
2202
+ 'text-max-width': '100px',
2203
+ 'border-width': 2,
2204
+ 'border-color': '#30363d',
2205
+ 'background-color': '#3b82f6',
2206
+ 'shape': 'round-rectangle'
2207
+ }
2208
+ },
2209
+ // Virtual START/END nodes
2210
+ { selector: 'node[?isVirtual]', style: {
2211
+ 'background-color': '#6b7280', 'shape': 'ellipse', 'border-color': '#4b5563',
2212
+ 'font-size': '8px', 'font-weight': 'bold', 'text-valign': 'center', 'text-margin-y': 0
2213
+ }},
2214
+ // Color by fail rate: green → yellow → red
2215
+ { selector: 'node[failRate <= 0]', style: { 'background-color': '#10b981', 'border-color': '#2ea043' }},
2216
+ { selector: 'node[failRate > 0][failRate <= 0.1]', style: { 'background-color': '#22c55e', 'border-color': '#3fb950' }},
2217
+ { selector: 'node[failRate > 0.1][failRate <= 0.3]', style: { 'background-color': '#eab308', 'border-color': '#d29922' }},
2218
+ { selector: 'node[failRate > 0.3]', style: { 'background-color': '#ef4444', 'border-color': '#f85149' }},
2219
+ // Selected
2220
+ { selector: ':selected', style: { 'border-width': 4, 'border-color': '#f59e0b', 'overlay-opacity': 0.08 }},
2221
+ // Edges
2222
+ {
2223
+ selector: 'edge',
2224
+ style: {
2225
+ 'width': 'data(width)',
2226
+ 'opacity': 'data(opacity)',
2227
+ 'line-color': '#6b7280',
2228
+ 'target-arrow-color': '#6b7280',
2229
+ 'target-arrow-shape': 'triangle',
2230
+ 'curve-style': 'bezier',
2231
+ 'arrow-scale': 0.7,
2232
+ 'label': 'data(label)',
2233
+ 'font-size': '8px',
2234
+ 'color': '#8b949e',
2235
+ 'text-outline-color': '#0d1117',
2236
+ 'text-outline-width': 1.5,
2237
+ 'text-rotation': 'autorotate'
2238
+ }
2239
+ }
2240
+ ],
2241
+ layout: {
2242
+ name: 'breadthfirst',
2243
+ directed: true,
2244
+ padding: 50,
2245
+ spacingFactor: 1.6,
2246
+ animate: true,
2247
+ animationDuration: 400,
2248
+ roots: elements.filter(function(e) { return e.data && e.data.id === '[START]'; }).length > 0
2249
+ ? ['[START]'] : undefined
2250
+ },
2251
+ minZoom: 0.15,
2252
+ maxZoom: 4,
2253
+ wheelSensitivity: 0.3
2254
+ });
2255
+
2256
+ // Click node → show detail
2257
+ this._cyProcessMap.on('tap', 'node', function(e) {
2258
+ var d = e.target.data().fullData;
2259
+ if (!d || d.isVirtual) return;
2260
+ var panel = document.getElementById('processMapDetailPanel');
2261
+ var title = document.getElementById('processMapDetailTitle');
2262
+ var body = document.getElementById('processMapDetailBody');
2263
+
2264
+ title.textContent = d.label;
2265
+ var html = '';
2266
+ html += self.detailRow('Occurrences', d.count);
2267
+ html += self.detailRow('Frequency', (d.frequency * 100).toFixed(1) + '% of traces');
2268
+ if (d.avgDuration > 0) html += self.detailRow('Avg Duration', self.computeDuration(0, d.avgDuration));
2269
+ html += self.detailRow('Failure Rate', (d.failRate * 100).toFixed(1) + '%');
2270
+ body.innerHTML = html;
2271
+ panel.classList.add('active');
2272
+ });
2273
+
2274
+ this._cyProcessMap.on('tap', function(e) {
2275
+ if (e.target === self._cyProcessMap) {
2276
+ document.getElementById('processMapDetailPanel').classList.remove('active');
2277
+ }
2278
+ });
2279
+
2280
+ // Close button
2281
+ var closeBtn = document.getElementById('processMapDetailClose');
2282
+ if (closeBtn) {
2283
+ closeBtn.onclick = function() {
2284
+ document.getElementById('processMapDetailPanel').classList.remove('active');
2285
+ };
2286
+ }
2287
+ }
1909
2288
  }
1910
2289
 
1911
2290
  // Initialize
@@ -1235,6 +1235,8 @@
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>
1239
+ <div class="tab" data-tab="processmap">Process Map</div>
1238
1240
  </div>
1239
1241
 
1240
1242
  <div class="toolbar" id="toolbar">
@@ -1332,6 +1334,35 @@
1332
1334
  </div>
1333
1335
  </div>
1334
1336
  </div>
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
+
1349
+ <!-- Process Map tab -->
1350
+ <div class="tab-panel" id="panel-processmap">
1351
+ <div id="cyProcessMap" style="width:100%;height:100%;min-height:500px;">
1352
+ <div class="empty-state" id="processMapEmpty">
1353
+ <div class="empty-state-icon">&#9881;</div>
1354
+ <div class="empty-state-title">Process Map</div>
1355
+ <div class="empty-state-text">Select a trace to view the process mining graph for its agent.<br>Shows activity flows aggregated across all executions.</div>
1356
+ </div>
1357
+ </div>
1358
+ <div class="node-detail-panel" id="processMapDetailPanel">
1359
+ <div class="node-detail-header">
1360
+ <h4 id="processMapDetailTitle">Activity Details</h4>
1361
+ <button class="node-detail-close" id="processMapDetailClose">&times;</button>
1362
+ </div>
1363
+ <div class="node-detail-body" id="processMapDetailBody"></div>
1364
+ </div>
1365
+ </div>
1335
1366
  </div>
1336
1367
  </main>
1337
1368
  </div>
package/dist/server.cjs CHANGED
@@ -1701,6 +1701,167 @@ 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
+ });
1773
+ this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1774
+ try {
1775
+ const agentId = req.params.agentId;
1776
+ const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1777
+ if (traces.length === 0) {
1778
+ return res.status(404).json({ error: "No traces for agent" });
1779
+ }
1780
+ const activityCounts = /* @__PURE__ */ new Map();
1781
+ const transitionCounts = /* @__PURE__ */ new Map();
1782
+ const activityDurations = /* @__PURE__ */ new Map();
1783
+ const activityStatuses = /* @__PURE__ */ new Map();
1784
+ let totalTraces = 0;
1785
+ for (const trace of traces) {
1786
+ totalTraces++;
1787
+ const activities = [];
1788
+ if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1789
+ for (const evt of trace.sessionEvents) {
1790
+ const name = evt.toolName || evt.name || evt.type;
1791
+ if (!name) continue;
1792
+ activities.push({
1793
+ name,
1794
+ type: evt.type,
1795
+ status: evt.toolError ? "failed" : "completed",
1796
+ duration: evt.duration || 0
1797
+ });
1798
+ }
1799
+ } else {
1800
+ const nodes2 = trace.nodes || {};
1801
+ const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1802
+ for (const node of sorted) {
1803
+ activities.push({
1804
+ name: node.name || node.type || node.id,
1805
+ type: node.type || "unknown",
1806
+ status: node.status || "completed",
1807
+ duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1808
+ });
1809
+ }
1810
+ }
1811
+ const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
1812
+ for (let i = 0; i < seq.length; i++) {
1813
+ const act = seq[i];
1814
+ activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
1815
+ if (i < seq.length - 1) {
1816
+ const key = act + " \u2192 " + seq[i + 1];
1817
+ transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
1818
+ }
1819
+ }
1820
+ for (const act of activities) {
1821
+ if (act.duration > 0) {
1822
+ const durs = activityDurations.get(act.name) || [];
1823
+ durs.push(act.duration);
1824
+ activityDurations.set(act.name, durs);
1825
+ }
1826
+ const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
1827
+ if (act.status === "failed") st.fail++;
1828
+ else st.ok++;
1829
+ activityStatuses.set(act.name, st);
1830
+ }
1831
+ }
1832
+ const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
1833
+ const durs = activityDurations.get(name) || [];
1834
+ const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
1835
+ const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
1836
+ return {
1837
+ id: name,
1838
+ label: name,
1839
+ count,
1840
+ frequency: count / totalTraces,
1841
+ avgDuration,
1842
+ failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
1843
+ isVirtual: name === "[START]" || name === "[END]"
1844
+ };
1845
+ });
1846
+ const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
1847
+ const [source, target] = key.split(" \u2192 ");
1848
+ return { source, target, count, frequency: count / totalTraces };
1849
+ });
1850
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1851
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1852
+ res.json({
1853
+ agentId,
1854
+ totalTraces,
1855
+ nodes,
1856
+ edges,
1857
+ maxEdgeCount,
1858
+ maxNodeCount
1859
+ });
1860
+ } catch (error) {
1861
+ console.error("Process graph error:", error);
1862
+ res.status(500).json({ error: "Failed to build process graph" });
1863
+ }
1864
+ });
1704
1865
  this.app.get("/api/stats/:agentId", (req, res) => {
1705
1866
  try {
1706
1867
  const agentStats = this.stats.getAgentStats(req.params.agentId);
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DashboardServer
3
- } from "./chunk-25MUPUYY.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.0",
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",