















@@ -0,0 +1,103 @@
1+/**
2+ * QQBot outbound response watchdog timeout resolver.
3+ *
4+ * Background — issue #85267:
5+ * The reporter ran openclaw + ollama + `qwen3.5:27b` (a slow local model)
6+ * with `models.providers.ollama.timeoutSeconds: 1800` and saw the
7+ * QQBot reply path abort at ~5 minutes with "LLM request timed out",
8+ * despite the direct ollama call to the same model working. The
9+ * embedded-runner / idle-timeout layer already honors longer
10+ * provider timeouts (see `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`),
11+ * but the QQBot outbound dispatcher held an independent hardcoded
12+ * `RESPONSE_TIMEOUT = 300_000` watchdog that quietly undercut the
13+ * configured ceiling.
14+ *
15+ * Fix shape (clawsweeper `clawsweeper:fix-shape-clear`):
16+ * Don't add a new QQBot-only knob. Instead derive the QQBot wait
17+ * budget from the existing agent/provider timeout settings the user
18+ * already configured:
19+ * - `agents.defaults.timeoutSeconds`
20+ * - `models.providers.<id>.timeoutSeconds` (max across configured providers)
21+ * Take the maximum and clamp to `[DEFAULT_RESPONSE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_MS]`.
22+ * The default floor preserves the existing 5-minute guard for users
23+ * that have not configured any longer ceiling — i.e. a no-op for
24+ * typical cloud-model deployments.
25+ */
26+27+/**
28+ * Default QQBot outbound response watchdog when no config override is
29+ * present. Preserves the historical 5-minute guard for unconfigured
30+ * deployments.
31+ */
32+export const DEFAULT_RESPONSE_TIMEOUT_MS = 300_000;
33+34+/**
35+ * Upper bound to keep the watchdog inside the safe `setTimeout` range
36+ * (approximately 24.8 days). Mirrors `MAX_SAFE_TIMEOUT_MS` in
37+ * `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`.
38+ */
39+const MAX_SAFE_TIMEOUT_MS = 2_147_000_000;
40+41+interface AgentsDefaultsLike {
42+timeoutSeconds?: unknown;
43+}
44+45+interface AgentsBlockLike {
46+defaults?: AgentsDefaultsLike;
47+}
48+49+interface ProviderEntryLike {
50+timeoutSeconds?: unknown;
51+}
52+53+interface ModelsBlockLike {
54+providers?: Record<string, ProviderEntryLike | undefined> | undefined;
55+}
56+57+interface CfgShape {
58+agents?: AgentsBlockLike;
59+models?: ModelsBlockLike;
60+}
61+62+function positiveSecondsToMs(value: unknown): number | undefined {
63+if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
64+return undefined;
65+}
66+return Math.floor(value * 1000);
67+}
68+69+/**
70+ * Resolve the QQBot outbound response watchdog (ms).
71+ *
72+ * The watchdog is the longest of:
73+ * - `DEFAULT_RESPONSE_TIMEOUT_MS` (5 min, historical floor)
74+ * - `cfg.agents.defaults.timeoutSeconds` converted to ms
75+ * - the maximum `cfg.models.providers.<id>.timeoutSeconds` across
76+ * configured providers, converted to ms
77+ *
78+ * Returns at most `MAX_SAFE_TIMEOUT_MS` so the chosen value is always
79+ * a safe `setTimeout` argument.
80+ */
81+export function resolveResponseTimeoutMs(cfg: unknown): number {
82+const candidates: number[] = [DEFAULT_RESPONSE_TIMEOUT_MS];
83+84+const typed = (cfg ?? {}) as CfgShape;
85+86+const agentDefaultMs = positiveSecondsToMs(typed.agents?.defaults?.timeoutSeconds);
87+if (agentDefaultMs !== undefined) {
88+candidates.push(agentDefaultMs);
89+}
90+91+const providers = typed.models?.providers;
92+if (providers && typeof providers === "object") {
93+for (const entry of Object.values(providers)) {
94+const providerMs = positiveSecondsToMs(entry?.timeoutSeconds);
95+if (providerMs !== undefined) {
96+candidates.push(providerMs);
97+}
98+}
99+}
100+101+const chosen = Math.max(...candidates);
102+return Math.min(chosen, MAX_SAFE_TIMEOUT_MS);
103+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。