From 644251295467aa83c7d6eb368cf0561fd31fd5b5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Feb 2026 20:02:53 +0000 Subject: [PATCH] perf: reduce hotspot test startup and timeout costs --- src/agents/bash-tools.e2e.test.ts | 4 +- src/discord/monitor/gateway-plugin.ts | 63 ++++++++++++++++++++++ src/discord/monitor/provider.proxy.test.ts | 8 +-- src/discord/monitor/provider.ts | 63 +--------------------- src/gateway/tools-invoke-http.test.ts | 38 ++++++------- test/gateway.multi.e2e.test.ts | 12 ++--- 6 files changed, 93 insertions(+), 95 deletions(-) create mode 100644 src/discord/monitor/gateway-plugin.ts diff --git a/src/agents/bash-tools.e2e.test.ts b/src/agents/bash-tools.e2e.test.ts index e8cd852b47..fa2adb4dc8 100644 --- a/src/agents/bash-tools.e2e.test.ts +++ b/src/agents/bash-tools.e2e.test.ts @@ -146,7 +146,7 @@ describe("exec tool backgrounding", () => { }); it("uses default timeout when timeout is omitted", async () => { - const customBash = createExecTool({ timeoutSec: 1, backgroundMs: 10 }); + const customBash = createExecTool({ timeoutSec: 0.2, backgroundMs: 10 }); const customProcess = createProcessTool(); const result = await customBash.execute("call1", { @@ -165,7 +165,7 @@ describe("exec tool backgrounding", () => { }); status = (poll.details as { status: string }).status; if (status === "running") { - await sleep(50); + await sleep(20); } } diff --git a/src/discord/monitor/gateway-plugin.ts b/src/discord/monitor/gateway-plugin.ts new file mode 100644 index 0000000000..ae4aea597b --- /dev/null +++ b/src/discord/monitor/gateway-plugin.ts @@ -0,0 +1,63 @@ +import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import WebSocket from "ws"; +import type { DiscordAccountConfig } from "../../config/types.js"; +import type { RuntimeEnv } from "../../runtime.js"; +import { danger } from "../../globals.js"; + +export function resolveDiscordGatewayIntents( + intentsConfig?: import("../../config/types.discord.js").DiscordIntentsConfig, +): number { + let intents = + GatewayIntents.Guilds | + GatewayIntents.GuildMessages | + GatewayIntents.MessageContent | + GatewayIntents.DirectMessages | + GatewayIntents.GuildMessageReactions | + GatewayIntents.DirectMessageReactions; + if (intentsConfig?.presence) { + intents |= GatewayIntents.GuildPresences; + } + if (intentsConfig?.guildMembers) { + intents |= GatewayIntents.GuildMembers; + } + return intents; +} + +export function createDiscordGatewayPlugin(params: { + discordConfig: DiscordAccountConfig; + runtime: RuntimeEnv; +}): GatewayPlugin { + const intents = resolveDiscordGatewayIntents(params.discordConfig?.intents); + const proxy = params.discordConfig?.proxy?.trim(); + const options = { + reconnect: { maxAttempts: 50 }, + intents, + autoInteractions: true, + }; + + if (!proxy) { + return new GatewayPlugin(options); + } + + try { + const agent = new HttpsProxyAgent(proxy); + + params.runtime.log?.("discord: gateway proxy enabled"); + + class ProxyGatewayPlugin extends GatewayPlugin { + constructor() { + super(options); + } + + createWebSocket(url: string) { + return new WebSocket(url, { agent }); + } + } + + return new ProxyGatewayPlugin(); + } catch (err) { + params.runtime.error?.(danger(`discord: invalid gateway proxy: ${String(err)}`)); + return new GatewayPlugin(options); + } +} diff --git a/src/discord/monitor/provider.proxy.test.ts b/src/discord/monitor/provider.proxy.test.ts index caed864629..b9a89e1132 100644 --- a/src/discord/monitor/provider.proxy.test.ts +++ b/src/discord/monitor/provider.proxy.test.ts @@ -50,7 +50,7 @@ describe("createDiscordGatewayPlugin", () => { }); it("uses proxy agent for gateway WebSocket when configured", async () => { - const { __testing } = await import("./provider.js"); + const { createDiscordGatewayPlugin } = await import("./gateway-plugin.js"); const { GatewayPlugin } = await import("@buape/carbon/gateway"); const runtime = { @@ -61,7 +61,7 @@ describe("createDiscordGatewayPlugin", () => { }), }; - const plugin = __testing.createDiscordGatewayPlugin({ + const plugin = createDiscordGatewayPlugin({ discordConfig: { proxy: "http://proxy.test:8080" }, runtime, }); @@ -82,7 +82,7 @@ describe("createDiscordGatewayPlugin", () => { }); it("falls back to the default gateway plugin when proxy is invalid", async () => { - const { __testing } = await import("./provider.js"); + const { createDiscordGatewayPlugin } = await import("./gateway-plugin.js"); const { GatewayPlugin } = await import("@buape/carbon/gateway"); const runtime = { @@ -93,7 +93,7 @@ describe("createDiscordGatewayPlugin", () => { }), }; - const plugin = __testing.createDiscordGatewayPlugin({ + const plugin = createDiscordGatewayPlugin({ discordConfig: { proxy: "bad-proxy" }, runtime, }); diff --git a/src/discord/monitor/provider.ts b/src/discord/monitor/provider.ts index b8233f18f4..e61627e155 100644 --- a/src/discord/monitor/provider.ts +++ b/src/discord/monitor/provider.ts @@ -1,12 +1,9 @@ +import type { GatewayPlugin } from "@buape/carbon/gateway"; import { Client, ReadyListener, type BaseMessageInteractiveComponent } from "@buape/carbon"; -import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway"; import { Routes } from "discord-api-types/v10"; -import { HttpsProxyAgent } from "https-proxy-agent"; import { inspect } from "node:util"; -import WebSocket from "ws"; import type { HistoryEntry } from "../../auto-reply/reply/history.js"; import type { OpenClawConfig, ReplyToMode } from "../../config/config.js"; -import type { DiscordAccountConfig } from "../../config/types.js"; import type { RuntimeEnv } from "../../runtime.js"; import { resolveTextChunkLimit } from "../../auto-reply/chunk.js"; import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js"; @@ -31,6 +28,7 @@ import { resolveDiscordUserAllowlist } from "../resolve-users.js"; import { normalizeDiscordToken } from "../token.js"; import { createAgentComponentButton, createAgentSelectMenu } from "./agent-components.js"; import { createExecApprovalButton, DiscordExecApprovalHandler } from "./exec-approvals.js"; +import { createDiscordGatewayPlugin } from "./gateway-plugin.js"; import { registerGateway, unregisterGateway } from "./gateway-registry.js"; import { DiscordMessageListener, @@ -57,44 +55,6 @@ export type MonitorDiscordOpts = { replyToMode?: ReplyToMode; }; -function createDiscordGatewayPlugin(params: { - discordConfig: DiscordAccountConfig; - runtime: RuntimeEnv; -}): GatewayPlugin { - const intents = resolveDiscordGatewayIntents(params.discordConfig?.intents); - const proxy = params.discordConfig?.proxy?.trim(); - const options = { - reconnect: { maxAttempts: 50 }, - intents, - autoInteractions: true, - }; - - if (!proxy) { - return new GatewayPlugin(options); - } - - try { - const agent = new HttpsProxyAgent(proxy); - - params.runtime.log?.("discord: gateway proxy enabled"); - - class ProxyGatewayPlugin extends GatewayPlugin { - constructor() { - super(options); - } - - createWebSocket(url: string) { - return new WebSocket(url, { agent }); - } - } - - return new ProxyGatewayPlugin(); - } catch (err) { - params.runtime.error?.(danger(`discord: invalid gateway proxy: ${String(err)}`)); - return new GatewayPlugin(options); - } -} - function summarizeAllowList(list?: Array) { if (!list || list.length === 0) { return "any"; @@ -164,25 +124,6 @@ function formatDiscordDeployErrorDetails(err: unknown): string { return details.length > 0 ? ` (${details.join(", ")})` : ""; } -function resolveDiscordGatewayIntents( - intentsConfig?: import("../../config/types.discord.js").DiscordIntentsConfig, -): number { - let intents = - GatewayIntents.Guilds | - GatewayIntents.GuildMessages | - GatewayIntents.MessageContent | - GatewayIntents.DirectMessages | - GatewayIntents.GuildMessageReactions | - GatewayIntents.DirectMessageReactions; - if (intentsConfig?.presence) { - intents |= GatewayIntents.GuildPresences; - } - if (intentsConfig?.guildMembers) { - intents |= GatewayIntents.GuildMembers; - } - return intents; -} - export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { const cfg = opts.config ?? loadConfig(); const account = resolveDiscordAccount({ diff --git a/src/gateway/tools-invoke-http.test.ts b/src/gateway/tools-invoke-http.test.ts index 98f047e4a1..0db60b7188 100644 --- a/src/gateway/tools-invoke-http.test.ts +++ b/src/gateway/tools-invoke-http.test.ts @@ -262,22 +262,20 @@ describe("POST /tools/invoke", () => { // oxlint-disable-next-line typescript/no-explicit-any } as any; - const port = await getFreePort(); - const server = await startGatewayServer(port, { bind: "loopback" }); const token = resolveGatewayToken(); - const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, { - method: "POST", - headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, - body: JSON.stringify({ tool: "sessions_spawn", args: { task: "test" }, sessionKey: "main" }), + const res = await invokeTool({ + port: sharedPort, + tool: "sessions_spawn", + args: { task: "test" }, + headers: { authorization: `Bearer ${token}` }, + sessionKey: "main", }); expect(res.status).toBe(404); const body = await res.json(); expect(body.ok).toBe(false); expect(body.error.type).toBe("not_found"); - - await server.close(); }); it("denies sessions_send via HTTP gateway", async () => { @@ -286,18 +284,16 @@ describe("POST /tools/invoke", () => { // oxlint-disable-next-line typescript/no-explicit-any } as any; - const port = await getFreePort(); - const server = await startGatewayServer(port, { bind: "loopback" }); const token = resolveGatewayToken(); - const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, { - method: "POST", - headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, - body: JSON.stringify({ tool: "sessions_send", args: {}, sessionKey: "main" }), + const res = await invokeTool({ + port: sharedPort, + tool: "sessions_send", + headers: { authorization: `Bearer ${token}` }, + sessionKey: "main", }); expect(res.status).toBe(404); - await server.close(); }); it("denies gateway tool via HTTP", async () => { @@ -306,18 +302,16 @@ describe("POST /tools/invoke", () => { // oxlint-disable-next-line typescript/no-explicit-any } as any; - const port = await getFreePort(); - const server = await startGatewayServer(port, { bind: "loopback" }); const token = resolveGatewayToken(); - const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, { - method: "POST", - headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, - body: JSON.stringify({ tool: "gateway", args: {}, sessionKey: "main" }), + const res = await invokeTool({ + port: sharedPort, + tool: "gateway", + headers: { authorization: `Bearer ${token}` }, + sessionKey: "main", }); expect(res.status).toBe(404); - await server.close(); }); it("uses the configured main session key when sessionKey is missing or main", async () => { diff --git a/test/gateway.multi.e2e.test.ts b/test/gateway.multi.e2e.test.ts index caafa416f6..e3a6b2383f 100644 --- a/test/gateway.multi.e2e.test.ts +++ b/test/gateway.multi.e2e.test.ts @@ -387,10 +387,8 @@ describe("gateway multi-instance e2e", () => { "spins up two gateways and exercises WS + HTTP + node pairing", { timeout: E2E_TIMEOUT_MS }, async () => { - const gwA = await spawnGatewayInstance("a"); - instances.push(gwA); - const gwB = await spawnGatewayInstance("b"); - instances.push(gwB); + const [gwA, gwB] = await Promise.all([spawnGatewayInstance("a"), spawnGatewayInstance("b")]); + instances.push(gwA, gwB); const [hookResA, hookResB] = await Promise.all([ postJson( @@ -415,8 +413,10 @@ describe("gateway multi-instance e2e", () => { expect(hookResB.status).toBe(200); expect((hookResB.json as { ok?: boolean } | undefined)?.ok).toBe(true); - const nodeA = await connectNode(gwA, "node-a"); - const nodeB = await connectNode(gwB, "node-b"); + const [nodeA, nodeB] = await Promise.all([ + connectNode(gwA, "node-a"), + connectNode(gwB, "node-b"), + ]); nodeClients.push(nodeA.client, nodeB.client); await Promise.all([