mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat: SSE agent state, maint module, timestamp fix, missed call lead lookup
- SSE agent state stream: supervisor maintains state map from Ozonetel webhooks, streams via /api/supervisor/agent-state/stream - Force-logout via SSE: distinct force-logout event type avoids conflict with normal login cycle - Maint module (/api/maint): OTP-guarded endpoints for force-ready, unlock-agent, backfill-missed-calls, fix-timestamps - Fix Ozonetel IST→UTC timestamp conversion: istToUtc() in webhook controller and missed-queue service - Missed call lead lookup: ingestion queries leads by phone, stores leadId + leadName on Call entity - Timestamp backfill endpoint: throttled at 700ms/mutation, idempotent (skips already-fixed records) - Structured logging: full JSON payloads for agent/call webhooks, [DISPOSE] trace with agentId - Fix dead code: agent-state endpoint auto-assign was after return statement - Export SupervisorService for cross-module injection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Controller, Get, Post, Body, Query, Logger } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Query, Sse, Logger } from '@nestjs/common';
|
||||
import { Observable, filter, map } from 'rxjs';
|
||||
import { SupervisorService } from './supervisor.service';
|
||||
|
||||
@Controller('api/supervisor')
|
||||
@@ -22,7 +23,7 @@ export class SupervisorController {
|
||||
@Post('call-event')
|
||||
handleCallEvent(@Body() body: any) {
|
||||
const event = body.data ?? body;
|
||||
this.logger.log(`Call event: ${event.action} ucid=${event.ucid ?? event.monitorUCID} agent=${event.agent_id ?? event.agentID}`);
|
||||
this.logger.log(`[CALL-EVENT] ${JSON.stringify(event)}`);
|
||||
this.supervisor.handleCallEvent(event);
|
||||
return { received: true };
|
||||
}
|
||||
@@ -30,8 +31,25 @@ export class SupervisorController {
|
||||
@Post('agent-event')
|
||||
handleAgentEvent(@Body() body: any) {
|
||||
const event = body.data ?? body;
|
||||
this.logger.log(`Agent event: ${event.action} agent=${event.agentId ?? event.agent_id}`);
|
||||
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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user