mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
refactor: consolidate async timing helpers (#99721)
This commit is contained in:
parent
342d13a914
commit
c7aca4f029
43 changed files with 123 additions and 242 deletions
|
|
@ -5,6 +5,7 @@
|
|||
* redaction/headers, and request/response correlation over WebSocket.
|
||||
*/
|
||||
import { parseBrowserHttpUrl, redactCdpUrl } from "openclaw/plugin-sdk/browser-config";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import WebSocket from "ws";
|
||||
import { isLoopbackHost } from "../gateway/net.js";
|
||||
|
|
@ -423,12 +424,6 @@ type CdpSocketOptions = {
|
|||
handshakeMaxRetryDelayMs?: number;
|
||||
};
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeRetryCount(value: number | undefined, fallback: number): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
return fallback;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* Dispatches normalized actions to either Playwright-backed OpenClaw browser
|
||||
* control or Chrome MCP existing-session operations with navigation guards.
|
||||
*/
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import {
|
||||
clickChromeMcpElement,
|
||||
|
|
@ -51,12 +52,6 @@ import { readRoutePositiveInteger, readRouteTimerTimeoutMs } from "./route-numer
|
|||
import type { BrowserRouteRegistrar } from "./types.js";
|
||||
import { asyncBrowserRoute, jsonError, toStringOrEmpty } from "./utils.js";
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
const EXISTING_SESSION_INTERACTION_NAVIGATION_RECHECK_DELAYS_MS = [0, 250, 500] as const;
|
||||
|
||||
async function readExistingSessionLocationHref(params: {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import type {
|
|||
MigrationPlan,
|
||||
MigrationProviderContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { defaultCodexAppInventoryCache } from "../app-server/app-inventory-cache.js";
|
||||
import {
|
||||
|
|
@ -385,12 +386,6 @@ function isCodexPluginLoadWarningItem(item: MigrationItem): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function buildTargetCodexPluginAppCacheKey(ctx: MigrationProviderContext): Promise<string> {
|
||||
const targets = resolveCodexMigrationTargets(ctx);
|
||||
const appServer = resolveTargetCodexAppServer(ctx);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import {
|
|||
resolveSendableOutboundReplyParts,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import type { ReplyDispatchKind, ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { danger, logVerbose, shouldLogVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { readLatestAssistantTextByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime";
|
||||
import { resolveDiscordAccount, resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
|
|
@ -71,12 +71,6 @@ import {
|
|||
DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
|
||||
} from "./timeouts.js";
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
const loadReplyRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/reply-runtime"));
|
||||
|
||||
function isProcessAborted(abortSignal?: AbortSignal): boolean {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Feishu plugin module implements app registration behavior.
|
||||
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
/**
|
||||
* Feishu app registration via OAuth device-code flow.
|
||||
*
|
||||
|
|
@ -340,12 +341,6 @@ export async function getAppOwnerOpenId(params: {
|
|||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function sleepRegistrationPollInterval(intervalSeconds: number): Promise<void> {
|
||||
const intervalMs =
|
||||
finiteSecondsToTimerSafeMilliseconds(intervalSeconds) ??
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import { kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import { sleep as delay } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
||||
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
||||
import { resolveIMessageAccount, type ResolvedIMessageAccount } from "./accounts.js";
|
||||
|
|
@ -419,12 +420,6 @@ async function resolveApprovalBindingMessageGuid(params: {
|
|||
);
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function resolveFallbackSentMessageGuid(params: {
|
||||
dbPath?: string;
|
||||
target: ParsedIMessageTarget;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Memory Core plugin module implements watch settle behavior.
|
||||
import fsSync from "node:fs";
|
||||
import path from "node:path";
|
||||
import { sleep as delay } from "openclaw/plugin-sdk/runtime-env";
|
||||
|
||||
export type MemoryWatchEventStats = {
|
||||
isDirectory?: () => boolean;
|
||||
|
|
@ -46,12 +47,6 @@ function snapshotPath(filePath: string): WatchPathSnapshot | null {
|
|||
}
|
||||
}
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function recordMemoryWatchEventPath(
|
||||
queue: MemoryWatchSettleQueue,
|
||||
watchPath?: string,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
isSameMemoryDreamingDay,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-status";
|
||||
import { appendMemoryHostEvent } from "openclaw/plugin-sdk/memory-host-events";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
|
|
@ -847,12 +848,6 @@ function isProcessLikelyAlive(pid: number): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function withInProcessShortTermLock<T>(lockPath: string, task: () => Promise<T>): Promise<T> {
|
||||
const previous = inProcessShortTermLocks.get(lockPath) ?? Promise.resolve();
|
||||
let releaseCurrent!: () => void;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
import * as crypto from "node:crypto";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type { MediaSource, OpenedLocalFile } from "../messaging/media-source.js";
|
||||
import { openLocalFile } from "../messaging/media-source.js";
|
||||
|
|
@ -631,9 +632,3 @@ async function runWithConcurrency(
|
|||
await Promise.all(batch.map((task) => task()));
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
* parameterized by `RetryPolicy` and optional `PersistentRetryPolicy`.
|
||||
*/
|
||||
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { EngineLogger } from "../types.js";
|
||||
import { formatErrorMessage } from "../utils/format.js";
|
||||
|
||||
|
|
@ -146,12 +147,6 @@ async function persistentRetryLoop<T>(
|
|||
throw lastError ?? new Error(`Persistent retry timed out (${policy.timeoutMs / 1000}s)`);
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
// ============ Pre-built Retry Policies ============
|
||||
|
||||
/** Standard upload retry: exponential backoff, skip 400/401/timeout errors. */
|
||||
|
|
|
|||
|
|
@ -44,7 +44,12 @@ import { dispatchInboundMessage } from "openclaw/plugin-sdk/reply-runtime";
|
|||
import { createReplyDispatcherWithTyping } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
danger,
|
||||
logVerbose,
|
||||
shouldLogVerbose,
|
||||
sleep as delay,
|
||||
} from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
|
@ -141,12 +146,6 @@ function hasSignalStatusReplyDeliveryFailure(result: SignalStatusDispatchResult)
|
|||
);
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSignalStatusReactionEmojis(
|
||||
emojis: StatusReactionEmojis | undefined,
|
||||
): StatusReactionEmojis | undefined {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
resolveExpiresAtMsFromDurationSeconds,
|
||||
resolveExpiresAtMsFromEpochSeconds,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import type { ProviderAuthContext, ProviderAuthMethod } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
buildOauthProviderAuthResult,
|
||||
|
|
@ -13,6 +12,8 @@ import {
|
|||
type OAuthCredential,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { applyXaiConfig, XAI_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import { xaiUserAgent } from "./src/xai-user-agent.js";
|
||||
|
||||
|
|
@ -299,12 +300,6 @@ function describeXaiOAuthTokenFailure(params: {
|
|||
};
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function exchangeXaiOAuthToken(
|
||||
params: {
|
||||
tokenEndpoint: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Measures gateway watch idle CPU and dist/runtime artifact churn.
|
||||
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
|
|
@ -14,6 +14,7 @@ import {
|
|||
writeBuildStamp,
|
||||
writeRuntimePostBuildStamp,
|
||||
} from "./lib/local-build-metadata.mjs";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
import { resolveBuildRequirement } from "./run-node.mjs";
|
||||
|
||||
const DEFAULTS = {
|
||||
|
|
@ -334,12 +335,6 @@ function runCheckedCommand(command, args) {
|
|||
throw new Error(`${command} ${args.join(" ")} failed with status ${result.status ?? "unknown"}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function parsePsCpuTimeMs(timeText) {
|
||||
const [maybeDays, clockText] = timeText.includes("-") ? timeText.split("-", 2) : ["0", timeText];
|
||||
const days = Number(maybeDays);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|||
import { completeSimple, type AssistantMessage, type Model } from "openclaw/plugin-sdk/llm";
|
||||
import * as ts from "typescript";
|
||||
import { formatErrorMessage } from "../src/infra/errors.ts";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
|
||||
|
||||
const { formatGeneratedModule } = (await import(
|
||||
|
|
@ -634,12 +635,6 @@ function buildBatchPrompt(items: readonly TranslationBatchItem[]): string {
|
|||
].join("\n");
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
if (ms < 1_000) {
|
||||
return `${Math.round(ms)}ms`;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
redactForDevToolLog,
|
||||
redactHomePath,
|
||||
} from "../lib/dev-tooling-safety.ts";
|
||||
import { sleep } from "../lib/sleep.mjs";
|
||||
|
||||
function writeStdoutLine(message: string): void {
|
||||
process.stdout.write(`${message}\n`);
|
||||
|
|
@ -157,12 +158,6 @@ class CliArgumentError extends Error {
|
|||
override name = "CliArgumentError";
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function remainingTimeoutMs(deadlineMs: number, nowMs = Date.now()): number {
|
||||
const remaining = Math.floor(deadlineMs - nowMs);
|
||||
if (!Number.isFinite(deadlineMs) || remaining <= 0) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { mkdir, open, writeFile } from "node:fs/promises";
|
|||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { sleep as delay } from "../lib/sleep.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "../lib/windows-taskkill.mjs";
|
||||
|
||||
type Options = {
|
||||
|
|
@ -119,12 +120,6 @@ function parseOptions(args = process.argv.slice(2)): Options {
|
|||
};
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldUseAltScreen(options: Options) {
|
||||
return options.altScreen && process.stdout.isTTY;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Log assertions for config reload E2E scenarios.
|
||||
import { sleep } from "../../../lib/sleep.mjs";
|
||||
import { readPositiveIntEnv } from "../env-limits.mjs";
|
||||
import { createConfigReloadLogScanner } from "./log-scanner.mjs";
|
||||
|
||||
|
|
@ -6,10 +7,6 @@ const logPath = process.env.OPENCLAW_CONFIG_RELOAD_LOG_PATH ?? "/tmp/config-relo
|
|||
const deadlineMs = Date.now() + readPositiveIntEnv("OPENCLAW_CONFIG_RELOAD_LOG_TIMEOUT_MS", 30_000);
|
||||
const maxReadBytes = readPositiveIntEnv("OPENCLAW_CONFIG_RELOAD_LOG_MAX_READ_BYTES", 256 * 1024);
|
||||
|
||||
const sleep = (ms) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
const scanner = createConfigReloadLogScanner(logPath, {
|
||||
maxReadBytes,
|
||||
tailLineLimit: 160,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
// WebSocket client helpers for gateway network E2E scenarios.
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { WebSocket } from "ws";
|
||||
import { sleep as delay } from "../../../lib/sleep.mjs";
|
||||
import { waitForWebSocketOpen } from "../websocket-open.mjs";
|
||||
import { readGatewayNetworkClientConnectTimeoutMs } from "./limits.mjs";
|
||||
import { onceFrame } from "./ws-frames.mjs";
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function remainingDeadlineMs(deadline) {
|
||||
return Math.max(1, deadline - Date.now());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Guest Transports script supports OpenClaw repository automation.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { sleep } from "../../lib/sleep.mjs";
|
||||
import { run } from "./host-command.ts";
|
||||
import type { PhaseRunner } from "./phase-runner.ts";
|
||||
import { encodePowerShell, psSingleQuote } from "./powershell.ts";
|
||||
|
|
@ -44,12 +45,6 @@ function timeoutBefore(deadline: number, fallbackMs: number): number {
|
|||
return Math.min(fallbackMs, Math.max(1_000, deadline - Date.now()));
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function throwIfFailed(label: string, result: CommandResult, check: boolean | undefined): void {
|
||||
if (check === false || result.status === 0) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|||
import { createServer } from "node:http";
|
||||
import { createConnection } from "node:net";
|
||||
import path from "node:path";
|
||||
import { sleep as delay } from "../../lib/sleep.mjs";
|
||||
import { die, run, say, sh, warn } from "./host-command.ts";
|
||||
import type { HostServer } from "./types.ts";
|
||||
|
||||
|
|
@ -195,12 +196,6 @@ async function canConnect(port: number): Promise<boolean> {
|
|||
});
|
||||
}
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
appendBoundedOutput,
|
||||
stopHostServerChild,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|||
import { copyFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { sleep as delay } from "../../lib/sleep.mjs";
|
||||
import { readPositiveIntEnv } from "./env-limits.ts";
|
||||
import { exists, readJson } from "./filesystem.ts";
|
||||
import { die, repoRoot, run, say, sh } from "./host-command.ts";
|
||||
|
|
@ -294,12 +295,6 @@ function isErrorCode(error: unknown, code: string): boolean {
|
|||
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
||||
}
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
acquirePackageLock,
|
||||
removeStalePackageLock,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { clampTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion";
|
||||
import { sleep } from "../lib/sleep.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "../lib/windows-taskkill.mjs";
|
||||
import { createPnpmRunnerSpawnSpec } from "../pnpm-runner.mjs";
|
||||
import { readPositiveIntEnv } from "./lib/env-limits.mjs";
|
||||
|
|
@ -995,12 +996,6 @@ function spawnDaemon(params: {
|
|||
return child.pid;
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForChildExit(child: ChildProcess) {
|
||||
if (child.exitCode !== null || child.signalCode !== null) {
|
||||
return Promise.resolve(child.exitCode);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Gateway Bench Child script supports OpenClaw repository automation.
|
||||
import { spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { sleep as delay } from "./sleep.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./windows-taskkill.mjs";
|
||||
|
||||
export { delay };
|
||||
|
||||
const TEARDOWN_GRACE_MS = 2_000;
|
||||
const TEARDOWN_KILL_GRACE_MS = 1_000;
|
||||
const EXIT_POLL_MS = 10;
|
||||
|
|
@ -22,12 +25,6 @@ export type StopChildOptions = {
|
|||
teardownGraceMs?: number;
|
||||
};
|
||||
|
||||
export function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export async function stopChild(
|
||||
child: ChildProcessWithoutNullStreams,
|
||||
options: StopChildOptions = {},
|
||||
|
|
|
|||
2
scripts/lib/sleep.d.mts
Normal file
2
scripts/lib/sleep.d.mts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/** Promise-based sleep that preserves the native global timer contract. */
|
||||
export declare function sleep(ms: number): Promise<void>;
|
||||
6
scripts/lib/sleep.mjs
Normal file
6
scripts/lib/sleep.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/** Promise-based sleep that preserves the native global timer contract. */
|
||||
export function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import path from "node:path";
|
|||
import { performance } from "node:perf_hooks";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { readBoundedResponseText } from "./lib/bounded-response.mjs";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
|
||||
|
||||
const DEFAULT_METHODS = ["health", "config.get"];
|
||||
|
|
@ -139,12 +140,6 @@ async function getFreePort() {
|
|||
});
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function formatErrorMessage(error) {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
writeBuildStamp as writeDistBuildStamp,
|
||||
writeRuntimePostBuildStamp as writeDistRuntimePostBuildStamp,
|
||||
} from "./lib/local-build-metadata.mjs";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
import {
|
||||
discoverStaticExtensionAssets,
|
||||
listStaticExtensionAssetSources,
|
||||
|
|
@ -686,11 +687,6 @@ const parsePositiveIntegerEnv = (env, name, fallback) => {
|
|||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
||||
};
|
||||
|
||||
const sleep = (ms) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
const resolveRunNodeOutputLogPath = (deps) => {
|
||||
const outputLog = deps.env[RUN_NODE_OUTPUT_LOG_ENV]?.trim();
|
||||
if (!outputLog) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
parseProfile,
|
||||
resolveDockerE2ePlan,
|
||||
} from "./lib/docker-e2e-plan.mjs";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
|
||||
const SCRIPT_ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ROOT_DIR = path.resolve(process.env.OPENCLAW_DOCKER_E2E_REPO_ROOT || SCRIPT_ROOT_DIR);
|
||||
|
|
@ -226,12 +227,6 @@ function orderLanes(poolLanes, timingStore) {
|
|||
.map(({ poolLane }) => poolLane);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function utcStampForPath() {
|
||||
return new Date().toISOString().replaceAll("-", "").replaceAll(":", "").replace(/\..*$/, "Z");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Verifies published plugin npm packages include built runtime entries and
|
||||
// metadata expected by OpenClaw.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import * as tar from "tar";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
|
||||
const DEFAULT_NPM_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
const DEFAULT_NPM_COMMAND_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
||||
|
|
@ -271,12 +272,6 @@ function npmViewReadme(spec) {
|
|||
return runPluginNpmCommand(["view", spec, "readme", "--json", "--prefer-online"]);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function packPublishedPackage(spec, destinationDir) {
|
||||
const attempts = readPositiveIntEnv("OPENCLAW_PLUGIN_NPM_VERIFY_ATTEMPTS", 90);
|
||||
const delayMs = readPositiveIntEnv("OPENCLAW_PLUGIN_NPM_VERIFY_DELAY_MS", 10000);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import fs from "node:fs";
|
|||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { sleep } from "./lib/sleep.mjs";
|
||||
import { isRestartRelevantRunNodePath, runNodeWatchedPaths } from "./run-node-watch-paths.mjs";
|
||||
|
||||
const WATCH_NODE_RUNNER = "scripts/run-node.mjs";
|
||||
|
|
@ -95,11 +96,6 @@ const isProcessAlive = (pid, signalProcess) => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const sleep = (ms) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
const createWatchLockKey = (cwd, args) =>
|
||||
createHash("sha256").update(cwd).update("\0").update(args.join("\0")).digest("hex").slice(0, 12);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { Command } from "commander";
|
|||
import type { CronDeliveryPreview, CronJob } from "../../cron/types.js";
|
||||
import { parseStrictPositiveInteger } from "../../infra/parse-finite-number.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { sleep } from "../../utils/sleep.js";
|
||||
import type { GatewayRpcOpts } from "../gateway-rpc.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import { parseDurationMs } from "../parse-duration.js";
|
||||
|
|
@ -36,12 +37,6 @@ type CronRunLogEntryResult = {
|
|||
status?: "ok" | "error" | "skipped";
|
||||
};
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function parseCronRunWaitDuration(raw: unknown, label: string): number {
|
||||
const input =
|
||||
typeof raw === "string" || typeof raw === "number" || typeof raw === "bigint"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { note } from "../../packages/terminal-core/src/note.js";
|
|||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { HealthFinding } from "../flows/health-checks.js";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
import type { StatusSummary } from "./status.types.js";
|
||||
|
||||
type LocalTuiProcess = {
|
||||
|
|
@ -141,12 +142,6 @@ function isProcessAlive(controller: ProcessController, pid: number): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
/** Terminates local TUI processes with SIGTERM, then SIGKILL for remaining pids. */
|
||||
export async function terminateLocalTuiProcesses(params: {
|
||||
processes: LocalTuiProcess[];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Detects suspicious config clobbers and finds recovery snapshots.
|
||||
import path from "node:path";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
|
||||
/** Maximum retained clobbered-config snapshots per config file. */
|
||||
const CONFIG_CLOBBER_SNAPSHOT_LIMIT = 32;
|
||||
|
|
@ -52,12 +53,6 @@ function isFsErrorCode(error: unknown, code: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveClobberPaths(configPath: string): {
|
||||
dir: string;
|
||||
prefix: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Gateway agent-command test helpers.
|
||||
// Waits for mocked agent command dispatches in async gateway tests.
|
||||
import { vi } from "vitest";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
import { agentCommand } from "./test-helpers.runtime-state.js";
|
||||
|
||||
type AgentCommandCall = Record<string, unknown>;
|
||||
|
|
@ -9,11 +10,6 @@ function agentCommandCalls(): Array<[AgentCommandCall]> {
|
|||
return vi.mocked(agentCommand).mock.calls as unknown as Array<[AgentCommandCall]>;
|
||||
}
|
||||
|
||||
const sleep = (ms: number) =>
|
||||
new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
/** Waits until the mocked `agentCommand` receives a call for a specific run id. */
|
||||
export async function waitForAgentCommandCall(runId: string): Promise<AgentCommandCall> {
|
||||
for (let elapsed = 0; elapsed <= 2_000; elapsed += 5) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import { getFreePortBlockWithPermissionFallback } from "../test-utils/ports.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
import { startGatewayClientWhenEventLoopReady } from "./client-start-readiness.js";
|
||||
import { GatewayClient, type GatewayClientOptions } from "./client.js";
|
||||
|
||||
|
|
@ -311,12 +312,6 @@ export async function createBootstrapWorkspace(
|
|||
return { expectedInjectedFiles, workspaceDir, workspaceRootDir };
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldRetryCliCronMcpProbeReply(text: string): boolean {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(text);
|
||||
if (!normalized) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/st
|
|||
import { renderCatFacePngBase64 } from "../../test/helpers/live-image-probe.js";
|
||||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import { parseStrictPositiveInteger } from "../infra/parse-finite-number.js";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
import type { GatewayClient } from "./client.js";
|
||||
import {
|
||||
shouldRetryCliCronMcpProbeReply,
|
||||
|
|
@ -45,12 +46,6 @@ function logCliCronProbe(step: string, details?: Record<string, unknown>): void
|
|||
console.error(`[gateway-cli-live:cron] ${step}${suffix}`);
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function pollCliCronJobVisible(params: {
|
||||
port: number;
|
||||
token: string;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export {
|
|||
success,
|
||||
warn,
|
||||
} from "../globals.js";
|
||||
export { sleep } from "../utils.js";
|
||||
export { sleep } from "../utils/sleep.js";
|
||||
export { withTimeout } from "../utils/with-timeout.js";
|
||||
export { isTruthyEnvValue } from "../infra/env.js";
|
||||
export * from "../logging.js";
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { resolveRealtimeVoiceFastContextConsult } from "./fast-context-runtime.j
|
|||
|
||||
describe("resolveRealtimeVoiceFastContextConsult", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
mocks.getActiveMemorySearchManager.mockReset();
|
||||
});
|
||||
|
|
@ -45,4 +46,37 @@ describe("resolveRealtimeVoiceFastContextConsult", () => {
|
|||
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
it("preserves the fast-context timeout error and clears the timer", async () => {
|
||||
vi.useFakeTimers();
|
||||
const logger = { debug: vi.fn() };
|
||||
mocks.getActiveMemorySearchManager.mockResolvedValue({
|
||||
manager: {
|
||||
search: vi.fn(() => new Promise<never>(() => {})),
|
||||
},
|
||||
});
|
||||
|
||||
const result = resolveRealtimeVoiceFastContextConsult({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
sessionKey: "voice:15550001234",
|
||||
config: {
|
||||
enabled: true,
|
||||
timeoutMs: 25,
|
||||
maxResults: 3,
|
||||
sources: ["memory", "sessions"],
|
||||
fallbackToConsult: true,
|
||||
},
|
||||
args: { question: "What do you remember?" },
|
||||
logger,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
|
||||
await expect(result).resolves.toEqual({ handled: false });
|
||||
expect(logger.debug).toHaveBeenCalledWith(
|
||||
"[talk] fast context lookup failed: fast context lookup timed out after 25ms",
|
||||
);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coerc
|
|||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { getActiveMemorySearchManager } from "../plugins/memory-runtime.js";
|
||||
import { withTimeout } from "../utils/with-timeout.js";
|
||||
import type { RealtimeVoiceAgentConsultResult } from "./agent-consult-runtime.js";
|
||||
import { parseRealtimeVoiceAgentConsultArgs } from "./agent-consult-tool.js";
|
||||
|
||||
|
|
@ -112,28 +113,6 @@ function buildMissText(query: string, labels: RealtimeVoiceFastContextLabels): s
|
|||
].join("\n\n");
|
||||
}
|
||||
|
||||
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
||||
const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 1);
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
return await Promise.race([
|
||||
promise,
|
||||
new Promise<T>((_resolve, reject) => {
|
||||
// resolveTimerTimeoutMs caps huge configured deadlines before they
|
||||
// reach Node's timer APIs.
|
||||
timer = setTimeout(
|
||||
() => reject(new RealtimeFastContextTimeoutError(resolvedTimeoutMs)),
|
||||
resolvedTimeoutMs,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function lookupFastContext(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
|
|
@ -178,6 +157,7 @@ export async function resolveRealtimeVoiceFastContextConsult(params: {
|
|||
const labels = resolveLabels(params.labels);
|
||||
const query = buildSearchQuery(params.args);
|
||||
try {
|
||||
const timeoutMs = resolveTimerTimeoutMs(params.config.timeoutMs, 1);
|
||||
const lookup = await withTimeout(
|
||||
lookupFastContext({
|
||||
cfg: params.cfg,
|
||||
|
|
@ -186,7 +166,8 @@ export async function resolveRealtimeVoiceFastContextConsult(params: {
|
|||
config: params.config,
|
||||
query,
|
||||
}),
|
||||
params.config.timeoutMs,
|
||||
timeoutMs,
|
||||
{ createError: () => new RealtimeFastContextTimeoutError(timeoutMs) },
|
||||
);
|
||||
if (lookup.status === "unavailable") {
|
||||
params.logger.debug?.(`[talk] fast context unavailable: ${lookup.error}`);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { startGatewayClientWhenEventLoopReady } from "../gateway/client-start-re
|
|||
import { GatewayClient, GatewayClientRequestError } from "../gateway/client.js";
|
||||
import { isLoopbackHost } from "../gateway/net.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { sleep } from "../utils/sleep.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { TUI_SETUP_AUTH_SOURCE_CONFIG, TUI_SETUP_AUTH_SOURCE_ENV } from "./setup-launch-env.js";
|
||||
import type {
|
||||
|
|
@ -95,12 +96,6 @@ function resolveStartupRetryDelayMs(err: GatewayClientRequestError): number {
|
|||
return Math.min(Math.max(retryAfterMs, 100), STARTUP_CHAT_HISTORY_MAX_RETRY_MS);
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function nonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import {
|
|||
resolveRequiredHomeDir,
|
||||
} from "./infra/home-dir.js";
|
||||
import { isPlainObject } from "./infra/plain-object.js";
|
||||
import { resolveTimerTimeoutMs } from "./shared/number-coercion.js";
|
||||
export { escapeRegExp } from "./shared/regexp.js";
|
||||
export { sleep } from "./utils/sleep.js";
|
||||
|
||||
/** Creates a directory tree if it does not already exist. */
|
||||
export async function ensureDir(dir: string) {
|
||||
|
|
@ -62,13 +62,6 @@ export function normalizeE164(number: string): string {
|
|||
return `+${digits}`;
|
||||
}
|
||||
|
||||
/** Promise-based sleep that clamps timer inputs through the shared timeout resolver. */
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, resolveTimerTimeoutMs(ms, 0, 0));
|
||||
});
|
||||
}
|
||||
|
||||
// Surrogate-safe slicing helpers live in a node-free leaf module so browser/UI
|
||||
// bundles can import them without pulling in filesystem code. Re-exported here
|
||||
// to preserve the historical `utils.ts` import surface.
|
||||
|
|
|
|||
8
src/utils/sleep.ts
Normal file
8
src/utils/sleep.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";
|
||||
|
||||
/** Promise-based sleep that clamps timer inputs through the shared timeout resolver. */
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, resolveTimerTimeoutMs(ms, 0, 0));
|
||||
});
|
||||
}
|
||||
22
test/scripts/sleep.test.ts
Normal file
22
test/scripts/sleep.test.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { sleep } from "../../scripts/lib/sleep.mjs";
|
||||
|
||||
describe("scripts/lib/sleep.mjs", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY])(
|
||||
"preserves the native global timer delay for %s",
|
||||
async (delayMs) => {
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementation((callback) => {
|
||||
queueMicrotask(() => callback());
|
||||
return 0 as unknown as ReturnType<typeof setTimeout>;
|
||||
});
|
||||
|
||||
await expect(sleep(delayMs)).resolves.toBeUndefined();
|
||||
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), delayMs);
|
||||
},
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue