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:
2026-07-29 04:32:27 -05:00
parent 8f044567c0
commit 83e3ec2cff
37 changed files with 6979 additions and 1129 deletions
+68
View File
@@ -0,0 +1,68 @@
import { useState, useEffect } from 'react';
import { auth } from '@/services/auth';
// ─────────────────────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────────────────────
export type AuthStatus = 'loading' | 'authenticated' | 'anonymous' | 'expired';
const CHECK_INTERVAL_MS = 30_000;
// ─────────────────────────────────────────────────────────────
// Hook
// ─────────────────────────────────────────────────────────────
/**
* Reactive hook that exposes the current authentication status.
*
* - 'loading': initial state while verifying session on mount
* - 'authenticated': valid session exists (token + expireDate valid)
* - 'anonymous': no session stored
* - 'expired': session exists but expireDate has passed
*/
export function useAuth() {
const [status, setStatus] = useState<AuthStatus>('loading');
useEffect(() => {
function checkAuth(): void {
const session = auth.getSession();
if (!session) {
setStatus('anonymous');
return;
}
// Check expireDate
const expireMs = new Date(session.expireDate).getTime();
if (isNaN(expireMs) || Date.now() >= expireMs) {
auth.logout();
setStatus('expired');
return;
}
// Verify token is still valid
const token = auth.getToken();
if (!token) {
setStatus('expired');
return;
}
setStatus('authenticated');
}
// Initial check
checkAuth();
// Periodic re-check every 30s to detect expiry
const interval = setInterval(checkAuth, CHECK_INTERVAL_MS);
return () => clearInterval(interval);
}, []);
return {
status,
isAuthenticated: status === 'authenticated',
isLoading: status === 'loading',
};
}