fix(dashboard): resolver bugs críticos de tiempo real en HITL — race conditions, In-Band Auth y multi-stream buffer
- AppShell: corregir condición de carrera REST/WS que perdía tokens de agent_stream_chunk
- init_state atómico + eliminación de doble fuente REST/WS para actualización en tiempo real
- conversation_ended e idempotencia de eventos en máquina de estados por conversación
- Seguridad: migrar JWT de query param a In-Band Auth (primer mensaje {action:auth}) con timeout 5s y cierre 1008
- Multi-stream buffer: reemplazar buffer plano por TTL LRU (200 entradas, 60s TTL) para evitar pisado de tokens entre agentes
- agent_stream_completed ya no borra buffer incondicionalmente — delega purge a la política LRU
- Timer: corregir display de 00:00 en estado PENDING con visualización inmediata + cleanup en stop()
- Tests: 8 tests multi-stream, tests In-Band Auth, tests idempotencia y máquina de estados, tests Timer
- Resultado: 86/86 tests pasan | TypeScript 0 errores
This commit is contained in:
@@ -104,16 +104,6 @@ const Timer = forwardRef<TimerHandle, TimerProps>(({ caseId }, ref) => {
|
||||
}
|
||||
}, [caseId]);
|
||||
|
||||
// ── Cleanup on unmount ────────────────────────────────────
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ── Imperative API ────────────────────────────────────────
|
||||
|
||||
const start = useCallback(() => {
|
||||
@@ -128,6 +118,9 @@ const Timer = forwardRef<TimerHandle, TimerProps>(({ caseId }, ref) => {
|
||||
accumulated: accumulatedRef.current,
|
||||
});
|
||||
|
||||
// Mostrar valor actual INMEDIATAMENTE (sin esperar 1s al primer tick)
|
||||
setDisplaySeconds(accumulatedRef.current);
|
||||
|
||||
intervalRef.current = setInterval(() => {
|
||||
if (startTimestampRef.current === null) return;
|
||||
const elapsed = Math.floor(
|
||||
@@ -182,6 +175,21 @@ const Timer = forwardRef<TimerHandle, TimerProps>(({ caseId }, ref) => {
|
||||
getElapsed,
|
||||
]);
|
||||
|
||||
// ── Cleanup on unmount ────────────────────────────────────
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// 1. Primero: persistir estado vía stop() (ya es idempotente)
|
||||
stop();
|
||||
// 2. Segundo: anular el intervalo (por si stop no lo hizo)
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
// 3. Tercero: resetear bandera (para StrictMode)
|
||||
isRunningRef.current = false;
|
||||
};
|
||||
}, [stop]);
|
||||
|
||||
// ── Render ────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user