Agent Office
- ● {connected ? 'Connected' : 'Disconnected'}
+ ● {statusText}
diff --git a/src/pages/agent-office/hooks/useAgentManager.js b/src/pages/agent-office/hooks/useAgentManager.js
index 70b35ae..436104b 100644
--- a/src/pages/agent-office/hooks/useAgentManager.js
+++ b/src/pages/agent-office/hooks/useAgentManager.js
@@ -1,25 +1,34 @@
// src/pages/agent-office/hooks/useAgentManager.js
import { useState, useEffect, useRef, useCallback } from 'react';
-const WS_RECONNECT_DELAY = 3000;
+// Exponential backoff with cap. 1s → 2s → 4s → 8s → 16s → 30s (cap)
+const WS_BACKOFF_BASE_MS = 1000;
+const WS_BACKOFF_CAP_MS = 30000;
+const WS_BACKOFF_MAX_EXP = 5;
export function useAgentManager() {
const [agents, setAgents] = useState({}); // { agentId: { state, detail, task_id } }
const [pendingTasks, setPendingTasks] = useState([]); // [{id, agent_id, task_type, input_data}]
const [notifications, setNotifications] = useState({}); // { agentId: count }
const [connected, setConnected] = useState(false);
+ const [reconnectAttempt, setReconnectAttempt] = useState(0);
const [refreshTrigger, setRefreshTrigger] = useState(0); // 탭 데이터 리프레시용
const wsRef = useRef(null);
const reconnectRef = useRef(null);
const connectRef = useRef(null);
+ const attemptRef = useRef(0);
const connect = useCallback(() => {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const ws = new WebSocket(`${protocol}://${window.location.host}/api/agent-office/ws`);
wsRef.current = ws;
- ws.onopen = () => setConnected(true);
+ ws.onopen = () => {
+ setConnected(true);
+ attemptRef.current = 0;
+ setReconnectAttempt(0);
+ };
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
@@ -69,10 +78,17 @@ export function useAgentManager() {
ws.onclose = () => {
setConnected(false);
- reconnectRef.current = setTimeout(() => connectRef.current?.(), WS_RECONNECT_DELAY);
+ const exp = Math.min(attemptRef.current, WS_BACKOFF_MAX_EXP);
+ const delay = Math.min(WS_BACKOFF_BASE_MS * 2 ** exp, WS_BACKOFF_CAP_MS);
+ attemptRef.current += 1;
+ setReconnectAttempt(attemptRef.current);
+ reconnectRef.current = setTimeout(() => connectRef.current?.(), delay);
};
- ws.onerror = () => ws.close();
+ // onerror fires before onclose; swallow so the browser doesn't print an
+ // unhandled-error pair for every retry. onclose still runs and schedules
+ // the next attempt.
+ ws.onerror = () => {};
}, []);
useEffect(() => {
@@ -108,6 +124,7 @@ export function useAgentManager() {
pendingTasks,
notifications,
connected,
+ reconnectAttempt,
refreshTrigger,
sendCommand,
sendApproval,