fix: server-side ACW auto-dispose (Layer 3) — 30s timeout safety net

When Ozonetel sends an ACW event, starts a 30-second timer. If no
/api/ozonetel/dispose call arrives within that window (frontend
crashed, tab closed, page refreshed), auto-disposes with "General
Enquiry" + autoRelease:true. Agent exits ACW automatically.

Timer is cancelled when:
  - Frontend submits disposition normally (cancelAcwTimer in controller)
  - Agent transitions to Ready or Offline
  - Agent logs out

Wiring: OzonetelAgentModule now imports SupervisorModule (forwardRef
for circular dep), controller injects SupervisorService to cancel
the timer on successful dispose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 12:29:41 +05:30
parent 33dc8b5669
commit 7717536622
4 changed files with 69 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { EventBusService } from '../events/event-bus.service';
import { Topics } from '../events/event-types';
import { TelephonyConfigService } from '../config/telephony-config.service';
import { SupervisorService } from '../supervisor/supervisor.service';
@Controller('api/ozonetel')
export class OzonetelAgentController {
@@ -16,6 +17,7 @@ export class OzonetelAgentController {
private readonly missedQueue: MissedQueueService,
private readonly platform: PlatformGraphqlService,
private readonly eventBus: EventBusService,
private readonly supervisor: SupervisorService,
) {}
// Read-through accessors so admin updates take effect immediately.
@@ -124,6 +126,9 @@ export class OzonetelAgentController {
const ozonetelDisposition = this.mapToOzonetelDisposition(body.disposition);
// Cancel the ACW auto-dispose timer — the frontend submitted disposition
this.supervisor.cancelAcwTimer(this.defaultAgentId);
this.logger.log(`[DISPOSE] ucid=${body.ucid} disposition=${body.disposition} → ozonetel="${ozonetelDisposition}" agentId=${this.defaultAgentId} callerPhone=${body.callerPhone ?? 'none'} direction=${body.direction ?? 'unknown'} leadId=${body.leadId ?? 'none'}`);
try {