Files
helix-engage-server/src/supervisor/supervisor.controller.ts
saridsa2 b11f4ea336
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: log backfill endpoint for desktop log panel
- LogStreamService: ring buffer (500 entries) + getRecentLogs() method
- SupervisorController: GET /api/supervisor/logs/recent returns buffered
  log entries so the desktop log panel shows history on tab open, not
  just live stream

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 08:51:55 +05:30

96 lines
3.4 KiB
TypeScript

import { Controller, Get, Post, Body, Query, Sse, Logger } from '@nestjs/common';
import { Observable, filter, map } from 'rxjs';
import { SupervisorService } from './supervisor.service';
import { LogStreamService } from '../logging/log-stream.service';
@Controller('api/supervisor')
export class SupervisorController {
private readonly logger = new Logger(SupervisorController.name);
constructor(private readonly supervisor: SupervisorService) {}
@Get('active-calls')
getActiveCalls() {
return this.supervisor.getActiveCalls();
}
@Sse('active-calls/stream')
streamActiveCalls(): Observable<MessageEvent> {
this.logger.log('[SSE] Active calls stream opened');
return this.supervisor.activeCallSubject.pipe(
map(event => ({
data: JSON.stringify(event),
} as MessageEvent)),
);
}
@Get('team-performance')
async getTeamPerformance(@Query('date') date?: string) {
const targetDate = date ?? new Date().toISOString().split('T')[0];
this.logger.log(`Team performance: date=${targetDate}`);
return this.supervisor.getTeamPerformance(targetDate);
}
@Post('call-event')
handleCallEvent(@Body() body: any) {
const event = body.data ?? body;
this.logger.log(`[CALL-EVENT] ${JSON.stringify(event)}`);
this.supervisor.handleCallEvent(event);
return { received: true };
}
@Post('agent-event')
handleAgentEvent(@Body() body: any) {
const event = body.data ?? body;
this.logger.log(`[AGENT-EVENT] ${JSON.stringify(event)}`);
this.supervisor.handleAgentEvent(event);
return { received: true };
}
@Get('agent-state')
getAgentState(@Query('agentId') agentId: string) {
const state = this.supervisor.getAgentState(agentId);
return state ?? { state: 'offline', timestamp: null };
}
@Sse('agent-state/stream')
streamAgentState(@Query('agentId') agentId: string): Observable<MessageEvent> {
this.logger.log(`[SSE] Agent state stream opened for ${agentId}`);
return this.supervisor.agentStateSubject.pipe(
filter(event => event.agentId === agentId),
map(event => ({
data: JSON.stringify({ state: event.state, timestamp: event.timestamp }),
} as MessageEvent)),
);
}
// Worklist SSE — broadcast to all connected agents. When a missed
// call is created by the webhook, this fires immediately so agents
// don't wait for the 30s worklist poll. The payload includes the
// caller's phone + name for a toast notification.
@Sse('worklist/stream')
streamWorklistUpdates(): Observable<MessageEvent> {
this.logger.log('[SSE] Worklist stream opened');
return this.supervisor.worklistSubject.pipe(
map(event => ({
data: JSON.stringify(event),
} as MessageEvent)),
);
}
@Get('logs/recent')
getRecentLogs(@Query('limit') limit?: string) {
return LogStreamService.instance.getRecentLogs(limit ? parseInt(limit, 10) : 200);
}
@Sse('logs/stream')
streamLogs(): Observable<MessageEvent> {
this.logger.log('[SSE] Log stream opened');
return LogStreamService.instance.logSubject.pipe(
map(entry => ({
data: JSON.stringify(entry),
} as MessageEvent)),
);
}
}