mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 06:32:00 +00:00
fix: restore check gate
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
decodeAcpxRuntimeHandleState,
|
||||
encodeAcpxRuntimeHandleState,
|
||||
type AcpAgentRegistry,
|
||||
type AcpSessionRecord,
|
||||
type AcpRuntimeDoctorReport,
|
||||
type AcpRuntimeEvent,
|
||||
type AcpRuntimeHandle,
|
||||
|
||||
@@ -118,7 +118,7 @@ function sanitizeRecentModels(models: string[] | undefined, limit: number): stri
|
||||
async function readPreferencesStore(filePath: string): Promise<ModelPickerPreferencesStore> {
|
||||
const { value } = await readJsonFileWithFallback(filePath, {
|
||||
version: 1,
|
||||
entries: {},
|
||||
entries: {} as Record<string, ModelPickerPreferencesEntry>,
|
||||
});
|
||||
if (!value || typeof value !== "object" || value.version !== 1) {
|
||||
return { version: 1, entries: {} };
|
||||
|
||||
@@ -770,12 +770,9 @@ describe("discord component interactions", () => {
|
||||
const acknowledge = vi.fn().mockResolvedValue(undefined);
|
||||
const reply = vi.fn().mockResolvedValue(undefined);
|
||||
const update = vi.fn().mockResolvedValue(undefined);
|
||||
const baseInteraction = createComponentButtonInteraction().interaction as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const baseInteraction = createComponentButtonInteraction().interaction;
|
||||
const interaction = {
|
||||
...baseInteraction,
|
||||
...(baseInteraction as object),
|
||||
acknowledge,
|
||||
reply,
|
||||
update,
|
||||
@@ -825,12 +822,9 @@ describe("discord component interactions", () => {
|
||||
const button = createDiscordComponentButton(createComponentContext());
|
||||
const acknowledge = vi.fn().mockResolvedValue(undefined);
|
||||
const followUp = vi.fn().mockResolvedValue(undefined);
|
||||
const baseInteraction = createComponentButtonInteraction().interaction as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const baseInteraction = createComponentButtonInteraction().interaction;
|
||||
const interaction = {
|
||||
...baseInteraction,
|
||||
...(baseInteraction as object),
|
||||
acknowledge,
|
||||
followUp,
|
||||
} as unknown as ButtonInteraction;
|
||||
|
||||
@@ -43,7 +43,10 @@ const defaultOptions = {
|
||||
maxQueueSize: 1000,
|
||||
runtimeProfile: "persistent",
|
||||
scheduler: {},
|
||||
} satisfies Omit<ProxyRequestClientOptions, "fetch">;
|
||||
} satisfies Omit<ProxyRequestClientOptions, "fetch"> & {
|
||||
runtimeProfile: string;
|
||||
scheduler: object;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
|
||||
@@ -17,6 +17,7 @@ describe("duckduckgo web search provider", () => {
|
||||
({ createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js"));
|
||||
({ __testing: ddgClientTesting } =
|
||||
await vi.importActual<typeof import("./ddg-client.js")>("./ddg-client.js"));
|
||||
await import("../index.js");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const {
|
||||
@@ -109,7 +110,7 @@ async function runAction(params: {
|
||||
params: params.params ?? {},
|
||||
mediaLocalRoots: params.mediaLocalRoots,
|
||||
toolContext: params.toolContext,
|
||||
} as unknown);
|
||||
} as Parameters<ReturnType<typeof requireMSTeamsHandleAction>>[0]);
|
||||
}
|
||||
|
||||
async function expectActionError(
|
||||
@@ -226,7 +227,7 @@ describe("msteamsPlugin message actions", () => {
|
||||
tenantId: "tenant-id",
|
||||
},
|
||||
},
|
||||
} as unknown,
|
||||
} as OpenClawConfig,
|
||||
})?.actions,
|
||||
).toContain("upload-file");
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@ export async function handleStructuredPayload(
|
||||
replyText: string,
|
||||
recordActivity: () => void,
|
||||
): Promise<boolean> {
|
||||
const { _target, account, _cfg, log } = ctx;
|
||||
const { account, log } = ctx;
|
||||
const payloadResult = parseQQBotPayload(replyText);
|
||||
|
||||
if (!payloadResult.isPayload) {
|
||||
@@ -195,14 +195,7 @@ export async function handleStructuredPayload(
|
||||
return true;
|
||||
}
|
||||
|
||||
const payloadType =
|
||||
parsedPayload &&
|
||||
typeof parsedPayload === "object" &&
|
||||
"type" in parsedPayload &&
|
||||
typeof parsedPayload.type === "string"
|
||||
? parsedPayload.type
|
||||
: "unknown";
|
||||
log?.error(`[qqbot:${account.accountId}] Unknown payload type: ${payloadType}`);
|
||||
log?.error(`[qqbot:${account.accountId}] Unknown payload type in structured payload`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,42 @@ export interface STTConfig {
|
||||
model: string;
|
||||
}
|
||||
|
||||
type QQBotSttProviderConfig = {
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
type QQBotSttChannelConfig = QQBotSttProviderConfig & {
|
||||
enabled?: boolean;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
type QQBotSttToolAudioModel = QQBotSttProviderConfig & {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
type QQBotSttConfigRoot = {
|
||||
channels?: {
|
||||
qqbot?: {
|
||||
stt?: QQBotSttChannelConfig;
|
||||
};
|
||||
};
|
||||
models?: {
|
||||
providers?: Record<string, QQBotSttProviderConfig>;
|
||||
};
|
||||
tools?: {
|
||||
media?: {
|
||||
audio?: {
|
||||
models?: QQBotSttToolAudioModel[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function resolveSTTConfig(cfg: Record<string, unknown>): STTConfig | null {
|
||||
const c = cfg as unknown;
|
||||
const c = cfg as QQBotSttConfigRoot;
|
||||
|
||||
// Prefer plugin-specific STT config.
|
||||
const channelStt = c?.channels?.qqbot?.stt;
|
||||
|
||||
@@ -44,6 +44,10 @@ let _log:
|
||||
| { info: (msg: string) => void; error: (msg: string) => void; debug?: (msg: string) => void }
|
||||
| undefined;
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function fetchJson(url: string, timeoutMs: number): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.get(
|
||||
@@ -80,12 +84,18 @@ async function fetchDistTags(): Promise<Record<string, string>> {
|
||||
for (const url of REGISTRIES) {
|
||||
try {
|
||||
const json = await fetchJson(url, 10_000);
|
||||
const tags = json["dist-tags"];
|
||||
if (tags && typeof tags === "object") {
|
||||
return tags;
|
||||
const tags = isRecord(json) ? json["dist-tags"] : undefined;
|
||||
if (isRecord(tags)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(tags).filter((entry): entry is [string, string] => {
|
||||
return typeof entry[1] === "string";
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
_log?.debug?.(`[qqbot:update-checker] ${url} failed: ${e.message}`);
|
||||
_log?.debug?.(
|
||||
`[qqbot:update-checker] ${url} failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error("all registries failed");
|
||||
@@ -141,7 +151,8 @@ export async function getUpdateInfo(): Promise<UpdateInfo> {
|
||||
const tags = await fetchDistTags();
|
||||
return buildUpdateInfo(tags);
|
||||
} catch (err: unknown) {
|
||||
_log?.debug?.(`[qqbot:update-checker] check failed: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
_log?.debug?.(`[qqbot:update-checker] check failed: ${message}`);
|
||||
return {
|
||||
current: CURRENT_VERSION,
|
||||
latest: null,
|
||||
@@ -149,7 +160,7 @@ export async function getUpdateInfo(): Promise<UpdateInfo> {
|
||||
alpha: null,
|
||||
hasUpdate: false,
|
||||
checkedAt: Date.now(),
|
||||
error: err.message,
|
||||
error: message,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -162,7 +173,7 @@ export async function checkVersionExists(version: string): Promise<boolean> {
|
||||
try {
|
||||
const url = `${baseUrl}/${version}`;
|
||||
const json = await fetchJson(url, 10_000);
|
||||
if (json && json.version === version) {
|
||||
if (isRecord(json) && json.version === version) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -194,9 +194,42 @@ export interface TTSConfig {
|
||||
speed?: number;
|
||||
}
|
||||
|
||||
type QQBotTtsProviderConfig = {
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
authStyle?: string;
|
||||
queryParams?: Record<string, string>;
|
||||
};
|
||||
|
||||
type QQBotTtsBlock = QQBotTtsProviderConfig & {
|
||||
model?: string;
|
||||
voice?: string;
|
||||
speed?: number;
|
||||
};
|
||||
|
||||
type QQBotMessagesTtsConfig = {
|
||||
auto?: string;
|
||||
enabled?: boolean;
|
||||
provider?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
type QQBotTtsConfigRoot = {
|
||||
channels?: {
|
||||
qqbot?: {
|
||||
tts?: QQBotTtsBlock & { enabled?: boolean; provider?: string };
|
||||
};
|
||||
};
|
||||
models?: {
|
||||
providers?: Record<string, QQBotTtsProviderConfig>;
|
||||
};
|
||||
messages?: {
|
||||
tts?: QQBotMessagesTtsConfig;
|
||||
};
|
||||
};
|
||||
|
||||
function resolveTTSFromBlock(
|
||||
block: Record<string, unknown>,
|
||||
providerCfg: Record<string, unknown> | undefined,
|
||||
block: QQBotTtsBlock,
|
||||
providerCfg: QQBotTtsProviderConfig | undefined,
|
||||
): TTSConfig | null {
|
||||
const baseUrl: string | undefined = block?.baseUrl || providerCfg?.baseUrl;
|
||||
const apiKey: string | undefined = block?.apiKey || providerCfg?.apiKey;
|
||||
@@ -228,7 +261,7 @@ function resolveTTSFromBlock(
|
||||
}
|
||||
|
||||
export function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null {
|
||||
const c = cfg as unknown;
|
||||
const c = cfg as QQBotTtsConfigRoot;
|
||||
|
||||
// Prefer plugin-specific TTS config first.
|
||||
const channelTts = c?.channels?.qqbot?.tts;
|
||||
@@ -245,7 +278,7 @@ export function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null
|
||||
const msgTts = c?.messages?.tts;
|
||||
if (msgTts && msgTts.auto !== "off" && msgTts.auto !== "disabled") {
|
||||
const providerId: string = msgTts?.provider || "openai";
|
||||
const providerBlock = msgTts?.[providerId];
|
||||
const providerBlock = msgTts?.[providerId] as QQBotTtsBlock | undefined;
|
||||
const providerCfg = c?.models?.providers?.[providerId];
|
||||
const result = resolveTTSFromBlock(providerBlock ?? {}, providerCfg);
|
||||
if (result) {
|
||||
|
||||
@@ -18,7 +18,7 @@ const { waitForTransportReadyMock, spawnSignalDaemonMock, streamMock } =
|
||||
getSignalToolResultTestMocks();
|
||||
|
||||
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
|
||||
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
|
||||
type MonitorSignalProviderOptions = NonNullable<Parameters<typeof monitorSignalProvider>[0]>;
|
||||
|
||||
function createMonitorRuntime() {
|
||||
return {
|
||||
|
||||
@@ -28,7 +28,7 @@ const {
|
||||
} = getSignalToolResultTestMocks();
|
||||
|
||||
const SIGNAL_BASE_URL = "http://127.0.0.1:8080";
|
||||
type MonitorSignalProviderOptions = Parameters<typeof monitorSignalProvider>[0];
|
||||
type MonitorSignalProviderOptions = NonNullable<Parameters<typeof monitorSignalProvider>[0]>;
|
||||
|
||||
async function runMonitorWithMocks(opts: MonitorSignalProviderOptions) {
|
||||
return monitorSignalProvider({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { ClientRequest, IncomingMessage, RequestOptions } from "node:http";
|
||||
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
|
||||
// Mock http and https modules before importing the client
|
||||
@@ -21,6 +22,27 @@ let sendFileUrl: typeof import("./client.js").sendFileUrl;
|
||||
let fetchChatUsers: typeof import("./client.js").fetchChatUsers;
|
||||
let resolveLegacyWebhookNameToChatUserId: typeof import("./client.js").resolveLegacyWebhookNameToChatUserId;
|
||||
|
||||
type RequestCallback = (res: IncomingMessage) => void;
|
||||
type MockRequestHandler = (
|
||||
url: string | URL,
|
||||
options: RequestOptions,
|
||||
callback?: RequestCallback,
|
||||
) => ClientRequest;
|
||||
|
||||
function createMockResponseEmitter(statusCode: number): IncomingMessage {
|
||||
const res = new EventEmitter() as Partial<IncomingMessage>;
|
||||
res.statusCode = statusCode;
|
||||
return res as IncomingMessage;
|
||||
}
|
||||
|
||||
function createMockRequestEmitter(): ClientRequest {
|
||||
const req = new EventEmitter() as Partial<ClientRequest>;
|
||||
req.write = vi.fn() as ClientRequest["write"];
|
||||
req.end = vi.fn() as ClientRequest["end"];
|
||||
req.destroy = vi.fn() as ClientRequest["destroy"];
|
||||
return req as ClientRequest;
|
||||
}
|
||||
|
||||
async function settleTimers<T>(promise: Promise<T>): Promise<T> {
|
||||
await Promise.resolve();
|
||||
await vi.runAllTimersAsync();
|
||||
@@ -29,20 +51,16 @@ async function settleTimers<T>(promise: Promise<T>): Promise<T> {
|
||||
|
||||
function mockResponse(statusCode: number, body: string) {
|
||||
const httpsRequest = vi.mocked(https.request);
|
||||
httpsRequest.mockImplementation((_url: unknown, _opts: unknown, callback: unknown) => {
|
||||
const res = new EventEmitter() as unknown;
|
||||
res.statusCode = statusCode;
|
||||
httpsRequest.mockImplementation(((...args) => {
|
||||
const callback = args[2];
|
||||
const res = createMockResponseEmitter(statusCode);
|
||||
process.nextTick(() => {
|
||||
callback(res);
|
||||
callback?.(res);
|
||||
res.emit("data", Buffer.from(body));
|
||||
res.emit("end");
|
||||
});
|
||||
const req = new EventEmitter() as unknown;
|
||||
req.write = vi.fn();
|
||||
req.end = vi.fn();
|
||||
req.destroy = vi.fn();
|
||||
return req;
|
||||
});
|
||||
return createMockRequestEmitter();
|
||||
}) as MockRequestHandler);
|
||||
}
|
||||
|
||||
function mockSuccessResponse() {
|
||||
@@ -156,18 +174,15 @@ function mockUserListResponseImpl(
|
||||
users: Array<{ user_id: number; username: string; nickname: string }>,
|
||||
once: boolean,
|
||||
) {
|
||||
const httpsGet = vi.mocked((https as unknown).get);
|
||||
const impl = (_url: unknown, _opts: unknown, callback: unknown) => {
|
||||
const res = new EventEmitter() as unknown;
|
||||
res.statusCode = 200;
|
||||
const httpsGet = vi.mocked(https.get);
|
||||
const impl: MockRequestHandler = (_url, _opts, callback) => {
|
||||
const res = createMockResponseEmitter(200);
|
||||
process.nextTick(() => {
|
||||
callback(res);
|
||||
callback?.(res);
|
||||
res.emit("data", Buffer.from(JSON.stringify({ success: true, data: { users } })));
|
||||
res.emit("end");
|
||||
});
|
||||
const req = new EventEmitter() as unknown;
|
||||
req.destroy = vi.fn();
|
||||
return req;
|
||||
return createMockRequestEmitter();
|
||||
};
|
||||
if (once) {
|
||||
httpsGet.mockImplementationOnce(impl);
|
||||
@@ -251,7 +266,7 @@ describe("resolveLegacyWebhookNameToChatUserId", () => {
|
||||
incomingUrl: baseUrl,
|
||||
mutableWebhookUsername: "anyone",
|
||||
});
|
||||
const httpsGet = vi.mocked((https as unknown).get);
|
||||
const httpsGet = vi.mocked(https.get);
|
||||
expect(httpsGet).toHaveBeenCalledWith(
|
||||
expect.stringContaining("method=user_list"),
|
||||
expect.any(Object),
|
||||
@@ -274,7 +289,7 @@ describe("resolveLegacyWebhookNameToChatUserId", () => {
|
||||
|
||||
expect(result1).toBe(4);
|
||||
expect(result2).toBe(9);
|
||||
const httpsGet = vi.mocked((https as unknown).get);
|
||||
const httpsGet = vi.mocked(https.get);
|
||||
expect(httpsGet).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
@@ -283,12 +298,11 @@ describe("fetchChatUsers", () => {
|
||||
installFakeTimerHarness();
|
||||
|
||||
it("filters malformed user entries while keeping valid ones", async () => {
|
||||
const httpsGet = vi.mocked((https as unknown).get);
|
||||
httpsGet.mockImplementation((_url: unknown, _opts: unknown, callback: unknown) => {
|
||||
const res = new EventEmitter() as unknown;
|
||||
res.statusCode = 200;
|
||||
const httpsGet = vi.mocked(https.get);
|
||||
httpsGet.mockImplementation(((_url, _opts, callback) => {
|
||||
const res = createMockResponseEmitter(200);
|
||||
process.nextTick(() => {
|
||||
callback(res);
|
||||
callback?.(res);
|
||||
res.emit(
|
||||
"data",
|
||||
Buffer.from(
|
||||
@@ -305,10 +319,8 @@ describe("fetchChatUsers", () => {
|
||||
);
|
||||
res.emit("end");
|
||||
});
|
||||
const req = new EventEmitter() as unknown;
|
||||
req.destroy = vi.fn();
|
||||
return req;
|
||||
});
|
||||
return createMockRequestEmitter();
|
||||
}) as MockRequestHandler);
|
||||
|
||||
const users = await fetchChatUsers(
|
||||
"https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22test%22",
|
||||
@@ -324,7 +336,7 @@ describe("fetchChatUsers", () => {
|
||||
|
||||
await fetchChatUsers(freshUrl);
|
||||
|
||||
const httpsGet = vi.mocked((https as unknown).get);
|
||||
const httpsGet = vi.mocked(https.get);
|
||||
expect(httpsGet.mock.calls[0]?.[1]).toMatchObject({ rejectUnauthorized: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,9 +167,11 @@ export async function fetchChatUsers(
|
||||
return;
|
||||
}
|
||||
const transport = parsedUrl.protocol === "https:" ? https : http;
|
||||
const requestOptions: http.RequestOptions | https.RequestOptions =
|
||||
parsedUrl.protocol === "https:" ? { rejectUnauthorized: !allowInsecureSsl } : {};
|
||||
|
||||
transport
|
||||
.get(listUrl, { rejectUnauthorized: !allowInsecureSsl } as unknown, (res) => {
|
||||
.get(listUrl, requestOptions, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (c: Buffer) => {
|
||||
data += c.toString();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawPluginApi } from "./api.js";
|
||||
import register from "./index.js";
|
||||
|
||||
@@ -248,6 +248,7 @@ export function createXaiWebSearchProvider(): WebSearchProviderPlugin {
|
||||
),
|
||||
inlineCitations: resolveXaiInlineCitations(searchConfig),
|
||||
cacheTtlMs: resolveCacheTtlMs(searchConfig?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
cacheTtlMs: resolveCacheTtlMs(searchConfig?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -617,7 +617,10 @@ function buildMessageToolDescription(options?: {
|
||||
desc += ` Other configured channels: ${otherChannels.join(", ")}.`;
|
||||
}
|
||||
|
||||
return appendMessageToolReadHint(desc, allActions);
|
||||
return appendMessageToolReadHint(
|
||||
desc,
|
||||
Array.from(allActions) as Iterable<ChannelMessageActionName | "send">,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"openclaw/extension-api": ["./src/extensionAPI.ts"],
|
||||
"openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"],
|
||||
"openclaw/plugin-sdk/*": ["./src/plugin-sdk/*.ts"],
|
||||
"@openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"],
|
||||
"@openclaw/plugin-sdk/*": ["./src/plugin-sdk/*.ts"],
|
||||
"openclaw/plugin-sdk/account-id": ["./src/plugin-sdk/account-id.ts"],
|
||||
"@openclaw/*": ["./extensions/*"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user