From 9ca426314793e657aed36a4e83b33f36eccebfc9 Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Tue, 24 Feb 2026 17:20:34 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20discord=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/api/chat/route.ts | 1 - apps/web/app/api/chat/stream/route.ts | 1 - src/agents/timeout.ts | 9 +++++---- src/commands/agent-via-gateway.ts | 5 +++-- src/discord/resolve-channels.test.ts | 27 +++++++++++++++++++++++++++ src/discord/resolve-channels.ts | 5 ++++- src/gateway/chat-abort.ts | 7 +++++++ 7 files changed, 46 insertions(+), 9 deletions(-) diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 8dfeb9d6be5..ac678a1a730 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -17,7 +17,6 @@ import { join } from "node:path"; import { resolveOpenClawStateDir } from "@/lib/workspace"; export const runtime = "nodejs"; -export const maxDuration = 600; function deriveSubagentInfo(sessionKey: string): { parentSessionId: string; task: string } | null { const registryPath = join(resolveOpenClawStateDir(), "subagents", "runs.json"); diff --git a/apps/web/app/api/chat/stream/route.ts b/apps/web/app/api/chat/stream/route.ts index e073c947314..b59d9737a92 100644 --- a/apps/web/app/api/chat/stream/route.ts +++ b/apps/web/app/api/chat/stream/route.ts @@ -19,7 +19,6 @@ import { join } from "node:path"; import { resolveOpenClawStateDir } from "@/lib/workspace"; export const runtime = "nodejs"; -export const maxDuration = 600; function deriveSubagentInfo(sessionKey: string): { parentSessionId: string; task: string } | null { const registryPath = join(resolveOpenClawStateDir(), "subagents", "runs.json"); diff --git a/src/agents/timeout.ts b/src/agents/timeout.ts index 6a1b5c3d838..be4aaaae92e 100644 --- a/src/agents/timeout.ts +++ b/src/agents/timeout.ts @@ -1,6 +1,6 @@ import type { OpenClawConfig } from "../config/config.js"; -const DEFAULT_AGENT_TIMEOUT_SECONDS = 1800; +const DEFAULT_AGENT_TIMEOUT_SECONDS = 0; const MAX_SAFE_TIMEOUT_MS = 2_147_000_000; const normalizeNumber = (value: unknown): number | undefined => @@ -9,7 +9,8 @@ const normalizeNumber = (value: unknown): number | undefined => export function resolveAgentTimeoutSeconds(cfg?: OpenClawConfig): number { const raw = normalizeNumber(cfg?.agents?.defaults?.timeoutSeconds); const seconds = raw ?? DEFAULT_AGENT_TIMEOUT_SECONDS; - return Math.max(seconds, 1); + // 0 means "no timeout"; only clamp positive values to >= 1. + return seconds === 0 ? 0 : Math.max(seconds, 1); } export function resolveAgentTimeoutMs(opts: { @@ -21,9 +22,9 @@ export function resolveAgentTimeoutMs(opts: { const minMs = Math.max(normalizeNumber(opts.minMs) ?? 1, 1); const clampTimeoutMs = (valueMs: number) => Math.min(Math.max(valueMs, minMs), MAX_SAFE_TIMEOUT_MS); - const defaultMs = clampTimeoutMs(resolveAgentTimeoutSeconds(opts.cfg) * 1000); - // Use the maximum timer-safe timeout to represent "no timeout" when explicitly set to 0. const NO_TIMEOUT_MS = MAX_SAFE_TIMEOUT_MS; + const defaultSeconds = resolveAgentTimeoutSeconds(opts.cfg); + const defaultMs = defaultSeconds === 0 ? NO_TIMEOUT_MS : clampTimeoutMs(defaultSeconds * 1000); const overrideMs = normalizeNumber(opts.overrideMs); if (overrideMs !== undefined) { if (overrideMs === 0) { diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index 98de24f2f63..e60a94f34ac 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -74,7 +74,7 @@ function parseTimeoutSeconds(opts: { cfg: ReturnType; timeout const raw = opts.timeout !== undefined ? Number.parseInt(String(opts.timeout), 10) - : (opts.cfg.agents?.defaults?.timeoutSeconds ?? 600); + : (opts.cfg.agents?.defaults?.timeoutSeconds ?? 0); if (Number.isNaN(raw) || raw < 0) { throw new Error("--timeout must be a non-negative integer (seconds; 0 means no timeout)"); } @@ -226,7 +226,8 @@ async function agentViaGatewayStreamJson(opts: AgentCliOpts, _runtime: RuntimeEn } } const timeoutSeconds = parseTimeoutSeconds({ cfg, timeout: opts.timeout }); - const gatewayTimeoutMs = Math.max(10_000, (timeoutSeconds + 30) * 1000); + const gatewayTimeoutMs = + timeoutSeconds === 0 ? NO_GATEWAY_TIMEOUT_MS : Math.max(10_000, (timeoutSeconds + 30) * 1000); const sessionKey = resolveSessionKeyForRequest({ cfg, diff --git a/src/discord/resolve-channels.test.ts b/src/discord/resolve-channels.test.ts index 89ca9b416f3..2056bdd6c0f 100644 --- a/src/discord/resolve-channels.test.ts +++ b/src/discord/resolve-channels.test.ts @@ -87,6 +87,33 @@ describe("resolveDiscordChannelAllowlist", () => { expect(res[0]?.channelId).toBeUndefined(); }); + it("resolves numeric guildId/channelId pair by ID", async () => { + const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => { + const url = urlToString(input); + if (url.endsWith("/users/@me/guilds")) { + return jsonResponse([{ id: "1468757251106934866", name: "Test Server" }]); + } + if (url.endsWith("/guilds/1468757251106934866/channels")) { + return jsonResponse([ + { id: "1475662965717078159", name: "bot-chat", guild_id: "1468757251106934866", type: 0 }, + { id: "9999", name: "general", guild_id: "1468757251106934866", type: 0 }, + ]); + } + return new Response("not found", { status: 404 }); + }); + + const res = await resolveDiscordChannelAllowlist({ + token: "test", + entries: ["1468757251106934866/1475662965717078159"], + fetcher, + }); + + expect(res[0]?.resolved).toBe(true); + expect(res[0]?.guildId).toBe("1468757251106934866"); + expect(res[0]?.channelId).toBe("1475662965717078159"); + expect(res[0]?.channelName).toBe("bot-chat"); + }); + it("bare numeric guild id is misrouted as channel id (regression)", async () => { // Demonstrates why provider.ts must prefix guild-only entries with "guild:" // In reality, Discord returns 404 when a guild ID is sent to /channels/, diff --git a/src/discord/resolve-channels.ts b/src/discord/resolve-channels.ts index 857b2e47533..c6cf7da83b9 100644 --- a/src/discord/resolve-channels.ts +++ b/src/discord/resolve-channels.ts @@ -230,8 +230,11 @@ export async function resolveDiscordChannelAllowlist(params: { continue; } const channels = await getChannels(guild.id); + const isIdQuery = /^\d+$/.test(channelQuery); const matches = channels.filter( - (channel) => normalizeDiscordSlug(channel.name) === normalizeDiscordSlug(channelQuery), + (channel) => + (isIdQuery && channel.id === channelQuery) || + normalizeDiscordSlug(channel.name) === normalizeDiscordSlug(channelQuery), ); const match = preferActiveMatch(matches); if (match) { diff --git a/src/gateway/chat-abort.ts b/src/gateway/chat-abort.ts index 0d544324133..200243cfa37 100644 --- a/src/gateway/chat-abort.ts +++ b/src/gateway/chat-abort.ts @@ -16,6 +16,8 @@ export function isChatStopCommandText(text: string): boolean { return trimmed.toLowerCase() === "/stop" || isAbortTrigger(trimmed); } +const NO_EXPIRATION_THRESHOLD_MS = 2_000_000_000; + export function resolveChatRunExpiresAtMs(params: { now: number; timeoutMs: number; @@ -23,6 +25,11 @@ export function resolveChatRunExpiresAtMs(params: { minMs?: number; maxMs?: number; }): number { + // "No timeout" runs (timeoutMs at or above the timer-safe max) never expire + // automatically; they can still be aborted via /stop or chat.abort. + if (params.timeoutMs >= NO_EXPIRATION_THRESHOLD_MS) { + return Number.MAX_SAFE_INTEGER; + } const { now, timeoutMs, graceMs = 60_000, minMs = 2 * 60_000, maxMs = 24 * 60 * 60_000 } = params; const boundedTimeoutMs = Math.max(0, timeoutMs); const target = now + boundedTimeoutMs + graceMs;