From 8cf02e7c4749e0d4262dde40354bfcc63e6fa32f Mon Sep 17 00:00:00 2001 From: Altay Date: Thu, 9 Apr 2026 23:47:59 +0100 Subject: [PATCH] fix(ci): clear check-additional follow-up regressions (#63934) * fix(ci): route messaging temp files through openclaw tmp dir * fix(ci): clear qa-lab follow-up guardrails * fix(ci): own-check ACP fallback resolvers * fix(ci): preserve memory-core write error causes * fix(ci): narrow qa-channel boundary alias * fix(test): type memory-core dreaming api stubs --- extensions/active-memory/index.ts | 4 +- extensions/browser/src/browser/chrome-mcp.ts | 4 +- extensions/diffs/src/test-helpers.ts | 4 +- .../google/video-generation-provider.ts | 6 ++- extensions/memory-core/src/cli.runtime.ts | 9 +++- .../memory-core/src/dreaming-narrative.ts | 1 + extensions/memory-core/src/dreaming.test.ts | 49 ++++++++++++------- extensions/memory-core/src/test-helpers.ts | 6 ++- extensions/memory-wiki/src/test-helpers.ts | 4 +- extensions/openshell/src/backend.ts | 3 +- extensions/qa-lab/src/gateway-child.ts | 5 +- .../qa-lab/src/model-catalog.runtime.ts | 6 ++- extensions/qqbot/src/utils/platform.ts | 9 ++-- extensions/signal/src/install-signal-cli.ts | 4 +- extensions/xai/tsconfig.json | 1 + scripts/check-no-raw-channel-fetch.mjs | 4 +- scripts/lib/extension-package-boundary.ts | 2 + src/agents/acp-spawn.ts | 26 +++++----- .../outbound/delivery-queue.test-helpers.ts | 4 +- 19 files changed, 93 insertions(+), 58 deletions(-) diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index 4d42a3b8689..f9a3b029215 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -1,6 +1,5 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { DEFAULT_PROVIDER, @@ -15,6 +14,7 @@ import { type OpenClawConfig, } from "openclaw/plugin-sdk/config-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; const DEFAULT_TIMEOUT_MS = 15_000; const DEFAULT_AGENT_ID = "main"; @@ -1210,7 +1210,7 @@ async function runRecallSubagent(params: { : `agent:${params.agentId}:${subagentSuffix}`; const tempDir = params.config.persistTranscripts ? undefined - : await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-active-memory-")); + : await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-active-memory-")); const persistedDir = params.config.persistTranscripts ? resolveSafeTranscriptDir( resolvePersistentTranscriptBaseDir(params.api, params.agentId), diff --git a/extensions/browser/src/browser/chrome-mcp.ts b/extensions/browser/src/browser/chrome-mcp.ts index 6ad895d4e41..19be0b8e297 100644 --- a/extensions/browser/src/browser/chrome-mcp.ts +++ b/extensions/browser/src/browser/chrome-mcp.ts @@ -1,10 +1,10 @@ import { randomUUID } from "node:crypto"; import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime"; +import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { asRecord } from "../record-shared.js"; import type { ChromeMcpSnapshotNode } from "./chrome-mcp.snapshot.js"; import type { BrowserTab } from "./client.js"; @@ -332,7 +332,7 @@ async function callTool( } async function withTempFile(fn: (filePath: string) => Promise): Promise { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-chrome-mcp-")); + const dir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-chrome-mcp-")); const filePath = path.join(dir, randomUUID()); try { return await fn(filePath); diff --git a/extensions/diffs/src/test-helpers.ts b/extensions/diffs/src/test-helpers.ts index f97ed9573e1..77d3c2a761c 100644 --- a/extensions/diffs/src/test-helpers.ts +++ b/extensions/diffs/src/test-helpers.ts @@ -1,13 +1,13 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; +import { resolvePreferredOpenClawTmpDir } from "../api.js"; import { DiffArtifactStore } from "./store.js"; export async function createTempDiffRoot(prefix: string): Promise<{ rootDir: string; cleanup: () => Promise; }> { - const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix)); + const rootDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), prefix)); return { rootDir, cleanup: async () => { diff --git a/extensions/google/video-generation-provider.ts b/extensions/google/video-generation-provider.ts index 619f3691a1d..8efd9b1550c 100644 --- a/extensions/google/video-generation-provider.ts +++ b/extensions/google/video-generation-provider.ts @@ -1,9 +1,9 @@ import { mkdtemp, readFile, rm } from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { GoogleGenAI } from "@google/genai"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import type { GeneratedVideoAsset, @@ -124,7 +124,9 @@ async function downloadGeneratedVideo(params: { file: unknown; index: number; }): Promise { - const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-video-")); + const tempDir = await mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "openclaw-google-video-"), + ); const downloadPath = path.join(tempDir, `video-${params.index + 1}.mp4`); try { await params.client.files.download({ diff --git a/extensions/memory-core/src/cli.runtime.ts b/extensions/memory-core/src/cli.runtime.ts index 102e1b413f2..394a2ecd5d0 100644 --- a/extensions/memory-core/src/cli.runtime.ts +++ b/extensions/memory-core/src/cli.runtime.ts @@ -4,6 +4,7 @@ import os from "node:os"; import path from "node:path"; import { resolveMemoryRemDreamingConfig } from "openclaw/plugin-sdk/memory-core-host-status"; import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { colorize, defaultRuntime, @@ -158,7 +159,9 @@ async function createHistoricalRemHarnessWorkspace(params: { skippedPaths: string[]; }> { const sourceFiles = await listHistoricalDailyFiles(params.inputPath); - const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-rem-harness-")); + const workspaceDir = await fs.mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "openclaw-rem-harness-"), + ); const memoryDir = path.join(workspaceDir, "memory"); await fs.mkdir(memoryDir, { recursive: true }); for (const filePath of sourceFiles) { @@ -1720,7 +1723,9 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) { return; } - const scratchDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-rem-backfill-")); + const scratchDir = await fs.mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "openclaw-rem-backfill-"), + ); try { const sourceFiles = await listHistoricalDailyFiles(opts.path); if (sourceFiles.length === 0) { diff --git a/extensions/memory-core/src/dreaming-narrative.ts b/extensions/memory-core/src/dreaming-narrative.ts index 138fcf77d73..85633cad364 100644 --- a/extensions/memory-core/src/dreaming-narrative.ts +++ b/extensions/memory-core/src/dreaming-narrative.ts @@ -296,6 +296,7 @@ async function writeDreamsFileAtomic(dreamsPath: string, content: string): Promi if (cleanupError) { throw new Error( `Atomic DREAMS.md write failed (${formatErrorMessage(err)}); cleanup also failed (${formatErrorMessage(cleanupError)})`, + { cause: err }, ); } throw err; diff --git a/extensions/memory-core/src/dreaming.test.ts b/extensions/memory-core/src/dreaming.test.ts index b90505f7680..311ccff5e28 100644 --- a/extensions/memory-core/src/dreaming.test.ts +++ b/extensions/memory-core/src/dreaming.test.ts @@ -25,6 +25,15 @@ type CronParam = NonNullable>[number]; type CronAddInput = Parameters[0]; type CronPatch = Parameters[1]; +type DreamingPluginApi = Parameters[0]; +type DreamingPluginApiTestDouble = { + config: OpenClawConfig; + pluginConfig: Record; + logger: ReturnType; + runtime: unknown; + registerHook: (event: string, handler: Parameters[1]) => void; + on: ReturnType; +}; function createLogger() { return { @@ -141,6 +150,10 @@ function getBeforeAgentReplyHandler( ) => Promise; } +function registerShortTermPromotionDreamingForTest(api: DreamingPluginApiTestDouble): void { + registerShortTermPromotionDreaming(api as unknown as DreamingPluginApi); +} + describe("short-term dreaming config", () => { it("uses defaults and user timezone fallback", () => { const cfg = { @@ -700,7 +713,7 @@ describe("gateway startup reconciliation", () => { clearInternalHooks(); const logger = createLogger(); const harness = createCronHarness(); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: {} } }, pluginConfig: {}, logger, @@ -709,10 +722,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: vi.fn(), - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { cfg: { @@ -756,7 +769,7 @@ describe("gateway startup reconciliation", () => { const logger = createLogger(); const harness = createCronHarness(); const onMock = vi.fn(); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: { @@ -779,10 +792,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: onMock, - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); const deps = { cron: harness.cron }; await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { @@ -831,7 +844,7 @@ describe("gateway startup reconciliation", () => { const logger = createLogger(); const startupHarness = createCronHarness(); const onMock = vi.fn(); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: { @@ -854,10 +867,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: onMock, - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); const deps = { cron: startupHarness.cron }; await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { @@ -923,7 +936,7 @@ describe("gateway startup reconciliation", () => { const logger = createLogger(); const harness = createCronHarness(); const onMock = vi.fn(); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: { @@ -946,10 +959,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: onMock, - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { cfg: api.config, @@ -989,7 +1002,7 @@ describe("gateway startup reconciliation", () => { const logger = createLogger(); const harness = createCronHarness(); const onMock = vi.fn(); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: { @@ -1012,10 +1025,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: onMock, - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { cfg: api.config, @@ -1045,7 +1058,7 @@ describe("gateway startup reconciliation", () => { const onMock = vi.fn(); const now = Date.parse("2026-04-10T12:00:00Z"); const nowSpy = vi.spyOn(Date, "now").mockReturnValue(now); - const api = { + const api: DreamingPluginApiTestDouble = { config: { plugins: { entries: { @@ -1068,10 +1081,10 @@ describe("gateway startup reconciliation", () => { registerInternalHook(event, handler); }, on: onMock, - } as never; + }; try { - registerShortTermPromotionDreaming(api); + registerShortTermPromotionDreamingForTest(api); await triggerInternalHook( createInternalHookEvent("gateway", "startup", "gateway:startup", { cfg: api.config, diff --git a/extensions/memory-core/src/test-helpers.ts b/extensions/memory-core/src/test-helpers.ts index b740faa3e8a..6ca1ea5e8cf 100644 --- a/extensions/memory-core/src/test-helpers.ts +++ b/extensions/memory-core/src/test-helpers.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { afterAll, beforeAll } from "vitest"; export function createMemoryCoreTestHarness() { @@ -8,7 +8,9 @@ export function createMemoryCoreTestHarness() { let caseId = 0; beforeAll(async () => { - fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "memory-core-test-fixtures-")); + fixtureRoot = await fs.mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "memory-core-test-fixtures-"), + ); }); afterAll(async () => { diff --git a/extensions/memory-wiki/src/test-helpers.ts b/extensions/memory-wiki/src/test-helpers.ts index 1558af55427..3b9521e12f3 100644 --- a/extensions/memory-wiki/src/test-helpers.ts +++ b/extensions/memory-wiki/src/test-helpers.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { afterEach, vi } from "vitest"; import { createTestPluginApi } from "../../../test/helpers/plugins/plugin-api.js"; import type { OpenClawPluginApi } from "../api.js"; @@ -37,7 +37,7 @@ export function createMemoryWikiTestHarness() { }); async function createTempDir(prefix: string): Promise { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix)); + const tempDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), prefix)); tempDirs.push(tempDir); return tempDir; } diff --git a/extensions/openshell/src/backend.ts b/extensions/openshell/src/backend.ts index 0694d024de7..a482a3ac525 100644 --- a/extensions/openshell/src/backend.ts +++ b/extensions/openshell/src/backend.ts @@ -1,5 +1,4 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import type { CreateSandboxBackendParams, @@ -512,5 +511,5 @@ function buildOpenShellSandboxName(scopeKey: string): string { } function resolveOpenShellTmpRoot(): string { - return path.resolve(resolvePreferredOpenClawTmpDir() ?? os.tmpdir()); + return path.resolve(resolvePreferredOpenClawTmpDir()); } diff --git a/extensions/qa-lab/src/gateway-child.ts b/extensions/qa-lab/src/gateway-child.ts index 3d1cf159cc2..42f6e017b8f 100644 --- a/extensions/qa-lab/src/gateway-child.ts +++ b/extensions/qa-lab/src/gateway-child.ts @@ -8,6 +8,7 @@ import path from "node:path"; import { setTimeout as sleep } from "node:timers/promises"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { startQaGatewayRpcClient } from "./gateway-rpc-client.js"; import { splitQaModelRef } from "./model-selection.js"; import { seedQaAgentWorkspace } from "./qa-agent-workspace.js"; @@ -531,7 +532,9 @@ export async function startQaGatewayChild(params: { thinkingDefault?: QaThinkingLevel; controlUiEnabled?: boolean; }) { - const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-qa-suite-")); + const tempRoot = await fs.mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "openclaw-qa-suite-"), + ); const runtimeCwd = tempRoot; const distEntryPath = path.join(params.repoRoot, "dist", "index.js"); const workspaceDir = path.join(tempRoot, "workspace"); diff --git a/extensions/qa-lab/src/model-catalog.runtime.ts b/extensions/qa-lab/src/model-catalog.runtime.ts index 57669ec9cea..36818683a82 100644 --- a/extensions/qa-lab/src/model-catalog.runtime.ts +++ b/extensions/qa-lab/src/model-catalog.runtime.ts @@ -1,7 +1,7 @@ import { spawn } from "node:child_process"; import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { buildQaGatewayConfig } from "./qa-gateway-config.js"; const QA_FRONTIER_PROVIDER_IDS = ["anthropic", "google", "openai"] as const; @@ -60,7 +60,9 @@ export function selectQaRunnerModelOptions(rows: ModelRow[]): QaRunnerModelOptio } export async function loadQaRunnerModelOptions(params: { repoRoot: string }) { - const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-qa-model-catalog-")); + const tempRoot = await fs.mkdtemp( + path.join(resolvePreferredOpenClawTmpDir(), "openclaw-qa-model-catalog-"), + ); const workspaceDir = path.join(tempRoot, "workspace"); const stateDir = path.join(tempRoot, "state"); const homeDir = path.join(tempRoot, "home"); diff --git a/extensions/qqbot/src/utils/platform.ts b/extensions/qqbot/src/utils/platform.ts index f37d5ecd309..a4e59ffed60 100644 --- a/extensions/qqbot/src/utils/platform.ts +++ b/extensions/qqbot/src/utils/platform.ts @@ -10,6 +10,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { debugLog, debugWarn } from "./debug-log.js"; // Basic platform information. @@ -36,7 +37,7 @@ export function isWindows(): boolean { * Priority: * 1. `os.homedir()` * 2. `$HOME` or `%USERPROFILE%` - * 3. `os.tmpdir()` as a last resort + * 3. the OpenClaw temp directory as a last resort */ export function getHomeDir(): string { try { @@ -53,7 +54,7 @@ export function getHomeDir(): string { } // Final fallback. - return os.tmpdir(); + return resolvePreferredOpenClawTmpDir(); } /** @@ -83,9 +84,9 @@ export function getQQBotMediaDir(...subPaths: string[]): string { // Temporary directory helpers. -/** Return the OS temp directory. */ +/** Return the preferred OpenClaw temp directory. */ export function getTempDir(): string { - return os.tmpdir(); + return resolvePreferredOpenClawTmpDir(); } // Tilde expansion. diff --git a/extensions/signal/src/install-signal-cli.ts b/extensions/signal/src/install-signal-cli.ts index d7bc0a3ec49..1abebcd4666 100644 --- a/extensions/signal/src/install-signal-cli.ts +++ b/extensions/signal/src/install-signal-cli.ts @@ -1,13 +1,13 @@ import { createWriteStream } from "node:fs"; import fs from "node:fs/promises"; import { request } from "node:https"; -import os from "node:os"; import path from "node:path"; import { pipeline } from "node:stream/promises"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/run-command"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import { CONFIG_DIR, extractArchive, resolveBrewExecutable } from "openclaw/plugin-sdk/setup-tools"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; export type ReleaseAsset = { @@ -247,7 +247,7 @@ async function installSignalCliFromRelease(runtime: RuntimeEnv): Promise + normalizeLineConversationIdFallback(params.groupId ?? params.to), + telegram: (params: { to?: string; threadId?: string | number; groupId?: string }) => + normalizeTelegramConversationIdFallback(params), +} as const; + function resolveSpawnMode(params: { requestedMode?: SpawnAcpMode; threadRequested: boolean; @@ -509,17 +516,14 @@ function resolveConversationIdForThreadBinding(params: { if (normalizeOptionalString(pluginResolvedConversationId)) { return normalizeOptionalString(pluginResolvedConversationId); } - if (channelKey === "line") { - const lineConversationId = normalizeLineConversationIdFallback(params.groupId ?? params.to); - if (lineConversationId) { - return lineConversationId; - } - } - if (channelKey === "telegram") { - const telegramConversationId = normalizeTelegramConversationIdFallback(params); - if (telegramConversationId) { - return telegramConversationId; - } + const compatibilityConversationId = + channelKey && Object.hasOwn(threadBindingFallbackConversationResolvers, channelKey) + ? threadBindingFallbackConversationResolvers[ + channelKey as keyof typeof threadBindingFallbackConversationResolvers + ](params) + : undefined; + if (compatibilityConversationId) { + return compatibilityConversationId; } const genericConversationId = resolveConversationIdFromTargets({ threadId: params.threadId, diff --git a/src/infra/outbound/delivery-queue.test-helpers.ts b/src/infra/outbound/delivery-queue.test-helpers.ts index f2bf02120e6..1d48f4a5a72 100644 --- a/src/infra/outbound/delivery-queue.test-helpers.ts +++ b/src/infra/outbound/delivery-queue.test-helpers.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; -import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, vi } from "vitest"; +import { resolvePreferredOpenClawTmpDir } from "../tmp-openclaw-dir.js"; import type { DeliverFn, RecoveryLogger } from "./delivery-queue.js"; export function installDeliveryQueueTmpDirHooks(): { readonly tmpDir: () => string } { @@ -10,7 +10,7 @@ export function installDeliveryQueueTmpDirHooks(): { readonly tmpDir: () => stri let fixtureCount = 0; beforeAll(() => { - fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-dq-suite-")); + fixtureRoot = fs.mkdtempSync(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-dq-suite-")); }); beforeEach(() => {