From 282b9fe6168bcdbf7491b6491240089fc9452b83 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 16:20:54 -0700 Subject: [PATCH] fix(feishu): split timeout env parsing from client --- CHANGELOG.md | 1 + extensions/feishu/src/client-timeout.ts | 42 +++++++++++++++++++++++++ extensions/feishu/src/client.ts | 42 +++++-------------------- 3 files changed, 50 insertions(+), 35 deletions(-) create mode 100644 extensions/feishu/src/client-timeout.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c956419706..9c89bbfdd0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai - Active Memory: keep non-empty `memory_search` results from being fast-failed as empty when debug telemetry reports zero hits. - Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc. - Plugins/install: allow official catalog-matched npm channel plugins such as Feishu to pass the trusted install scanner path while keeping spoofed package names blocked. Thanks @vincentkoc. +- Feishu: keep timeout env parsing separate from the HTTP client wrapper so package security scans no longer report a false env-harvesting hit during install. Thanks @vincentkoc. - Upgrade/config: validate configured web-search providers and statically suppressed model/provider pairs against the active plugin set at config load, so stale plugin state fails loud before runtime fallback. - Status/update: resolve beta update-channel checks from the installed version when config still says `stable`, and let `status --deep` reuse live gateway channel credential state instead of warning on command-path-only token misses. - Doctor/plugins: preserve unmanaged third-party plugin `node_modules` during `doctor --fix`, while still pruning OpenClaw-managed runtime dependency caches. diff --git a/extensions/feishu/src/client-timeout.ts b/extensions/feishu/src/client-timeout.ts new file mode 100644 index 00000000000..bc2c6260367 --- /dev/null +++ b/extensions/feishu/src/client-timeout.ts @@ -0,0 +1,42 @@ +import type { FeishuConfig } from "./types.js"; + +/** Default HTTP timeout for Feishu API requests (30 seconds). */ +export const FEISHU_HTTP_TIMEOUT_MS = 30_000; +export const FEISHU_HTTP_TIMEOUT_MAX_MS = 300_000; +export const FEISHU_HTTP_TIMEOUT_ENV_VAR = "OPENCLAW_FEISHU_HTTP_TIMEOUT_MS"; + +export type FeishuClientTimeoutConfig = { + httpTimeoutMs?: number; + config?: Pick; +}; + +export function resolveConfiguredHttpTimeoutMs(creds: FeishuClientTimeoutConfig): number { + const clampTimeout = (value: number): number => { + const rounded = Math.floor(value); + return Math.min(Math.max(rounded, 1), FEISHU_HTTP_TIMEOUT_MAX_MS); + }; + + const fromDirectField = creds.httpTimeoutMs; + if ( + typeof fromDirectField === "number" && + Number.isFinite(fromDirectField) && + fromDirectField > 0 + ) { + return clampTimeout(fromDirectField); + } + + const envRaw = process.env[FEISHU_HTTP_TIMEOUT_ENV_VAR]; + if (envRaw) { + const envValue = Number(envRaw); + if (Number.isFinite(envValue) && envValue > 0) { + return clampTimeout(envValue); + } + } + + const fromConfig = creds.config?.httpTimeoutMs; + const timeout = fromConfig; + if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) { + return FEISHU_HTTP_TIMEOUT_MS; + } + return clampTimeout(timeout); +} diff --git a/extensions/feishu/src/client.ts b/extensions/feishu/src/client.ts index d83424be8c4..8060096b1a2 100644 --- a/extensions/feishu/src/client.ts +++ b/extensions/feishu/src/client.ts @@ -5,6 +5,12 @@ import { readPluginPackageVersion, resolveAmbientNodeProxyAgent, } from "openclaw/plugin-sdk/extension-shared"; +import { + FEISHU_HTTP_TIMEOUT_ENV_VAR, + FEISHU_HTTP_TIMEOUT_MAX_MS, + FEISHU_HTTP_TIMEOUT_MS, + resolveConfiguredHttpTimeoutMs, +} from "./client-timeout.js"; import type { FeishuConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js"; const require = createRequire(import.meta.url); @@ -77,10 +83,7 @@ let feishuClientSdk: FeishuClientSdk = defaultFeishuClientSdk; } } -/** Default HTTP timeout for Feishu API requests (30 seconds). */ -export const FEISHU_HTTP_TIMEOUT_MS = 30_000; -export const FEISHU_HTTP_TIMEOUT_MAX_MS = 300_000; -export const FEISHU_HTTP_TIMEOUT_ENV_VAR = "OPENCLAW_FEISHU_HTTP_TIMEOUT_MS"; +export { FEISHU_HTTP_TIMEOUT_ENV_VAR, FEISHU_HTTP_TIMEOUT_MAX_MS, FEISHU_HTTP_TIMEOUT_MS }; type FeishuHttpInstanceLike = Pick< typeof feishuClientSdk.defaultHttpInstance, @@ -147,37 +150,6 @@ export type FeishuClientCredentials = { config?: Pick; }; -function resolveConfiguredHttpTimeoutMs(creds: FeishuClientCredentials): number { - const clampTimeout = (value: number): number => { - const rounded = Math.floor(value); - return Math.min(Math.max(rounded, 1), FEISHU_HTTP_TIMEOUT_MAX_MS); - }; - - const fromDirectField = creds.httpTimeoutMs; - if ( - typeof fromDirectField === "number" && - Number.isFinite(fromDirectField) && - fromDirectField > 0 - ) { - return clampTimeout(fromDirectField); - } - - const envRaw = process.env[FEISHU_HTTP_TIMEOUT_ENV_VAR]; - if (envRaw) { - const envValue = Number(envRaw); - if (Number.isFinite(envValue) && envValue > 0) { - return clampTimeout(envValue); - } - } - - const fromConfig = creds.config?.httpTimeoutMs; - const timeout = fromConfig; - if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) { - return FEISHU_HTTP_TIMEOUT_MS; - } - return clampTimeout(timeout); -} - /** * Create or get a cached Feishu client for an account. * Accepts any object with appId, appSecret, and optional domain/accountId.