From 6b0d6e25405b98f29cbdd0a8b77674b3c294ee73 Mon Sep 17 00:00:00 2001 From: cpojer Date: Mon, 2 Feb 2026 21:34:47 +0900 Subject: [PATCH] chore: We have a sleep at home. The sleep at home: --- .../bash-tools.exec.background-abort.test.ts | 3 +-- .../bash-tools.process.send-keys.test.ts | 7 +++-- src/agents/bash-tools.test.ts | 3 +-- src/agents/claude-cli-runner.test.ts | 3 ++- src/agents/openclaw-tools.sessions.test.ts | 17 ++++++++---- ...ounces-agent-wait-lifecycle-events.test.ts | 7 ++--- ...n-normalizes-allowlisted-agent-ids.test.ts | 27 +++++++++++++------ ...resolves-main-announce-target-from.test.ts | 14 +++++++--- src/auto-reply/reply/reply-dispatcher.ts | 4 +-- src/channels/ack-reactions.test.ts | 7 ++--- src/cli/ports.ts | 5 +--- src/config/sessions.test.ts | 2 +- src/discord/monitor.test.ts | 3 ++- .../server.roles-allowlist-update.e2e.test.ts | 3 +-- src/infra/retry.ts | 4 +-- src/slack/monitor/media.ts | 3 ++- ...gent-mention-patterns-group-gating.test.ts | 3 ++- src/web/auto-reply/deliver-reply.ts | 3 +-- 18 files changed, 69 insertions(+), 49 deletions(-) diff --git a/src/agents/bash-tools.exec.background-abort.test.ts b/src/agents/bash-tools.exec.background-abort.test.ts index a3d69f327..949999de2 100644 --- a/src/agents/bash-tools.exec.background-abort.test.ts +++ b/src/agents/bash-tools.exec.background-abort.test.ts @@ -1,4 +1,5 @@ import { afterEach, expect, test } from "vitest"; +import { sleep } from "../utils.ts"; import { getFinishedSession, getSession, @@ -7,8 +8,6 @@ import { import { createExecTool } from "./bash-tools.exec"; import { killProcessTree } from "./shell-utils"; -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - afterEach(() => { resetProcessRegistryForTests(); }); diff --git a/src/agents/bash-tools.process.send-keys.test.ts b/src/agents/bash-tools.process.send-keys.test.ts index d12adbb28..d93715e60 100644 --- a/src/agents/bash-tools.process.send-keys.test.ts +++ b/src/agents/bash-tools.process.send-keys.test.ts @@ -1,10 +1,9 @@ import { afterEach, expect, test } from "vitest"; +import { sleep } from "../utils"; import { resetProcessRegistryForTests } from "./bash-process-registry"; import { createExecTool } from "./bash-tools.exec"; import { createProcessTool } from "./bash-tools.process"; -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - afterEach(() => { resetProcessRegistryForTests(); }); @@ -31,7 +30,7 @@ test("process send-keys encodes Enter for pty sessions", async () => { const deadline = Date.now() + (process.platform === "win32" ? 4000 : 2000); while (Date.now() < deadline) { - await wait(50); + await sleep(50); const poll = await processTool.execute("toolcall", { action: "poll", sessionId }); const details = poll.details as { status?: string; aggregated?: string }; if (details.status !== "running") { @@ -65,7 +64,7 @@ test("process submit sends Enter for pty sessions", async () => { const deadline = Date.now() + (process.platform === "win32" ? 4000 : 2000); while (Date.now() < deadline) { - await wait(50); + await sleep(50); const poll = await processTool.execute("toolcall", { action: "poll", sessionId }); const details = poll.details as { status?: string; aggregated?: string }; if (details.status !== "running") { diff --git a/src/agents/bash-tools.test.ts b/src/agents/bash-tools.test.ts index f4f2a938c..1cb0caf35 100644 --- a/src/agents/bash-tools.test.ts +++ b/src/agents/bash-tools.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js"; +import { sleep } from "../utils.js"; import { getFinishedSession, resetProcessRegistryForTests } from "./bash-process-registry.js"; import { createExecTool, createProcessTool, execTool, processTool } from "./bash-tools.js"; import { buildDockerExecArgs } from "./bash-tools.shared.js"; @@ -45,8 +46,6 @@ const normalizeText = (value?: string) => .join("\n") .trim(); -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - async function waitForCompletion(sessionId: string) { let status = "running"; const deadline = Date.now() + (process.platform === "win32" ? 8000 : 2000); diff --git a/src/agents/claude-cli-runner.test.ts b/src/agents/claude-cli-runner.test.ts index 5cf82fe1e..587a13ff2 100644 --- a/src/agents/claude-cli-runner.test.ts +++ b/src/agents/claude-cli-runner.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { sleep } from "../utils.js"; import { runClaudeCliAgent } from "./claude-cli-runner.js"; const runCommandWithTimeoutMock = vi.fn(); @@ -22,7 +23,7 @@ async function waitForCalls(mockFn: { mock: { calls: unknown[][] } }, count: num if (mockFn.mock.calls.length >= count) { return; } - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); } throw new Error(`Expected ${count} calls, got ${mockFn.mock.calls.length}`); } diff --git a/src/agents/openclaw-tools.sessions.test.ts b/src/agents/openclaw-tools.sessions.test.ts index d486780cd..aaaf31fe3 100644 --- a/src/agents/openclaw-tools.sessions.test.ts +++ b/src/agents/openclaw-tools.sessions.test.ts @@ -21,6 +21,7 @@ vi.mock("../config/config.js", async (importOriginal) => { }); import "./test-helpers/fast-core-tools.js"; +import { sleep } from "../utils.js"; import { createOpenClawTools } from "./openclaw-tools.js"; const waitForCalls = async (getCount: () => number, count: number, timeoutMs = 2000) => { @@ -29,7 +30,7 @@ const waitForCalls = async (getCount: () => number, count: number, timeoutMs = 2 if (Date.now() - start > timeoutMs) { throw new Error(`timed out waiting for ${count} calls`); } - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); } }; @@ -185,7 +186,10 @@ describe("sessions tools", () => { const sessionId = "sess-group"; const targetKey = "agent:main:discord:channel:1457165743010611293"; callGatewayMock.mockImplementation(async (opts: unknown) => { - const request = opts as { method?: string; params?: Record }; + const request = opts as { + method?: string; + params?: Record; + }; if (request.method === "sessions.resolve") { return { key: targetKey, @@ -388,7 +392,10 @@ describe("sessions tools", () => { const sessionId = "sess-send"; const targetKey = "agent:main:discord:channel:123"; callGatewayMock.mockImplementation(async (opts: unknown) => { - const request = opts as { method?: string; params?: Record }; + const request = opts as { + method?: string; + params?: Record; + }; if (request.method === "sessions.resolve") { return { key: targetKey }; } @@ -514,8 +521,8 @@ describe("sessions tools", () => { status: "ok", reply: "initial", }); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); + await sleep(0); const agentCalls = calls.filter((call) => call.method === "agent"); expect(agentCalls).toHaveLength(4); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts index 78d656dc3..c4ee75fe6 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts @@ -22,6 +22,7 @@ vi.mock("../config/config.js", async (importOriginal) => { }); import "./test-helpers/fast-core-tools.js"; +import { sleep } from "../utils.js"; import { createOpenClawTools } from "./openclaw-tools.js"; import { resetSubagentRegistryForTests } from "./subagent-registry.js"; @@ -107,9 +108,9 @@ describe("openclaw-tools: subagents", () => { runId: "run-1", }); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); + await sleep(0); + await sleep(0); const childWait = waitCalls.find((call) => call.runId === childRunId); expect(childWait?.timeoutMs).toBe(1000); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts index e2a2cd86c..00d622dec 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts @@ -23,6 +23,7 @@ vi.mock("../config/config.js", async (importOriginal) => { import { emitAgentEvent } from "../infra/agent-events.js"; import "./test-helpers/fast-core-tools.js"; +import { sleep } from "../utils.js"; import { createOpenClawTools } from "./openclaw-tools.js"; import { resetSubagentRegistryForTests } from "./subagent-registry.js"; @@ -165,7 +166,12 @@ describe("openclaw-tools: subagents", () => { if (request.method === "agent.wait") { const params = request.params as { runId?: string; timeoutMs?: number } | undefined; waitCalls.push(params ?? {}); - return { runId: params?.runId ?? "run-1", status: "ok", startedAt: 1000, endedAt: 2000 }; + return { + runId: params?.runId ?? "run-1", + status: "ok", + startedAt: 1000, + endedAt: 2000, + }; } if (request.method === "sessions.delete") { const params = request.params as { key?: string } | undefined; @@ -206,9 +212,9 @@ describe("openclaw-tools: subagents", () => { }, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); + await sleep(0); + await sleep(0); const childWait = waitCalls.find((call) => call.runId === childRunId); expect(childWait?.timeoutMs).toBe(1000); @@ -272,7 +278,12 @@ describe("openclaw-tools: subagents", () => { } if (request.method === "agent.wait") { const params = request.params as { runId?: string; timeoutMs?: number } | undefined; - return { runId: params?.runId ?? "run-1", status: "ok", startedAt: 1000, endedAt: 2000 }; + return { + runId: params?.runId ?? "run-1", + status: "ok", + startedAt: 1000, + endedAt: 2000, + }; } if (request.method === "sessions.delete" || request.method === "sessions.patch") { return { ok: true }; @@ -312,9 +323,9 @@ describe("openclaw-tools: subagents", () => { }, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); + await sleep(0); + await sleep(0); const agentCalls = calls.filter((call) => call.method === "agent"); expect(agentCalls).toHaveLength(2); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts index d261e9ae3..30c32aff1 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { sleep } from "../utils.ts"; const callGatewayMock = vi.fn(); vi.mock("../gateway/call.js", () => ({ @@ -82,7 +83,12 @@ describe("openclaw-tools: subagents", () => { if (request.method === "agent.wait") { const params = request.params as { runId?: string; timeoutMs?: number } | undefined; waitCalls.push(params ?? {}); - return { runId: params?.runId ?? "run-1", status: "ok", startedAt: 1000, endedAt: 2000 }; + return { + runId: params?.runId ?? "run-1", + status: "ok", + startedAt: 1000, + endedAt: 2000, + }; } if (request.method === "sessions.patch") { const params = request.params as { key?: string; label?: string } | undefined; @@ -126,9 +132,9 @@ describe("openclaw-tools: subagents", () => { }, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); + await sleep(0); + await sleep(0); const childWait = waitCalls.find((call) => call.runId === childRunId); expect(childWait?.timeoutMs).toBe(1000); diff --git a/src/auto-reply/reply/reply-dispatcher.ts b/src/auto-reply/reply/reply-dispatcher.ts index 52b363797..be505a8bc 100644 --- a/src/auto-reply/reply/reply-dispatcher.ts +++ b/src/auto-reply/reply/reply-dispatcher.ts @@ -2,6 +2,7 @@ import type { HumanDelayConfig } from "../../config/types.js"; import type { GetReplyOptions, ReplyPayload } from "../types.js"; import type { ResponsePrefixContext } from "./response-prefix-template.js"; import type { TypingController } from "./typing.js"; +import { sleep } from "../../utils.js"; import { normalizeReplyPayload, type NormalizeReplySkipReason } from "./normalize-reply.js"; export type ReplyDispatchKind = "tool" | "block" | "final"; @@ -37,9 +38,6 @@ function getHumanDelay(config: HumanDelayConfig | undefined): number { return Math.floor(Math.random() * (max - min + 1)) + min; } -/** Sleep for a given number of milliseconds. */ -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - export type ReplyDispatcherOptions = { deliver: ReplyDispatchDeliverer; responsePrefix?: string; diff --git a/src/channels/ack-reactions.test.ts b/src/channels/ack-reactions.test.ts index 862dff9f2..bd8698ef1 100644 --- a/src/channels/ack-reactions.test.ts +++ b/src/channels/ack-reactions.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { sleep } from "../utils.ts"; import { removeAckReactionAfterReply, shouldAckReaction, @@ -237,7 +238,7 @@ describe("removeAckReactionAfterReply", () => { remove, onError, }); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); expect(remove).toHaveBeenCalledTimes(1); expect(onError).not.toHaveBeenCalled(); }); @@ -250,7 +251,7 @@ describe("removeAckReactionAfterReply", () => { ackReactionValue: "👀", remove, }); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); expect(remove).not.toHaveBeenCalled(); }); @@ -262,7 +263,7 @@ describe("removeAckReactionAfterReply", () => { ackReactionValue: "👀", remove, }); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); expect(remove).not.toHaveBeenCalled(); }); }); diff --git a/src/cli/ports.ts b/src/cli/ports.ts index c12ea1669..ab5a39799 100644 --- a/src/cli/ports.ts +++ b/src/cli/ports.ts @@ -1,5 +1,6 @@ import { execFileSync } from "node:child_process"; import { resolveLsofCommandSync } from "../infra/ports-lsof.js"; +import { sleep } from "../utils.js"; export type PortProcess = { pid: number; command?: string }; @@ -9,10 +10,6 @@ export type ForceFreePortResult = { escalatedToSigkill: boolean; }; -function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - export function parseLsofOutput(output: string): PortProcess[] { const lines = output.split(/\r?\n/).filter(Boolean); const results: PortProcess[] = []; diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index 1a0d20435..345a30644 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { sleep } from "../utils.js"; import { buildGroupDisplayName, deriveSessionKey, @@ -428,7 +429,6 @@ describe("sessions", () => { "utf-8", ); - const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); await Promise.all([ updateSessionStoreEntry({ storePath, diff --git a/src/discord/monitor.test.ts b/src/discord/monitor.test.ts index 32545b0b6..2644b4095 100644 --- a/src/discord/monitor.test.ts +++ b/src/discord/monitor.test.ts @@ -1,5 +1,6 @@ import type { Guild } from "@buape/carbon"; import { describe, expect, it, vi } from "vitest"; +import { sleep } from "../utils.js"; import { allowListMatches, buildDiscordMediaPayload, @@ -88,7 +89,7 @@ describe("DiscordMessageListener", () => { {} as unknown as import("./monitor/listeners.js").DiscordMessageEvent, {} as unknown as import("@buape/carbon").Client, ); - await new Promise((resolve) => setTimeout(resolve, 0)); + await sleep(0); expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("discord handler failed")); }); diff --git a/src/gateway/server.roles-allowlist-update.e2e.test.ts b/src/gateway/server.roles-allowlist-update.e2e.test.ts index 406ba342c..aa3cf213b 100644 --- a/src/gateway/server.roles-allowlist-update.e2e.test.ts +++ b/src/gateway/server.roles-allowlist-update.e2e.test.ts @@ -16,6 +16,7 @@ vi.mock("../infra/update-runner.js", () => ({ })), })); +import { sleep } from "../utils.js"; import { connectOk, installGatewayTestHooks, @@ -43,8 +44,6 @@ afterAll(async () => { await server.close(); }); -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - const connectNodeClient = async (params: { port: number; commands: string[]; diff --git a/src/infra/retry.ts b/src/infra/retry.ts index dff51fd49..8d4e794ac 100644 --- a/src/infra/retry.ts +++ b/src/infra/retry.ts @@ -1,3 +1,5 @@ +import { sleep } from "../utils.js"; + export type RetryConfig = { attempts?: number; minDelayMs?: number; @@ -27,8 +29,6 @@ const DEFAULT_RETRY_CONFIG = { jitter: 0, }; -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - const asFiniteNumber = (value: unknown): number | undefined => typeof value === "number" && Number.isFinite(value) ? value : undefined; diff --git a/src/slack/monitor/media.ts b/src/slack/monitor/media.ts index 31be4f200..c3d6f8aa4 100644 --- a/src/slack/monitor/media.ts +++ b/src/slack/monitor/media.ts @@ -54,7 +54,8 @@ function resolveRequestUrl(input: RequestInfo | URL): string { if ("url" in input && typeof input.url === "string") { return input.url; } - return String(input); + + throw new Error(`Unable to resolve request URL from input: ${JSON.stringify(input, null, 2)}`); } function createSlackMediaFetch(token: string): FetchLike { diff --git a/src/web/auto-reply.web-auto-reply.uses-per-agent-mention-patterns-group-gating.test.ts b/src/web/auto-reply.web-auto-reply.uses-per-agent-mention-patterns-group-gating.test.ts index 80d63055e..e2d88e605 100644 --- a/src/web/auto-reply.web-auto-reply.uses-per-agent-mention-patterns-group-gating.test.ts +++ b/src/web/auto-reply.web-auto-reply.uses-per-agent-mention-patterns-group-gating.test.ts @@ -15,6 +15,7 @@ vi.mock("../agents/pi-embedded.js", () => ({ import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; import { resetLogger, setLoggerOverride } from "../logging.js"; +import { sleep } from "../utils.js"; import { monitorWebChannel } from "./auto-reply.js"; import { resetBaileysMocks, resetLoadConfigMock, setLoadConfigMock } from "./test-helpers.js"; @@ -33,7 +34,7 @@ const rmDirWithRetries = async (dir: string): Promise => { ? String((err as { code?: unknown }).code) : null; if (code === "ENOTEMPTY" || code === "EBUSY" || code === "EPERM") { - await new Promise((resolve) => setTimeout(resolve, 25)); + await sleep(25); continue; } throw err; diff --git a/src/web/auto-reply/deliver-reply.ts b/src/web/auto-reply/deliver-reply.ts index 21ddc2d2a..607b1ac41 100644 --- a/src/web/auto-reply/deliver-reply.ts +++ b/src/web/auto-reply/deliver-reply.ts @@ -4,6 +4,7 @@ import type { WebInboundMsg } from "./types.js"; import { chunkMarkdownTextWithMode, type ChunkMode } from "../../auto-reply/chunk.js"; import { logVerbose, shouldLogVerbose } from "../../globals.js"; import { convertMarkdownTables } from "../../markdown/tables.js"; +import { sleep } from "../../utils.js"; import { loadWebMedia } from "../media.js"; import { newConnectionId } from "../reconnect.js"; import { formatError } from "../session.js"; @@ -36,8 +37,6 @@ export async function deliverWebReply(params: { ? [replyResult.mediaUrl] : []; - const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - const sendWithRetry = async (fn: () => Promise, label: string, maxAttempts = 3) => { let lastErr: unknown; for (let attempt = 1; attempt <= maxAttempts; attempt++) {