From a8144e9acfef981933c15e8d6ef04f37acf987bb Mon Sep 17 00:00:00 2001 From: DoubleSky123 Date: Mon, 1 Jun 2026 07:13:15 -0700 Subject: [PATCH] feat(frontend): make SSE idle timeout configurable via VIBE_TRADING_SSE_TIMEOUT (#157) The frontend safety timeout was hardcoded at 90 s, causing false "Execution timed out" toasts for users running slow local models (e.g. Ollama on limited VRAM or CPU). - Backend: expose `sse_timeout_seconds` in `LLMSettingsResponse`, reading from `VIBE_TRADING_SSE_TIMEOUT` env var (default 90 s) - Frontend: fetch the value from `/settings/llm` on mount and use it instead of the hardcoded 90 000 ms constant - `.env.example`: document the new variable in the Agent Tuning section Co-authored-by: Sky <985369746@qq.com> Co-authored-by: Claude Sonnet 4.6 --- agent/.env.example | 3 +++ agent/api_server.py | 2 ++ frontend/src/lib/api.ts | 1 + frontend/src/pages/Agent.tsx | 11 +++++++++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/agent/.env.example b/agent/.env.example index 585fd3aa..72255ea3 100644 --- a/agent/.env.example +++ b/agent/.env.example @@ -139,3 +139,6 @@ TUSHARE_TOKEN=your-tushare-token # SUBAGENT_TIMEOUT=300 # SUBAGENT_MAX_ITER=25 # TOKEN_THRESHOLD=40000 +# Frontend SSE idle timeout in seconds before showing "Execution timed out". +# Increase when using slow local models (e.g. Ollama on CPU/limited VRAM). +# VIBE_TRADING_SSE_TIMEOUT=90 diff --git a/agent/api_server.py b/agent/api_server.py index e7d79ce7..292f9b01 100644 --- a/agent/api_server.py +++ b/agent/api_server.py @@ -170,6 +170,7 @@ class LLMSettingsResponse(BaseModel): timeout_seconds: int max_retries: int reasoning_effort: str + sse_timeout_seconds: int env_path: str providers: List[LLMProviderOption] @@ -926,6 +927,7 @@ def _build_llm_settings_response(values: Optional[Dict[str, str]] = None) -> LLM timeout_seconds=_coerce_int(env_values.get("TIMEOUT_SECONDS", "120"), 120), max_retries=_coerce_int(env_values.get("MAX_RETRIES", "2"), 2), reasoning_effort=env_values.get("LANGCHAIN_REASONING_EFFORT", "").strip().lower(), + sse_timeout_seconds=_coerce_int(env_values.get("VIBE_TRADING_SSE_TIMEOUT", "90"), 90), env_path=_project_relative_path(ENV_PATH), providers=LLM_PROVIDERS, ) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9607d12f..4e03ba8d 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -232,6 +232,7 @@ export interface LLMSettings { timeout_seconds: number; max_retries: number; reasoning_effort: string; + sse_timeout_seconds: number; env_path: string; providers: LLMProviderOption[]; } diff --git a/frontend/src/pages/Agent.tsx b/frontend/src/pages/Agent.tsx index f1f411aa..fb743e15 100644 --- a/frontend/src/pages/Agent.tsx +++ b/frontend/src/pages/Agent.tsx @@ -212,6 +212,7 @@ export function Agent() { const pendingGoalSessionRef = useRef(null); const [showScrollBtn, setShowScrollBtn] = useState(false); const lastEventRef = useRef(0); + const sseTimeoutMsRef = useRef(90_000); /* tool_progress coalescing — keep latest payload per-tool, flush once per rAF. */ const pendingProgressRef = useRef>>(new Map()); @@ -709,11 +710,17 @@ export function Agent() { useEffect(() => () => doDisconnect(), [doDisconnect]); - /* Safety timeout: if streaming but no SSE event for 90s, reset to idle */ + useEffect(() => { + api.getLLMSettings().then((s) => { + sseTimeoutMsRef.current = s.sse_timeout_seconds * 1000; + }).catch(() => {}); + }, []); + + /* Safety timeout: if streaming but no SSE event for sseTimeoutMsRef.current ms, reset to idle */ useEffect(() => { if (status !== "streaming") return; const timer = setInterval(() => { - if (lastEventRef.current && Date.now() - lastEventRef.current > 90_000 && act().status === "streaming") { + if (lastEventRef.current && Date.now() - lastEventRef.current > sseTimeoutMsRef.current && act().status === "streaming") { act().setStatus("idle"); toast.warning("Execution timed out, automatically stopped"); }