mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
fix: stabilize channel configured probes
This commit is contained in:
@@ -11,23 +11,17 @@ import {
|
||||
listPotentialConfiguredChannelIds,
|
||||
} from "./config-presence.js";
|
||||
|
||||
vi.mock("./plugins/bundled-ids.js", () => ({
|
||||
listBundledChannelPluginIds: () => ["matrix"],
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/persisted-auth-state.js", () => ({
|
||||
listBundledChannelIdsWithPersistedAuthState: () => ["matrix"],
|
||||
hasBundledChannelPersistedAuthState: ({
|
||||
channelId,
|
||||
env,
|
||||
}: {
|
||||
channelId: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) => channelId === "matrix" && env?.OPENCLAW_STATE_DIR?.includes("persisted-matrix"),
|
||||
}));
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
const matrixPresenceOptions = {
|
||||
channelIds: ["matrix"],
|
||||
persistedAuthStateProbe: {
|
||||
listChannelIds: () => ["matrix"],
|
||||
hasState: ({ channelId, env }: { channelId: string; env?: NodeJS.ProcessEnv }) =>
|
||||
channelId === "matrix" && Boolean(env?.OPENCLAW_STATE_DIR?.includes("persisted-matrix")),
|
||||
},
|
||||
};
|
||||
|
||||
function makeTempStateDir() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-channel-config-presence-"));
|
||||
tempDirs.push(dir);
|
||||
@@ -41,7 +35,7 @@ function expectPotentialConfiguredChannelCase(params: {
|
||||
expectedConfigured: boolean;
|
||||
options?: Parameters<typeof listPotentialConfiguredChannelIds>[2];
|
||||
}) {
|
||||
const options = params.options ?? {};
|
||||
const options = params.options ?? matrixPresenceOptions;
|
||||
expect(listPotentialConfiguredChannelIds(params.cfg, params.env, options)).toEqual(
|
||||
params.expectedIds,
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { listBundledChannelPluginIds } from "./plugins/bundled-ids.js";
|
||||
const IGNORED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]);
|
||||
|
||||
type ChannelPresenceOptions = {
|
||||
channelIds?: readonly string[];
|
||||
includePersistedAuthState?: boolean;
|
||||
persistedAuthStateProbe?: {
|
||||
listChannelIds: () => readonly string[];
|
||||
@@ -120,7 +121,7 @@ export function listPotentialConfiguredChannelPresenceSignals(
|
||||
signals.push({ channelId, source });
|
||||
};
|
||||
const configuredChannelIds = new Set<string>();
|
||||
const channelIds = listBundledChannelPluginIds(env);
|
||||
const channelIds = options.channelIds ?? listBundledChannelPluginIds(env);
|
||||
const channelEnvPrefixes = listChannelEnvPrefixes(channelIds);
|
||||
const channels = isRecord(cfg.channels) ? cfg.channels : null;
|
||||
if (channels) {
|
||||
@@ -164,7 +165,7 @@ function hasEnvConfiguredChannel(
|
||||
env: NodeJS.ProcessEnv,
|
||||
options: ChannelPresenceOptions = {},
|
||||
): boolean {
|
||||
const channelIds = listBundledChannelPluginIds(env);
|
||||
const channelIds = options.channelIds ?? listBundledChannelPluginIds(env);
|
||||
const channelEnvPrefixes = listChannelEnvPrefixes(channelIds);
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (!hasNonEmptyString(value)) {
|
||||
|
||||
@@ -12,7 +12,9 @@ afterEach(() => {
|
||||
for (const tempDir of tempDirs.splice(0)) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
vi.doUnmock("jiti");
|
||||
});
|
||||
|
||||
function createTempDir(): string {
|
||||
@@ -61,29 +63,39 @@ describe("channel plugin module loader helpers", () => {
|
||||
expect(createJiti).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects TypeScript modules without creating Jiti", async () => {
|
||||
const createJiti = vi.fn(() => {
|
||||
throw new Error("channel module loader must not create jiti");
|
||||
});
|
||||
it("loads TypeScript channel plugin modules through Jiti when no native hook exists", async () => {
|
||||
const loadWithJiti = vi.fn((target: string) => ({
|
||||
loadedBy: "jiti",
|
||||
target,
|
||||
}));
|
||||
const createJiti = vi.fn(() => loadWithJiti);
|
||||
vi.resetModules();
|
||||
vi.doMock("jiti", () => ({
|
||||
createJiti,
|
||||
}));
|
||||
const loaderModule = await importFreshModule<typeof import("./module-loader.js")>(
|
||||
import.meta.url,
|
||||
"./module-loader.js?scope=source-ts-native-hook",
|
||||
"./module-loader.js?scope=source-ts-jiti-fallback",
|
||||
);
|
||||
const rootDir = createTempDir();
|
||||
const modulePath = path.join(rootDir, "extensions", "demo", "index.ts");
|
||||
fs.mkdirSync(path.dirname(modulePath), { recursive: true });
|
||||
fs.writeFileSync(modulePath, "export const ok = true;\n", "utf8");
|
||||
|
||||
expect(() =>
|
||||
expect(
|
||||
loaderModule.loadChannelPluginModule({
|
||||
modulePath,
|
||||
rootDir,
|
||||
}),
|
||||
).toThrow(/must be built JavaScript/u);
|
||||
expect(createJiti).not.toHaveBeenCalled();
|
||||
).toEqual({
|
||||
loadedBy: "jiti",
|
||||
target: modulePath,
|
||||
});
|
||||
expect(createJiti).toHaveBeenCalledOnce();
|
||||
expect(createJiti).toHaveBeenCalledWith(
|
||||
expect.stringContaining("module-loader.ts"),
|
||||
expect.objectContaining({ tryNative: false }),
|
||||
);
|
||||
expect(loadWithJiti).toHaveBeenCalledWith(modulePath);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,10 +2,15 @@ import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { openBoundaryFileSync } from "../../infra/boundary-file-read.js";
|
||||
import {
|
||||
getCachedPluginJitiLoader,
|
||||
type PluginJitiLoaderCache,
|
||||
} from "../../plugins/jiti-loader-cache.js";
|
||||
import { isJavaScriptModulePath } from "../../plugins/native-module-require.js";
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url);
|
||||
const SOURCE_MODULE_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"]);
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
function hasNativeSourceRequireHook(modulePath: string): boolean {
|
||||
const extension = path.extname(modulePath).toLowerCase();
|
||||
@@ -15,13 +20,35 @@ function hasNativeSourceRequireHook(modulePath: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isSourceModulePath(modulePath: string): boolean {
|
||||
return SOURCE_MODULE_EXTENSIONS.has(path.extname(modulePath).toLowerCase());
|
||||
}
|
||||
|
||||
function loadModuleWithJiti(modulePath: string): unknown {
|
||||
const loadWithJiti = getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
jitiFilename: import.meta.url,
|
||||
tryNative: false,
|
||||
cacheScopeKey: "channel-plugin-module-loader",
|
||||
});
|
||||
return loadWithJiti(modulePath);
|
||||
}
|
||||
|
||||
function loadModule(modulePath: string): unknown {
|
||||
if (!isJavaScriptModulePath(modulePath) && !hasNativeSourceRequireHook(modulePath)) {
|
||||
if (isSourceModulePath(modulePath)) {
|
||||
return loadModuleWithJiti(modulePath);
|
||||
}
|
||||
throw new Error(`channel plugin module must be built JavaScript: ${modulePath}`);
|
||||
}
|
||||
try {
|
||||
return nodeRequire(modulePath);
|
||||
} catch (error) {
|
||||
if (isSourceModulePath(modulePath)) {
|
||||
return loadModuleWithJiti(modulePath);
|
||||
}
|
||||
throw new Error(`failed to load channel plugin module with native require: ${modulePath}`, {
|
||||
cause: error,
|
||||
});
|
||||
|
||||
@@ -1,30 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { isChannelConfigured } from "./channel-configured.js";
|
||||
|
||||
vi.mock("../channels/plugins/configured-state.js", () => ({
|
||||
hasBundledChannelConfiguredState: ({
|
||||
channelId,
|
||||
env,
|
||||
}: {
|
||||
channelId: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) => {
|
||||
if (channelId === "telegram") {
|
||||
return Boolean(env?.TELEGRAM_BOT_TOKEN);
|
||||
}
|
||||
if (channelId === "discord") {
|
||||
return Boolean(env?.DISCORD_BOT_TOKEN);
|
||||
}
|
||||
if (channelId === "slack") {
|
||||
return Boolean(env?.SLACK_BOT_TOKEN);
|
||||
}
|
||||
if (channelId === "irc") {
|
||||
return Boolean(env?.IRC_HOST && env?.IRC_NICK);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/bootstrap-registry.js", () => ({
|
||||
getBootstrapChannelPlugin: () => undefined,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user