diff --git a/src/main/ipc.ts b/src/main/ipc.ts index e6b3b4a..2ca4386 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -262,10 +262,10 @@ export async function setupIPC(): Promise { }); ipcMain.handle('tool:setTimeouts', (_, timeouts: { http?: number; mcp?: number }) => { - if (typeof timeouts.http === 'number' && timeouts.http > 0) { + if (typeof timeouts.http === 'number' && timeouts.http >= 0) { setHTTPTimeout(timeouts.http); } - if (typeof timeouts.mcp === 'number' && timeouts.mcp > 0) { + if (typeof timeouts.mcp === 'number' && timeouts.mcp >= 0) { setMCPTimeout(timeouts.mcp); } return { success: true }; diff --git a/src/main/mcp-manager.ts b/src/main/mcp-manager.ts index c0cc884..3423a87 100644 --- a/src/main/mcp-manager.ts +++ b/src/main/mcp-manager.ts @@ -42,7 +42,7 @@ interface MCPServerInstance { config: { name: string; command: string; args: string[]; enabled: boolean; description?: string }; process: ChildProcess; requestId: number; - pendingRequests: Map void; reject: (reason: Error) => void; timer: ReturnType }>; + pendingRequests: Map void; reject: (reason: Error) => void; timer: ReturnType | null }>; buffer: string; tools: MCPTool[]; initialized: boolean; @@ -55,9 +55,9 @@ const servers = new Map(); /** MCP 请求超时(毫秒),默认 60s,可通过 IPC 设置 */ let MCP_TIMEOUT = 60_000; -/** 设置 MCP 请求超时(毫秒) */ +/** 设置 MCP 请求超时(毫秒),0=禁用超时 */ export function setMCPTimeout(ms: number): void { - MCP_TIMEOUT = Math.max(10_000, ms); + MCP_TIMEOUT = ms === 0 ? 0 : Math.max(10_000, ms); } // ─── JSON-RPC 通信 ─── @@ -67,10 +67,10 @@ function sendRequest(server: MCPServerInstance, method: string, params?: Record< const id = ++server.requestId; const request: JSONRPCRequest = { jsonrpc: '2.0', id, method, params }; - const timer = setTimeout(() => { + const timer = MCP_TIMEOUT > 0 ? setTimeout(() => { server.pendingRequests.delete(id); reject(new Error(`MCP 请求超时: ${server.config.name} / ${method} (${MCP_TIMEOUT}ms)`)); - }, MCP_TIMEOUT); + }, MCP_TIMEOUT) : null; server.pendingRequests.set(id, { resolve, reject, timer }); @@ -79,7 +79,7 @@ function sendRequest(server: MCPServerInstance, method: string, params?: Record< server.process.stdin!.write(line); } catch (err) { server.pendingRequests.delete(id); - clearTimeout(timer); + if (timer) clearTimeout(timer); reject(new Error(`MCP 写入失败: ${(err as Error).message}`)); } }); @@ -89,7 +89,7 @@ function handleResponse(server: MCPServerInstance, response: JSONRPCResponse): v const pending = server.pendingRequests.get(response.id); if (!pending) return; - clearTimeout(pending.timer); + if (pending.timer) clearTimeout(pending.timer); server.pendingRequests.delete(response.id); if (response.error) { @@ -201,7 +201,7 @@ export async function startServer(config: { name: string; command: string; args: function cleanupServer(server: MCPServerInstance): void { for (const [, pending] of server.pendingRequests) { - clearTimeout(pending.timer); + if (pending.timer) clearTimeout(pending.timer); pending.reject(new Error('MCP 服务器已关闭')); } server.pendingRequests.clear(); diff --git a/src/main/tool-handlers-system.ts b/src/main/tool-handlers-system.ts index 736647e..caed0f2 100644 --- a/src/main/tool-handlers-system.ts +++ b/src/main/tool-handlers-system.ts @@ -127,9 +127,9 @@ export function killToolProcess(): boolean { /** HTTP 请求超时(毫秒),默认 30s,可通过 IPC 设置 */ let HTTP_TIMEOUT = 30_000; -/** 设置 HTTP 请求超时(毫秒) */ +/** 设置 HTTP 请求超时(毫秒),0=禁用超时 */ export function setHTTPTimeout(ms: number): void { - HTTP_TIMEOUT = Math.max(5000, ms); + HTTP_TIMEOUT = ms === 0 ? 0 : Math.max(5000, ms); } // ── LRU 搜索缓存 ────────────────────────────────── @@ -233,10 +233,17 @@ function buildFetchHeaders(url: string, attemptIndex: number, useMobileUA: boole }; } -/** 带超时的 fetch 封装 */ +/** 带超时的 fetch 封装,timeout=0 表示不超时 */ async function fetchWithTimeout(url: string, timeout = HTTP_TIMEOUT, headers?: Record, attemptIndex = 0): Promise { const _headers = headers || buildFetchHeaders(url, attemptIndex); + if (timeout <= 0) { + // 0 = 禁用超时 + try { + return await fetch(url, { headers: _headers, redirect: 'follow' }); + } catch { return null; } + } + const controller = new AbortController(); const tid = setTimeout(() => controller.abort(), timeout); try { @@ -444,13 +451,18 @@ export async function handleWebFetch(params: { url: string; max_chars?: number; const headers = buildFetchHeaders(url, attempt, useMobileUA); try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), HTTP_TIMEOUT); let resp: Response; - try { - resp = await fetch(url, { headers, signal: controller.signal, redirect: 'follow' }); - } finally { - clearTimeout(timeoutId); + if (HTTP_TIMEOUT > 0) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), HTTP_TIMEOUT); + try { + resp = await fetch(url, { headers, signal: controller.signal, redirect: 'follow' }); + } finally { + clearTimeout(timeoutId); + } + } else { + // 0 = 禁用超时 + resp = await fetch(url, { headers, redirect: 'follow' }); } if (!resp.ok) { @@ -566,7 +578,7 @@ export async function handleWebFetch(params: { url: string; max_chars?: number; } catch (err) { const errMsg = (err as Error).message; if (errMsg.includes('abort') && attempt < maxAttempts - 1) { - lastError = `请求超时 (${HTTP_TIMEOUT / 1000}s)(将重试)`; + lastError = `请求超时 (${HTTP_TIMEOUT > 0 ? HTTP_TIMEOUT / 1000 : '∞'}s)(将重试)`; continue; } if (errMsg.includes('abort')) { diff --git a/src/renderer/services/agent-engine.ts b/src/renderer/services/agent-engine.ts index ceeed21..80d3de7 100644 --- a/src/renderer/services/agent-engine.ts +++ b/src/renderer/services/agent-engine.ts @@ -1336,12 +1336,21 @@ async function handleThinking( const abortController = registerAbortController(); - // ── 流式超时保护 — 动态计算:基础 120s + 上下文 token 数 * 0.5s/1K tokens ── + // ── 流式超时保护 — 0=禁用, 正数=自定义, -1/未设置=动态计算 ── const baseTimeout = state.get('streamTimeout', 0); const numCtxForTimeout = getEffectiveNumCtx(); - const dynamicTimeout = baseTimeout > 0 ? baseTimeout : Math.max(120_000, 120_000 + Math.floor(numCtxForTimeout / 1000) * 500); - // 上限 10 分钟,避免极端值 - const STREAM_TIMEOUT_MS = Math.min(dynamicTimeout, 600_000); + let STREAM_TIMEOUT_MS: number; + if (baseTimeout === 0) { + // 0 = 禁用流式超时 + STREAM_TIMEOUT_MS = 0; + } else if (baseTimeout > 0) { + // 用户自定义超时,上限 10 分钟 + STREAM_TIMEOUT_MS = Math.min(baseTimeout, 600_000); + } else { + // -1 或未设置 = 动态计算:基础 120s + 上下文 token 数 * 0.5s/1K tokens + const dynamicTimeout = Math.max(120_000, 120_000 + Math.floor(numCtxForTimeout / 1000) * 500); + STREAM_TIMEOUT_MS = Math.min(dynamicTimeout, 600_000); + } let totalTimer: ReturnType | null = null;