Commit Graph

18 Commits

Author SHA1 Message Date
0248c4cad1 fix: #536 #538 performance metrics — filter CDR by agentId, add team call counts
#536: Performance endpoint now accepts agentId query param and filters
CDR to that agent only. Previously returned all agents' calls as one
agent's total. Fixed 'Unanswered' → 'NotAnswered' status filter.

#538: Team performance now includes per-agent call metrics (total,
inbound, outbound, answered, missed) from CDR data + teamTotals
aggregate. Previously only returned Ozonetel time breakdown without
any call counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 19:33:59 +05:30
9dc02e107a fix: E.164 phone format for outbound call records (+91 prefix)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:27:50 +05:30
c807cf737f fix: outbound call records via dispose + campaign-filtered polling
- Dispose endpoint creates Call entity for outbound calls (direction=OUTBOUND).
  The webhook now skips outbound, so dispose is the only path for outbound records.
- MissedQueueService filters abandonCalls by own campaign (read from TelephonyConfigService).
  Prevents cross-tenant ingestion from shared Ozonetel account.
- WorklistModule provides TelephonyConfigService directly (avoids circular dep with ConfigThemeModule).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:20:45 +05:30
9665500b63 fix: dispose uses per-agent ID + campaign fallback operator precedence
Dispose endpoint now accepts agentId from body (same pattern as dial
fix). Fixes "Invalid Agent ID" when disposing as non-default agent.
Also fixed JS operator precedence bug in campaign name fallback
that produced "Inbound_" instead of "Inbound_918041763400".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:49:10 +05:30
898ff65951 fix: camelCase field names + dial uses per-agent config
Defect 5: Worklist, missed-call-webhook, missed-queue, ai-chat, and
rules-engine all used legacy lowercase field names (callbackstatus,
callsourcenumber, missedcallcount, callbackattemptedat) from the old
VPS schema. Fixed to camelCase (callbackStatus, callSourceNumber,
missedCallCount, callbackAttemptedAt) matching the current SDK sync.

Defect 6: Dial endpoint used global defaults (OZONETEL_AGENT_ID env
var) instead of the logged-in agent's config. Now accepts agentId
and campaignName from the frontend request body. Falls back to
telephony config → DID-derived campaign name → explicit error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:29:19 +05:30
7717536622 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>
2026-04-10 12:29:41 +05:30
619e9ab405 feat(onboarding/phase-1): admin-editable telephony, ai, and setup-state config
Phase 1 of hospital onboarding & self-service plan
(docs/superpowers/plans/2026-04-06-hospital-onboarding-self-service.md).

Backend foundations to support the upcoming staff-portal Settings hub and
6-step setup wizard. No frontend in this phase.

New config services (mirroring ThemeService / WidgetConfigService):
- SetupStateService    — tracks completion of 6 wizard steps; isWizardRequired()
                         drives the post-login redirect
- TelephonyConfigService — Ozonetel + Exotel + SIP, replaces 8 env vars,
                           seeds from env on first boot, masks secrets on GET,
                           '***masked***' sentinel on PUT means "keep existing"
- AiConfigService      — provider, model, temperature, system prompt addendum;
                         API keys remain in env

New endpoints under /api/config:
- GET  /api/config/setup-state                returns state + wizardRequired flag
- PUT  /api/config/setup-state/steps/:step    mark step complete/incomplete
- POST /api/config/setup-state/dismiss        dismiss wizard
- POST /api/config/setup-state/reset
- GET  /api/config/telephony                  masked
- PUT  /api/config/telephony
- POST /api/config/telephony/reset
- GET  /api/config/ai
- PUT  /api/config/ai
- POST /api/config/ai/reset

ConfigThemeModule is now @Global() so the new sidecar config services are
injectable from AuthModule, OzonetelAgentModule, MaintModule without creating
a circular dependency (ConfigThemeModule already imports AuthModule for
SessionService).

Migrated 11 env-var read sites to use the new services:
- ozonetel-agent.service: exotel API + ozonetel did/sipId via read-through getters
- ozonetel-agent.controller: defaultAgentId/Password/SipId via getters
- kookoo-ivr.controller: sipId/callerId via getters
- auth.controller: OZONETEL_AGENT_PASSWORD (login + logout)
- agent-config.service: sipDomain/wsPort/campaignName via getters
- maint.controller: forceReady + unlockAgent
- ai-provider: createAiModel and isAiConfigured refactored to pure factories
  taking AiProviderOpts; no more ConfigService dependency
