refactor: consolidate async timing helpers (#99721)

This commit is contained in:
Dallin Romney 2026-07-03 17:58:15 -07:00 committed by GitHub
parent 342d13a914
commit c7aca4f029
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 123 additions and 242 deletions

View file

@ -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;

View file

@ -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: {

View file

@ -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);

View file

@ -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 {

View file

@ -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) ??

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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);
});
}

View file

@ -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. */

View file

@ -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 {

View file

@ -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;

View file

@ -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);

View file

@ -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`;

View file

@ -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) {

View file

@ -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;
}

View file

@ -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,

View file

@ -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());
}

View file

@ -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;

View file

@ -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,

View file

@ -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,

View file

@ -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);

View file

@ -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
View 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
View 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);
});
}

View file

@ -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;

View file

@ -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) {

View file

@ -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");
}

View file

@ -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);

View file

@ -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);

View file

@ -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"

View file

@ -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[];

View file

@ -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;

View file

@ -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) {

View file

@ -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) {

View file

@ -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;

View file

@ -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";

View file

@ -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);
});
});

View file

@ -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}`);

View file

@ -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;
}

View file

@ -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
View 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));
});
}

View 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);
},
);
});