fix(ci): clear extension lint regressions

This commit is contained in:
Vincent Koc
2026-04-15 12:08:33 +01:00
parent c727388f93
commit b855b1d047
3 changed files with 87 additions and 58 deletions

View File

@@ -1,5 +1,6 @@
import http from "node:http";
import https from "node:https";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
import type {
QaBusConversation,
QaBusEvent,
@@ -271,9 +272,17 @@ export async function injectQaBusInboundMessage(params: {
}
export async function getQaBusState(baseUrl: string): Promise<QaBusStateSnapshot> {
const response = await fetch(buildQaBusUrl(baseUrl, "/v1/state"));
if (!response.ok) {
throw new Error(`qa-bus request failed: ${response.status}`);
const { response, release } = await fetchWithSsrFGuard({
url: buildQaBusUrl(baseUrl, "/v1/state").toString(),
policy: { allowPrivateNetwork: true },
auditContext: "qa-channel.bus-state",
});
try {
if (!response.ok) {
throw new Error(`qa-bus request failed: ${response.status}`);
}
return (await response.json()) as QaBusStateSnapshot;
} finally {
await release();
}
return (await response.json()) as QaBusStateSnapshot;
}

View File

@@ -1,11 +1,11 @@
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
import { afterEach, describe, expect, it } from "vitest";
import { extractToolPayload } from "../../../src/infra/outbound/tool-payload.js";
import {
resetPluginRuntimeStateForTest,
setActivePluginRegistry,
} from "../../../src/plugins/runtime.js";
import { createTestRegistry } from "../../../src/test-utils/channel-plugins.js";
} from "openclaw/plugin-sdk/testing";
import { afterEach, describe, expect, it } from "vitest";
import { extractToolPayload } from "../../../src/infra/outbound/tool-payload.js";
import { createTestRegistry } from "../../../test/helpers/plugins/plugin-registry.js";
import { createStartAccountContext } from "../../../test/helpers/plugins/start-account-context.js";
import { createQaBusState, startQaBusServer } from "../../qa-lab/api.js";
import { qaChannelPlugin, setQaChannelRuntime } from "../api.js";

View File

@@ -1,6 +1,7 @@
import { createRequire } from "node:module";
import os from "node:os";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { debugLog, debugError } from "./utils/debug-log.js";
import { sanitizeFileName } from "./utils/platform.js";
@@ -112,12 +113,19 @@ async function doFetchToken(appId: string, clientSecret: string): Promise<string
debugLog(`[qqbot-api:${appId}] >>> POST ${TOKEN_URL}`);
let response: Response;
let release = async () => {};
try {
response = await fetch(TOKEN_URL, {
method: "POST",
headers: requestHeaders,
body: JSON.stringify(requestBody),
const guarded = await fetchWithSsrFGuard({
url: TOKEN_URL,
init: {
method: "POST",
headers: requestHeaders,
body: JSON.stringify(requestBody),
},
auditContext: "qqbot.token",
});
response = guarded.response;
release = guarded.release;
} catch (err) {
debugError(`[qqbot-api:${appId}] <<< Network error:`, err);
throw new Error(`Network error getting access_token: ${formatErrorMessage(err)}`, {
@@ -125,44 +133,48 @@ async function doFetchToken(appId: string, clientSecret: string): Promise<string
});
}
const responseHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
const tokenTraceId = response.headers.get("x-tps-trace-id") ?? "";
debugLog(
`[qqbot-api:${appId}] <<< Status: ${response.status} ${response.statusText}${tokenTraceId ? ` | TraceId: ${tokenTraceId}` : ""}`,
);
let data: { access_token?: string; expires_in?: number };
let rawBody: string;
try {
rawBody = await response.text();
// Redact the token before logging the raw response body.
const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token": "***"');
debugLog(`[qqbot-api:${appId}] <<< Body:`, logBody);
data = JSON.parse(rawBody) as { access_token?: string; expires_in?: number };
} catch (err) {
debugError(`[qqbot-api:${appId}] <<< Parse error:`, err);
throw new Error(`Failed to parse access_token response: ${formatErrorMessage(err)}`, {
cause: err,
const responseHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
const tokenTraceId = response.headers.get("x-tps-trace-id") ?? "";
debugLog(
`[qqbot-api:${appId}] <<< Status: ${response.status} ${response.statusText}${tokenTraceId ? ` | TraceId: ${tokenTraceId}` : ""}`,
);
let data: { access_token?: string; expires_in?: number };
let rawBody: string;
try {
rawBody = await response.text();
// Redact the token before logging the raw response body.
const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, '"access_token": "***"');
debugLog(`[qqbot-api:${appId}] <<< Body:`, logBody);
data = JSON.parse(rawBody) as { access_token?: string; expires_in?: number };
} catch (err) {
debugError(`[qqbot-api:${appId}] <<< Parse error:`, err);
throw new Error(`Failed to parse access_token response: ${formatErrorMessage(err)}`, {
cause: err,
});
}
if (!data.access_token) {
throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
}
const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1000;
tokenCacheMap.set(appId, {
token: data.access_token,
expiresAt,
appId,
});
debugLog(`[qqbot-api:${appId}] Token cached, expires at: ${new Date(expiresAt).toISOString()}`);
return data.access_token;
} finally {
await release();
}
if (!data.access_token) {
throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
}
const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1000;
tokenCacheMap.set(appId, {
token: data.access_token,
expiresAt,
appId,
});
debugLog(`[qqbot-api:${appId}] Token cached, expires at: ${new Date(expiresAt).toISOString()}`);
return data.access_token;
}
/** Clear one token cache or all token caches. */
@@ -247,8 +259,15 @@ export async function apiRequest<T = unknown>(
}
let res: Response;
let release = async () => {};
try {
res = await fetch(url, options);
const guarded = await fetchWithSsrFGuard({
url,
init: options,
auditContext: `qqbot.api${path}`,
});
res = guarded.response;
release = guarded.release;
} catch (err) {
clearTimeout(timeoutId);
if (err instanceof Error && err.name === "AbortError") {
@@ -270,24 +289,25 @@ export async function apiRequest<T = unknown>(
`[qqbot-api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`,
);
let data: T;
let rawBody: string;
try {
rawBody = await res.text();
let data: T;
const rawBody = await res.text();
debugLog(`[qqbot-api] <<< Body:`, rawBody);
data = JSON.parse(rawBody) as T;
if (!res.ok) {
const error = data as { message?: string; code?: number };
throw new Error(`API Error [${path}]: ${error.message ?? JSON.stringify(data)}`);
}
return data;
} catch (err) {
throw new Error(`Failed to parse response[${path}]: ${formatErrorMessage(err)}`, {
cause: err,
});
} finally {
await release();
}
if (!res.ok) {
const error = data as { message?: string; code?: number };
throw new Error(`API Error [${path}]: ${error.message ?? JSON.stringify(data)}`);
}
return data;
}
// Upload retry with exponential backoff.