diff --git a/internal/server/server.go b/internal/server/server.go
index 6baafb1..6dd7251 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -164,6 +164,11 @@ func (s *Server) setupRoutes() {
r.Post("/submit", s.slurpSubmitHandler)
r.Get("/artifacts/{ucxlAddr}", s.slurpRetrieveHandler)
})
+
+ // BACKBEAT monitoring endpoints
+ r.Route("/backbeat", func(r chi.Router) {
+ r.Get("/status", s.backbeatStatusHandler)
+ })
})
// GITEA webhook endpoint
@@ -1807,6 +1812,32 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
✅ Running
+
+
👥 Team Management
-
+
👥
-
No active teams
-
AI development teams will be formed automatically for new tasks
-
+
No assembled teams
+
Teams are automatically assembled when tasks are assigned to agents
@@ -1865,16 +1895,15 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
🤖 Agent Management
-
+
🤖
-
No registered agents
-
Register CHORUS agents to participate in development teams
-
+
No agents discovered
+
CHORUS agents are discovered organically and their personas tracked here
@@ -1952,15 +1981,6 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
console.log('Refreshing tasks...');
}
- function createTeam() {
- // Implement team creation logic
- alert('Team creation interface coming soon!');
- }
-
- function registerAgent() {
- // Implement agent registration logic
- alert('Agent registration interface coming soon!');
- }
function loadTasks() {
// Load tasks from API
@@ -2027,12 +2047,49 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
.then(response => response.json())
.then(data => {
console.log('Teams loaded:', data);
+ updateTeamsUI(data);
})
.catch(error => {
console.error('Error loading teams:', error);
});
}
+ function updateTeamsUI(data) {
+ const teamsContainer = document.getElementById('teams-grid');
+
+ if (data.teams && data.teams.length > 0) {
+ teamsContainer.innerHTML = data.teams.map(team =>
+ '
' +
+ '
' +
+ '
' + team.name + '
' +
+ '' + (team.status || 'ACTIVE').toUpperCase() + '' +
+ '' +
+ '
' +
+ 'Current Task' +
+ '' + (team.current_task || 'No active task') + '' +
+ '
' +
+ '
' +
+ 'Team Size' +
+ '' + (team.members ? team.members.length : 0) + ' agents' +
+ '
' +
+ '
' +
+ '
Team Members:' +
+ '
' +
+ (team.members || []).map(member =>
+ '
' +
+ '' +
+ '' + member.name + '' +
+ '' + (member.role || member.persona || 'General Agent') + '' +
+ '
'
+ ).join('') +
+ '
' +
+ '
' +
+ (team.created_at ? '
Assembled' + new Date(team.created_at).toLocaleDateString() + '
' : '') +
+ '
'
+ ).join('');
+ }
+ }
+
function loadAgents() {
// Load agents from API
fetch('/api/v1/agents')
@@ -2055,30 +2112,35 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
'
' +
'
' +
'
' + agent.name + '
' +
- '' +
+ '' +
'' +
'
' +
'Status' +
- '' + agent.status.toUpperCase() + '' +
+ '' + (agent.status || 'OFFLINE').toUpperCase() + '' +
'
' +
'
' +
- 'Model' +
- '' + agent.model + '' +
+ 'Current Persona' +
+ '' + (agent.persona || agent.role || 'General Agent') + '' +
'
' +
'
' +
- 'Tasks Completed' +
- '' + agent.tasks_completed + '' +
+ 'Model/Engine' +
+ '' + (agent.model || agent.engine || 'Unknown') + '' +
'
' +
'
' +
'Current Team' +
'' + (agent.current_team || 'Unassigned') + '' +
'
' +
+ '
' +
+ 'Tasks Completed' +
+ '' + (agent.tasks_completed || 0) + '' +
+ '
' +
'
' +
'
Capabilities:' +
'
' +
- agent.capabilities.map(cap => '' + cap + '').join('') +
+ (agent.capabilities || []).map(cap => '' + cap + '').join('') +
'
' +
'
' +
+ (agent.last_seen ? '
Last Seen' + new Date(agent.last_seen).toLocaleString() + '
' : '') +
'
' +
'
'
).join('');
@@ -2121,13 +2183,153 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
}
}, 30000);
+ // BACKBEAT Clock functionality
+ let beatHistory = [];
+ let canvas = null;
+ let ctx = null;
+ let lastDownbeat = null;
+
+ function initializeBackbeatClock() {
+ canvas = document.getElementById('pulse-trace');
+ if (canvas) {
+ canvas.width = canvas.offsetWidth;
+ canvas.height = 60;
+ ctx = canvas.getContext('2d');
+ drawPulseTrace();
+ }
+ }
+
+ function drawPulseTrace() {
+ if (!ctx) return;
+
+ const width = canvas.width;
+ const height = canvas.height;
+
+ // Clear canvas
+ ctx.clearRect(0, 0, width, height);
+
+ // Draw background grid
+ ctx.strokeStyle = '#e2e8f0';
+ ctx.lineWidth = 1;
+ for (let i = 0; i < width; i += 20) {
+ ctx.beginPath();
+ ctx.moveTo(i, 0);
+ ctx.lineTo(i, height);
+ ctx.stroke();
+ }
+ for (let i = 0; i < height; i += 15) {
+ ctx.beginPath();
+ ctx.moveTo(0, i);
+ ctx.lineTo(width, i);
+ ctx.stroke();
+ }
+
+ if (beatHistory.length < 2) return;
+
+ // Draw ECG-like trace
+ ctx.strokeStyle = '#667eea';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+
+ const timeWindow = 10000; // 10 seconds
+ const now = Date.now();
+ const startTime = now - timeWindow;
+
+ beatHistory.forEach((beat, index) => {
+ if (beat.timestamp < startTime) return;
+
+ const x = ((beat.timestamp - startTime) / timeWindow) * width;
+ const y = beat.isDownbeat ? height * 0.1 : height * 0.7;
+
+ if (index === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ // Create ECG-like spike
+ const prevX = index > 0 ? ((beatHistory[index-1].timestamp - startTime) / timeWindow) * width : x;
+ ctx.lineTo(x - 2, height * 0.5);
+ ctx.lineTo(x, y);
+ ctx.lineTo(x + 2, height * 0.5);
+ }
+ });
+
+ ctx.stroke();
+
+ // Draw heartbeat markers
+ ctx.fillStyle = '#e53e3e';
+ beatHistory.forEach(beat => {
+ if (beat.timestamp < startTime) return;
+ const x = ((beat.timestamp - startTime) / timeWindow) * width;
+ if (beat.isDownbeat) {
+ ctx.fillRect(x - 1, 5, 2, height - 10);
+ }
+ });
+ }
+
+ function loadBackbeatData() {
+ fetch('/api/v1/backbeat/status')
+ .then(response => response.json())
+ .then(data => {
+ updateBackbeatUI(data);
+ })
+ .catch(error => {
+ console.error('Error loading BACKBEAT data:', error);
+ });
+ }
+
+ function updateBackbeatUI(data) {
+ if (data.current_beat !== undefined) {
+ document.getElementById('current-beat').textContent = data.current_beat;
+ }
+
+ if (data.current_downbeat !== undefined) {
+ document.getElementById('current-downbeat').textContent = data.current_downbeat;
+ lastDownbeat = data.current_downbeat;
+ }
+
+ if (data.average_interval !== undefined) {
+ document.getElementById('avg-interval').textContent = Math.round(data.average_interval) + 'ms';
+ }
+
+ if (data.phase !== undefined) {
+ document.getElementById('beat-phase').textContent = data.phase;
+ }
+
+ // Add to beat history for visualization
+ if (data.current_beat !== undefined) {
+ const now = Date.now();
+ const isDownbeat = data.is_downbeat || false;
+
+ beatHistory.push({
+ beat: data.current_beat,
+ timestamp: now,
+ isDownbeat: isDownbeat,
+ phase: data.phase
+ });
+
+ // Keep only recent beats (last 10 seconds)
+ const cutoff = now - 10000;
+ beatHistory = beatHistory.filter(b => b.timestamp > cutoff);
+
+ drawPulseTrace();
+ }
+ }
+
// Load initial data
document.addEventListener('DOMContentLoaded', function() {
loadOverviewData();
loadTasks();
loadTeams();
loadAgents();
+ initializeBackbeatClock();
+ loadBackbeatData();
});
+
+ // Auto-refresh BACKBEAT data more frequently
+ setInterval(() => {
+ if (document.getElementById('overview').classList.contains('active')) {
+ loadBackbeatData();
+ }
+ }, 1000); // Update every second for real-time feel