- widget-chat.service, recordings.service, ai-enrichment.service,
  ai-chat.controller, ai-insight.consumer, call-assist.service: each builds
  the AI model from AiConfigService.getConfig() + ConfigService API keys

Hot-reload guarantee: every consumer reads via a getter or builds per-call,
so admin updates take effect without sidecar restart. WidgetChatService
specifically rebuilds the model on each streamReply().

Bug fix bundled: dropped widget.json.hospitalName field (the original
duplicate that started this whole thread). WidgetConfigService now reads
brand.hospitalName from ThemeService at the 2 generateKey call sites.
Single source of truth for hospital name is workspace branding.

First-boot env seeding: TelephonyConfigService and AiConfigService both
copy their respective env vars into a fresh data/*.json on onModuleInit if
the file doesn't exist. Existing deployments auto-migrate without manual
intervention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:02:07 +05:30
3e2e7372cc feat: event bus with Redpanda + AI insight consumer
- EventBusService: Kafka/Redpanda pub/sub with kafkajs, graceful fallback when broker unavailable
- Topics: call.completed, call.missed, agent.state
- AiInsightConsumer: on call.completed, fetches lead activity → OpenAI generates summary + suggested action → updates Lead entity on platform
- Disposition endpoint emits call.completed event after Ozonetel dispose
- EventsModule registered as @Global for cross-module injection
- Redpanda container added to VPS docker-compose
- Docker image rebuilt for linux/amd64 with kafkajs dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:38:02 +05:30
eb4000961f 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>
2026-03-24 22:04:31 +05:30
cec2526d37 feat: Phase 2 — missed call queue ingestion, auto-assignment, endpoints
- MissedQueueService: polls Ozonetel abandonCalls every 30s, dedup by phone
- Auto-assigns oldest PENDING_CALLBACK call on agent Ready (dispose + state change)
- GET /api/worklist/missed-queue, PATCH /api/worklist/missed-queue/:id/status
- Worklist query updated with callback fields and FIFO ordering
- PlatformGraphqlService.query() made public for server-to-server ops
- forwardRef circular dependency resolution between WorklistModule and OzonetelAgentModule

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:17:33 +05:30
4963a698d9 feat: agent state endpoint + search module
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:21:40 +05:30
8ba326589c feat: agent summary, AHT, and performance aggregation endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:41:05 +05:30
bbf77ed0e9 feat: call control, recording, CDR, missed calls, live call assist
- Call Control API (CONFERENCE/HOLD/MUTE/KICK_CALL)
- Recording pause/unpause
- Fetch CDR Detailed (call history with recordings)
- Abandon Calls (missed calls from Ozonetel)
- Call Assist WebSocket gateway (Deepgram STT + OpenAI suggestions)
- Call Assist service (lead context loading)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:36:35 +05:30
58225b7943 feat: webhook field fixes, Force Ready endpoint, improved error logging
- Fix Call record field names (recording, callerNumber, durationSec)
- Add POST /api/ozonetel/agent-ready using logout+login for Force Ready
- Add callerNumber to kookoo callback
- Better error logging with response body

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:22:47 +05:30
8c6cd2c156 feat: add Ozonetel Set Disposition API for proper ACW release
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 18:35:59 +05:30
6812006b53 feat: switch outbound dial to Kookoo API — outbound calls now work
- Replace CloudAgent V3 tbManualDial with Kookoo outbound.php
- Simple HTTP GET with api_key — no auth issues
- Kookoo callback endpoint: POST /webhooks/kookoo/callback
  - Creates Call record in platform
  - Matches caller to Lead by phone
- Remove agent login requirement before dial
- Tested: call queued successfully, phone rang

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:03:44 +05:30
a0df752cfd feat: Ozonetel V3 dial endpoint, worklist query fixes
- Switch outbound dial to /CAServicesV3/mdlConnection.php (tbManualDial)
- Use both Apikey + Basic Auth headers matching Ozonetel toolbar pattern
- Auto-login agent before dial attempt
- Dial controller returns 502 (not 401) for Ozonetel errors to prevent session logout
- Fix worklist: remove non-existent callId from followUp query
- Fix worklist: use unquoted MISSED enum, remove callerNumber subfield issue
- Worklist controller resolves agent name from platform currentUser API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:34:32 +05:30
a42d479f06 feat: wire sidecar to platform — auth proxy with workspace subdomain, GraphQL proxy, health check 2026-03-18 07:15:47 +05:30