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
|
@@ -1667,6 +1667,167 @@ 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
|
+
});
|
|
1739
|
+
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1740
|
+
try {
|
|
1741
|
+
const agentId = req.params.agentId;
|
|
1742
|
+
const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
|
|
1743
|
+
if (traces.length === 0) {
|
|
1744
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1745
|
+
}
|
|
1746
|
+
const activityCounts = /* @__PURE__ */ new Map();
|
|
1747
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
1748
|
+
const activityDurations = /* @__PURE__ */ new Map();
|
|
1749
|
+
const activityStatuses = /* @__PURE__ */ new Map();
|
|
1750
|
+
let totalTraces = 0;
|
|
1751
|
+
for (const trace of traces) {
|
|
1752
|
+
totalTraces++;
|
|
1753
|
+
const activities = [];
|
|
1754
|
+
if (trace.sessionEvents && trace.sessionEvents.length > 0) {
|
|
1755
|
+
for (const evt of trace.sessionEvents) {
|
|
1756
|
+
const name = evt.toolName || evt.name || evt.type;
|
|
1757
|
+
if (!name) continue;
|
|
1758
|
+
activities.push({
|
|
1759
|
+
name,
|
|
1760
|
+
type: evt.type,
|
|
1761
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1762
|
+
duration: evt.duration || 0
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
} else {
|
|
1766
|
+
const nodes2 = trace.nodes || {};
|
|
1767
|
+
const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1768
|
+
for (const node of sorted) {
|
|
1769
|
+
activities.push({
|
|
1770
|
+
name: node.name || node.type || node.id,
|
|
1771
|
+
type: node.type || "unknown",
|
|
1772
|
+
status: node.status || "completed",
|
|
1773
|
+
duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
|
|
1778
|
+
for (let i = 0; i < seq.length; i++) {
|
|
1779
|
+
const act = seq[i];
|
|
1780
|
+
activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
|
|
1781
|
+
if (i < seq.length - 1) {
|
|
1782
|
+
const key = act + " \u2192 " + seq[i + 1];
|
|
1783
|
+
transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
for (const act of activities) {
|
|
1787
|
+
if (act.duration > 0) {
|
|
1788
|
+
const durs = activityDurations.get(act.name) || [];
|
|
1789
|
+
durs.push(act.duration);
|
|
1790
|
+
activityDurations.set(act.name, durs);
|
|
1791
|
+
}
|
|
1792
|
+
const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
|
|
1793
|
+
if (act.status === "failed") st.fail++;
|
|
1794
|
+
else st.ok++;
|
|
1795
|
+
activityStatuses.set(act.name, st);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
|
|
1799
|
+
const durs = activityDurations.get(name) || [];
|
|
1800
|
+
const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
|
|
1801
|
+
const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
|
|
1802
|
+
return {
|
|
1803
|
+
id: name,
|
|
1804
|
+
label: name,
|
|
1805
|
+
count,
|
|
1806
|
+
frequency: count / totalTraces,
|
|
1807
|
+
avgDuration,
|
|
1808
|
+
failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
|
|
1809
|
+
isVirtual: name === "[START]" || name === "[END]"
|
|
1810
|
+
};
|
|
1811
|
+
});
|
|
1812
|
+
const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
|
|
1813
|
+
const [source, target] = key.split(" \u2192 ");
|
|
1814
|
+
return { source, target, count, frequency: count / totalTraces };
|
|
1815
|
+
});
|
|
1816
|
+
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
1817
|
+
const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
|
|
1818
|
+
res.json({
|
|
1819
|
+
agentId,
|
|
1820
|
+
totalTraces,
|
|
1821
|
+
nodes,
|
|
1822
|
+
edges,
|
|
1823
|
+
maxEdgeCount,
|
|
1824
|
+
maxNodeCount
|
|
1825
|
+
});
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
console.error("Process graph error:", error);
|
|
1828
|
+
res.status(500).json({ error: "Failed to build process graph" });
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1670
1831
|
this.app.get("/api/stats/:agentId", (req, res) => {
|
|
1671
1832
|
try {
|
|
1672
1833
|
const agentStats = this.stats.getAgentStats(req.params.agentId);
|
package/dist/cli.cjs
CHANGED
|
@@ -1561,6 +1561,167 @@ 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
|
+
});
|
|
1633
|
+
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1634
|
+
try {
|
|
1635
|
+
const agentId = req.params.agentId;
|
|
1636
|
+
const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
|
|
1637
|
+
if (traces.length === 0) {
|
|
1638
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1639
|
+
}
|
|
1640
|
+
const activityCounts = /* @__PURE__ */ new Map();
|
|
1641
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
1642
|
+
const activityDurations = /* @__PURE__ */ new Map();
|
|
1643
|
+
const activityStatuses = /* @__PURE__ */ new Map();
|
|
1644
|
+
let totalTraces = 0;
|
|
1645
|
+
for (const trace of traces) {
|
|
1646
|
+
totalTraces++;
|
|
1647
|
+
const activities = [];
|
|
1648
|
+
if (trace.sessionEvents && trace.sessionEvents.length > 0) {
|
|
1649
|
+
for (const evt of trace.sessionEvents) {
|
|
1650
|
+
const name = evt.toolName || evt.name || evt.type;
|
|
1651
|
+
if (!name) continue;
|
|
1652
|
+
activities.push({
|
|
1653
|
+
name,
|
|
1654
|
+
type: evt.type,
|
|
1655
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1656
|
+
duration: evt.duration || 0
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
const nodes2 = trace.nodes || {};
|
|
1661
|
+
const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1662
|
+
for (const node of sorted) {
|
|
1663
|
+
activities.push({
|
|
1664
|
+
name: node.name || node.type || node.id,
|
|
1665
|
+
type: node.type || "unknown",
|
|
1666
|
+
status: node.status || "completed",
|
|
1667
|
+
duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
|
|
1672
|
+
for (let i = 0; i < seq.length; i++) {
|
|
1673
|
+
const act = seq[i];
|
|
1674
|
+
activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
|
|
1675
|
+
if (i < seq.length - 1) {
|
|
1676
|
+
const key = act + " \u2192 " + seq[i + 1];
|
|
1677
|
+
transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
for (const act of activities) {
|
|
1681
|
+
if (act.duration > 0) {
|
|
1682
|
+
const durs = activityDurations.get(act.name) || [];
|
|
1683
|
+
durs.push(act.duration);
|
|
1684
|
+
activityDurations.set(act.name, durs);
|
|
1685
|
+
}
|
|
1686
|
+
const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
|
|
1687
|
+
if (act.status === "failed") st.fail++;
|
|
1688
|
+
else st.ok++;
|
|
1689
|
+
activityStatuses.set(act.name, st);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
|
|
1693
|
+
const durs = activityDurations.get(name) || [];
|
|
1694
|
+
const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
|
|
1695
|
+
const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
|
|
1696
|
+
return {
|
|
1697
|
+
id: name,
|
|
1698
|
+
label: name,
|
|
1699
|
+
count,
|
|
1700
|
+
frequency: count / totalTraces,
|
|
1701
|
+
avgDuration,
|
|
1702
|
+
failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
|
|
1703
|
+
isVirtual: name === "[START]" || name === "[END]"
|
|
1704
|
+
};
|
|
1705
|
+
});
|
|
1706
|
+
const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
|
|
1707
|
+
const [source, target] = key.split(" \u2192 ");
|
|
1708
|
+
return { source, target, count, frequency: count / totalTraces };
|
|
1709
|
+
});
|
|
1710
|
+
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
1711
|
+
const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
|
|
1712
|
+
res.json({
|
|
1713
|
+
agentId,
|
|
1714
|
+
totalTraces,
|
|
1715
|
+
nodes,
|
|
1716
|
+
edges,
|
|
1717
|
+
maxEdgeCount,
|
|
1718
|
+
maxNodeCount
|
|
1719
|
+
});
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
console.error("Process graph error:", error);
|
|
1722
|
+
res.status(500).json({ error: "Failed to build process graph" });
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1564
1725
|
this.app.get("/api/stats/:agentId", (req, res) => {
|
|
1565
1726
|
try {
|
|
1566
1727
|
const agentStats = this.stats.getAgentStats(req.params.agentId);
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1706,6 +1706,167 @@ 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
|
+
});
|
|
1778
|
+
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1779
|
+
try {
|
|
1780
|
+
const agentId = req.params.agentId;
|
|
1781
|
+
const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
|
|
1782
|
+
if (traces.length === 0) {
|
|
1783
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1784
|
+
}
|
|
1785
|
+
const activityCounts = /* @__PURE__ */ new Map();
|
|
1786
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
1787
|
+
const activityDurations = /* @__PURE__ */ new Map();
|
|
1788
|
+
const activityStatuses = /* @__PURE__ */ new Map();
|
|
1789
|
+
let totalTraces = 0;
|
|
1790
|
+
for (const trace of traces) {
|
|
1791
|
+
totalTraces++;
|
|
1792
|
+
const activities = [];
|
|
1793
|
+
if (trace.sessionEvents && trace.sessionEvents.length > 0) {
|
|
1794
|
+
for (const evt of trace.sessionEvents) {
|
|
1795
|
+
const name = evt.toolName || evt.name || evt.type;
|
|
1796
|
+
if (!name) continue;
|
|
1797
|
+
activities.push({
|
|
1798
|
+
name,
|
|
1799
|
+
type: evt.type,
|
|
1800
|
+
status: evt.toolError ? "failed" : "completed",
|
|
1801
|
+
duration: evt.duration || 0
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
} else {
|
|
1805
|
+
const nodes2 = trace.nodes || {};
|
|
1806
|
+
const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
|
|
1807
|
+
for (const node of sorted) {
|
|
1808
|
+
activities.push({
|
|
1809
|
+
name: node.name || node.type || node.id,
|
|
1810
|
+
type: node.type || "unknown",
|
|
1811
|
+
status: node.status || "completed",
|
|
1812
|
+
duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
|
|
1817
|
+
for (let i = 0; i < seq.length; i++) {
|
|
1818
|
+
const act = seq[i];
|
|
1819
|
+
activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
|
|
1820
|
+
if (i < seq.length - 1) {
|
|
1821
|
+
const key = act + " \u2192 " + seq[i + 1];
|
|
1822
|
+
transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
for (const act of activities) {
|
|
1826
|
+
if (act.duration > 0) {
|
|
1827
|
+
const durs = activityDurations.get(act.name) || [];
|
|
1828
|
+
durs.push(act.duration);
|
|
1829
|
+
activityDurations.set(act.name, durs);
|
|
1830
|
+
}
|
|
1831
|
+
const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
|
|
1832
|
+
if (act.status === "failed") st.fail++;
|
|
1833
|
+
else st.ok++;
|
|
1834
|
+
activityStatuses.set(act.name, st);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
|
|
1838
|
+
const durs = activityDurations.get(name) || [];
|
|
1839
|
+
const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
|
|
1840
|
+
const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
|
|
1841
|
+
return {
|
|
1842
|
+
id: name,
|
|
1843
|
+
label: name,
|
|
1844
|
+
count,
|
|
1845
|
+
frequency: count / totalTraces,
|
|
1846
|
+
avgDuration,
|
|
1847
|
+
failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
|
|
1848
|
+
isVirtual: name === "[START]" || name === "[END]"
|
|
1849
|
+
};
|
|
1850
|
+
});
|
|
1851
|
+
const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
|
|
1852
|
+
const [source, target] = key.split(" \u2192 ");
|
|
1853
|
+
return { source, target, count, frequency: count / totalTraces };
|
|
1854
|
+
});
|
|
1855
|
+
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
1856
|
+
const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
|
|
1857
|
+
res.json({
|
|
1858
|
+
agentId,
|
|
1859
|
+
totalTraces,
|
|
1860
|
+
nodes,
|
|
1861
|
+
edges,
|
|
1862
|
+
maxEdgeCount,
|
|
1863
|
+
maxNodeCount
|
|
1864
|
+
});
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
console.error("Process graph error:", error);
|
|
1867
|
+
res.status(500).json({ error: "Failed to build process graph" });
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1709
1870
|
this.app.get("/api/stats/:agentId", (req, res) => {
|
|
1710
1871
|
try {
|
|
1711
1872
|
const agentStats = this.stats.getAgentStats(req.params.agentId);
|