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
This commit is contained in:
Altay
2026-04-09 23:47:59 +01:00
committed by GitHub
parent 8e62df661e
commit 8cf02e7c47
19 changed files with 93 additions and 58 deletions

View File

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

View File

@@ -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<T>(fn: (filePath: string) => Promise<T>): Promise<T> {
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);

View File

@@ -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<void>;
}> {
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
const rootDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), prefix));
return {
rootDir,
cleanup: async () => {

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,15 @@ type CronParam = NonNullable<Parameters<typeof reconcileShortTermDreamingCronJob
type CronJobLike = Awaited<ReturnType<CronParam["list"]>>[number];
type CronAddInput = Parameters<CronParam["add"]>[0];
type CronPatch = Parameters<CronParam["update"]>[1];
type DreamingPluginApi = Parameters<typeof registerShortTermPromotionDreaming>[0];
type DreamingPluginApiTestDouble = {
config: OpenClawConfig;
pluginConfig: Record<string, unknown>;
logger: ReturnType<typeof createLogger>;
runtime: unknown;
registerHook: (event: string, handler: Parameters<typeof registerInternalHook>[1]) => void;
on: ReturnType<typeof vi.fn>;
};
function createLogger() {
return {
@@ -141,6 +150,10 @@ function getBeforeAgentReplyHandler(
) => Promise<unknown>;
}
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,

View File

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

View File

@@ -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<string> {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
const tempDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), prefix));
tempDirs.push(tempDir);
return tempDir;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<SignalI
};
}
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-signal-"));
const tmpDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-signal-"));
const archivePath = path.join(tmpDir, asset.name);
runtime.log(`Downloading signal-cli ${version} (${asset.name})…`);

View File

@@ -53,6 +53,7 @@
"openclaw/plugin-sdk/ssrf-runtime": [
"../../dist/plugin-sdk/src/plugin-sdk/ssrf-runtime.d.ts"
],
"@openclaw/qa-channel/api.js": ["../../dist/plugin-sdk/extensions/qa-channel/api.d.ts"],
"@openclaw/*.js": ["../../packages/plugin-sdk/dist/extensions/*.d.ts", "../*"],
"@openclaw/*": ["../*"],
"@openclaw/plugin-sdk/*": ["../../dist/plugin-sdk/src/plugin-sdk/*.d.ts"],

View File

@@ -17,7 +17,7 @@ const allowedRawFetchCallsites = new Set([
bundledPluginCallsite("bluebubbles", "src/test-harness.ts", 128),
bundledPluginCallsite("bluebubbles", "src/types.ts", 181),
bundledPluginCallsite("browser", "src/browser/cdp.helpers.ts", 235),
bundledPluginCallsite("browser", "src/browser/client-fetch.ts", 217),
bundledPluginCallsite("browser", "src/browser/client-fetch.ts", 192),
bundledPluginCallsite("browser", "src/browser/test-fetch.ts", 24),
bundledPluginCallsite("browser", "src/browser/test-fetch.ts", 27),
bundledPluginCallsite("chutes", "models.ts", 535),
@@ -48,7 +48,7 @@ const allowedRawFetchCallsites = new Set([
bundledPluginCallsite("qa-channel", "src/bus-client.ts", 41),
bundledPluginCallsite("qa-channel", "src/bus-client.ts", 221),
bundledPluginCallsite("qa-lab", "src/docker-up.runtime.ts", 274),
bundledPluginCallsite("qa-lab", "src/gateway-child.ts", 488),
bundledPluginCallsite("qa-lab", "src/gateway-child.ts", 489),
bundledPluginCallsite("qa-lab", "src/suite.ts", 330),
bundledPluginCallsite("qa-lab", "src/suite.ts", 341),
bundledPluginCallsite("qa-lab", "web/src/app.ts", 15),

View File

@@ -60,6 +60,7 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = {
"../dist/plugin-sdk/src/plugin-sdk/secret-ref-runtime.d.ts",
],
"openclaw/plugin-sdk/ssrf-runtime": ["../dist/plugin-sdk/src/plugin-sdk/ssrf-runtime.d.ts"],
"@openclaw/qa-channel/api.js": ["../dist/plugin-sdk/extensions/qa-channel/api.d.ts"],
"@openclaw/*.js": ["../packages/plugin-sdk/dist/extensions/*.d.ts", "../extensions/*"],
"@openclaw/*": ["../packages/plugin-sdk/dist/extensions/*", "../extensions/*"],
"@openclaw/plugin-sdk/*": ["../dist/plugin-sdk/src/plugin-sdk/*.d.ts"],
@@ -105,6 +106,7 @@ export const EXTENSION_PACKAGE_BOUNDARY_XAI_PATHS = {
"openclaw/plugin-sdk/provider-web-search-contract": [
"../../dist/plugin-sdk/src/plugin-sdk/provider-web-search-contract.d.ts",
],
"@openclaw/qa-channel/api.js": ["../../dist/plugin-sdk/extensions/qa-channel/api.d.ts"],
"@openclaw/*.js": ["../../packages/plugin-sdk/dist/extensions/*.d.ts", "../*"],
"@openclaw/*": ["../*"],
"@openclaw/plugin-sdk/*": ["../../dist/plugin-sdk/src/plugin-sdk/*.d.ts"],

View File

@@ -262,6 +262,13 @@ function normalizeTelegramConversationIdFallback(params: {
return /^-?\d+$/.test(normalized) ? normalized : undefined;
}
const threadBindingFallbackConversationResolvers = {
line: (params: { to?: string; groupId?: string }) =>
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,

View File

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