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.
- package/dist/{chunk-25MUPUYY.js → chunk-RWNVLZU7.js} +161 -0
- package/dist/cli.cjs +161 -0
- package/dist/cli.js +1 -1
- package/dist/index.cjs +161 -0
- package/dist/index.js +1 -1
- package/dist/public/dashboard.js +379 -0
- package/dist/public/index.html +31 -0
- package/dist/server.cjs +161 -0
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/public/dashboard.js +379 -0
- package/public/index.html +31 -0
package/dist/public/dashboard.js
CHANGED
|
@@ -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">⚙</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;">▶</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) + ' — ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
|
|
2071
|
+
var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
|
|
2072
|
+
html += ' — ' + 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">⚙</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">⚙</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">⚙</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
|
package/dist/public/index.html
CHANGED
|
@@ -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">☰</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">⚙</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">×</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
package/package.json
CHANGED