refactor: dedupe qa and diff error formatting

This commit is contained in:
Peter Steinberger
2026-04-07 03:50:58 +01:00
parent 54cd8ed25b
commit e169fcd263
10 changed files with 40 additions and 35 deletions

View File

@@ -1,6 +1,7 @@
import { constants as fsConstants } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { chromium } from "playwright-core";
import type { OpenClawConfig } from "../api.js";
import type { DiffRenderOptions, DiffTheme } from "./types.js";
@@ -255,7 +256,7 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
if (error instanceof Error && error.message === IMAGE_SIZE_LIMIT_ERROR) {
throw error;
}
const reason = error instanceof Error ? error.message : String(error);
const reason = formatErrorMessage(error);
throw new Error(
`Diff PNG/PDF rendering requires a Chromium-compatible browser. Set browser.executablePath or install Chrome/Chromium. ${reason}`,
{ cause: error },

View File

@@ -1,5 +1,6 @@
import fs from "node:fs/promises";
import { Static, Type } from "@sinclair/typebox";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import type { AnyAgentTool, OpenClawPluginApi, OpenClawPluginToolContext } from "../api.js";
import { PlaywrightDiffScreenshotter, type DiffScreenshotter } from "./browser.js";
import { resolveDiffImageRenderOptions } from "./config.js";
@@ -295,19 +296,19 @@ export function createDiffsTool(params: {
};
} catch (error) {
if (mode === "both") {
const errorMessage = formatErrorMessage(error);
return {
content: [
{
type: "text",
text:
`Diff viewer ready.\n${viewerUrl}\n` +
`File rendering failed: ${error instanceof Error ? error.message : String(error)}`,
`Diff viewer ready.\n${viewerUrl}\n` + `File rendering failed: ${errorMessage}`,
},
],
details: {
...baseDetails,
fileError: error instanceof Error ? error.message : String(error),
imageError: error instanceof Error ? error.message : String(error),
fileError: errorMessage,
imageError: errorMessage,
},
};
}

View File

@@ -211,7 +211,7 @@ function resolveGatewayInfoWithFallback(params: { runtime?: RuntimeEnv; error: u
if (!isTransientGatewayMetadataError(params.error)) {
throw params.error;
}
const message = params.error instanceof Error ? params.error.message : String(params.error);
const message = formatErrorMessage(params.error);
params.runtime?.log?.(
`discord: gateway metadata lookup failed transiently; using default gateway url (${message})`,
);

View File

@@ -532,7 +532,7 @@ export class QmdMemoryManager implements MemorySearchManager {
await this.removeCollection(conflictName);
existing.delete(conflictName);
} catch (removeErr) {
const removeMessage = removeErr instanceof Error ? removeErr.message : String(removeErr);
const removeMessage = formatErrorMessage(removeErr);
if (!this.isCollectionMissingError(removeMessage)) {
log.warn(`qmd collection remove failed for ${conflictName}: ${removeMessage}`);
}
@@ -547,7 +547,7 @@ export class QmdMemoryManager implements MemorySearchManager {
});
return true;
} catch (retryErr) {
const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
const retryMessage = formatErrorMessage(retryErr);
log.warn(
`qmd collection add failed for ${collection.name} after rebinding ${conflictName}: ${retryMessage} (initial: ${addErrorMessage})`,
);
@@ -821,7 +821,7 @@ export class QmdMemoryManager implements MemorySearchManager {
try {
await this.removeCollection(collection.name);
} catch (removeErr) {
const removeMessage = removeErr instanceof Error ? removeErr.message : String(removeErr);
const removeMessage = formatErrorMessage(removeErr);
if (!this.isCollectionMissingError(removeMessage)) {
log.warn(`qmd collection remove failed for ${collection.name}: ${removeMessage}`);
}
@@ -829,7 +829,7 @@ export class QmdMemoryManager implements MemorySearchManager {
try {
await this.addCollection(collection.path, collection.name, collection.pattern);
} catch (addErr) {
const addMessage = addErr instanceof Error ? addErr.message : String(addErr);
const addMessage = formatErrorMessage(addErr);
if (!this.isCollectionAlreadyExistsError(addMessage)) {
log.warn(`qmd collection add failed for ${collection.name}: ${addMessage}`);
}

View File

@@ -1,4 +1,5 @@
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import type { QaBusState } from "./bus-state.js";
import type {
QaBusCreateThreadInput,
@@ -33,7 +34,7 @@ export function writeJson(res: ServerResponse, statusCode: number, body: unknown
export function writeError(res: ServerResponse, statusCode: number, error: unknown) {
writeJson(res, statusCode, {
error: error instanceof Error ? error.message : String(error),
error: formatErrorMessage(error),
});
}

View File

@@ -5,6 +5,7 @@ import net from "node:net";
import os from "node:os";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { seedQaAgentWorkspace } from "./qa-agent-workspace.js";
import { buildQaGatewayConfig } from "./qa-gateway-config.js";
@@ -242,7 +243,7 @@ export async function startQaGatewayChild(params: {
JSON.stringify(rpcParams ?? {}),
],
}).catch((error) => {
const details = error instanceof Error ? error.message : String(error);
const details = formatErrorMessage(error);
throw new Error(`${details}\nGateway logs:\n${logs()}`);
});
},

View File

@@ -11,6 +11,7 @@ import path from "node:path";
import type { Duplex } from "node:stream";
import tls from "node:tls";
import { fileURLToPath } from "node:url";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { handleQaBusRequest, writeError, writeJson } from "./bus-server.js";
import { createQaBusState, type QaBusState } from "./bus-state.js";
import { createQaRunnerRuntime } from "./harness-runtime.js";
@@ -670,7 +671,7 @@ export async function startQaLabServer(params?: {
startedAt,
finishedAt: new Date().toISOString(),
artifacts: null,
error: error instanceof Error ? error.message : String(error),
error: formatErrorMessage(error),
};
} finally {
activeSuiteRun = null;

View File

@@ -1,3 +1,4 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import type { QaBusState } from "./bus-state.js";
export type QaScenarioStepContext = {
@@ -42,7 +43,7 @@ export async function runQaScenario(
...(details ? { details } : {}),
});
} catch (error) {
const details = error instanceof Error ? error.message : String(error);
const details = formatErrorMessage(error);
steps.push({
name: step.name,
status: "fail",

View File

@@ -6,6 +6,7 @@ import { setTimeout as sleep } from "node:timers/promises";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
import type { QaBusState } from "./bus-state.js";
import { extractQaToolPayload } from "./extract-tool-payload.js";
@@ -179,7 +180,7 @@ async function runScenario(name: string, steps: QaSuiteStep[]): Promise<QaSuiteS
...(details ? { details } : {}),
});
} catch (error) {
const details = error instanceof Error ? error.message : String(error);
const details = formatErrorMessage(error);
if (process.env.OPENCLAW_QA_DEBUG === "1") {
console.error(`[qa-suite] fail scenario="${name}" step="${step.name}" details=${details}`);
}
@@ -271,7 +272,7 @@ async function waitForConfigRestartSettle(
}
function isGatewayRestartRace(error: unknown) {
const text = error instanceof Error ? error.message : String(error);
const text = formatErrorMessage(error);
return (
text.includes("gateway closed (1012)") ||
text.includes("gateway closed (1006") ||
@@ -1484,7 +1485,7 @@ When the user asks for the hot install marker exactly, reply with exactly: HOT-I
250,
).catch((error) => {
throw new Error(
`image provider was never invoked: ${error instanceof Error ? error.message : String(error)}; toolOutput=${String(imageRequest.toolOutput ?? "")}`,
`image provider was never invoked: ${formatErrorMessage(error)}; toolOutput=${String(imageRequest.toolOutput ?? "")}`,
);
});
return `${outbound.text}\nIMAGE_PROMPT:${generated.prompt ?? ""}`;
@@ -1568,7 +1569,7 @@ When the user asks for the hot disable marker exactly, reply with exactly: HOT-P
200,
).catch((error) => {
throw new Error(
`hot-disable skill never became eligible: ${error instanceof Error ? error.message : String(error)}`,
`hot-disable skill never became eligible: ${formatErrorMessage(error)}`,
);
});
const beforeSkills = await readSkillStatus(env);
@@ -1595,9 +1596,9 @@ When the user asks for the hot disable marker exactly, reply with exactly: HOT-P
};
await waitForQaChannelReady(env, 60_000).catch((error) => {
throw new Error(
`qa-channel never returned ready after config.patch: ${
error instanceof Error ? error.message : String(error)
}`,
`qa-channel never returned ready after config.patch: ${formatErrorMessage(
error,
)}`,
);
});
await waitForCondition(
@@ -1609,9 +1610,7 @@ When the user asks for the hot disable marker exactly, reply with exactly: HOT-P
200,
).catch((error) => {
throw new Error(
`hot-disable skill never flipped to disabled: ${
error instanceof Error ? error.message : String(error)
}`,
`hot-disable skill never flipped to disabled: ${formatErrorMessage(error)}`,
);
});
const afterSkills = await readSkillStatus(env);
@@ -1667,16 +1666,14 @@ When the user asks for the hot disable marker exactly, reply with exactly: HOT-P
});
await waitForGatewayHealthy(env, 60_000).catch((error) => {
throw new Error(
`gateway never returned healthy after config.apply: ${
error instanceof Error ? error.message : String(error)
}`,
`gateway never returned healthy after config.apply: ${formatErrorMessage(error)}`,
);
});
await waitForQaChannelReady(env, 60_000).catch((error) => {
throw new Error(
`qa-channel never returned ready after config.apply: ${
error instanceof Error ? error.message : String(error)
}`,
`qa-channel never returned ready after config.apply: ${formatErrorMessage(
error,
)}`,
);
});
const outbound = await waitForOutboundMessage(
@@ -1685,9 +1682,9 @@ When the user asks for the hot disable marker exactly, reply with exactly: HOT-P
60_000,
).catch((error) => {
throw new Error(
`restart sentinel never appeared: ${
error instanceof Error ? error.message : String(error)
}; outbound=${recentOutboundSummary(state)}`,
`restart sentinel never appeared: ${formatErrorMessage(
error,
)}; outbound=${recentOutboundSummary(state)}`,
);
});
return `${outbound.conversation.id}: ${outbound.text}`;

View File

@@ -1,3 +1,5 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
/** Structured reminder payload emitted by the model. */
export interface CronReminderPayload {
type: "cron_reminder";
@@ -83,7 +85,7 @@ export function parseQQBotPayload(text: string): ParseResult {
} catch (e) {
return {
isPayload: true,
error: `Failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`,
error: `Failed to parse JSON: ${formatErrorMessage(e)}`,
};
}
}
@@ -143,7 +145,7 @@ export function decodeCronPayload(message: string): {
} catch (e) {
return {
isCronPayload: true,
error: `Failed to decode cron payload: ${e instanceof Error ? e.message : String(e)}`,
error: `Failed to decode cron payload: ${formatErrorMessage(e)}`,
};
}
}