Files
openclaw/src/plugins/plugin-sdk-native-resolver.test.ts
Peter Steinberger 1507a9701b refactor: centralize inbound supplemental context
* refactor: centralize inbound supplemental context

* refactor: trim supplemental finalizer typing

* docs: clarify supplemental context projection

* refactor: move inbound finalization into core

* refactor: simplify channel inbound facts

* refactor: fold supplemental media into inbound finalizer

* refactor: migrate channel inbound callers to builder

* docs: mark inbound finalizer compat types deprecated

* refactor: wire runtime turn context builder

* refactor: replace channel turn runtime API

* fix: respect discord quote visibility

* fix: avoid deprecated line dispatch helper

* refactor: deprecate channel message SDK seams

* docs: trim channel outbound SDK page

* test: migrate irc inbound assertion

* refactor: deprecate outbound SDK facades

* refactor: deprecate channel helper SDK facades

* refactor: deprecate channel streaming SDK facade

* refactor: move direct dm helpers into inbound SDK

* chore: mark legacy test-utils SDK alias deprecated

* refactor: remove unused allow-from read helper

* refactor: route remaining channel dispatch through core

* refactor: enforce modern extension SDK imports

* test: give slow image root tests more time

* ci: support node fallback on windows

* fix: add transcripts tool display metadata

* refactor: trim legacy channel test seams

* fix: preserve channel compat after rebase

* fix: keep deprecated channel inbound aliases

* fix: preserve discord thread context visibility

* fix: clean final rebase conflicts

* fix: preserve channel message dispatch aliases

* fix: sync channel refactor after rebase

* fix: sync channel refactor after latest main

* fix: dedupe memory-core subagent mock

* test: align clickclack inbound dispatch assertions

* fix: sync plugin sdk api hash after rebase

* fix: sync channel refactor after latest main

* fix: sync plugin sdk api hash after rebase

* fix: sync plugin sdk api hash after latest main

* test: remove stale inbound context awaits
2026-05-27 09:26:06 +01:00

248 lines
11 KiB
TypeScript

import fs from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
installOpenClawPluginSdkNativeResolver,
resetOpenClawPluginSdkNativeResolverForTest,
} from "./plugin-sdk-native-resolver.js";
afterEach(() => {
resetOpenClawPluginSdkNativeResolverForTest();
});
function writeJsonFile(targetPath: string, value: unknown): void {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function writeFakeOpenClawPackage(root: string): { distRoot: string; loaderModulePath: string } {
writeJsonFile(path.join(root, "package.json"), {
name: "openclaw",
type: "module",
bin: {
openclaw: "./openclaw.mjs",
},
exports: {
"./cli-entry": "./dist/cli-entry.js",
"./plugin-sdk": "./dist/plugin-sdk/root-alias.cjs",
"./plugin-sdk/channel-message": "./dist/plugin-sdk/channel-message.js",
"./plugin-sdk/channel-outbound": "./dist/plugin-sdk/channel-outbound.js",
"./plugin-sdk/source-only": "./dist/plugin-sdk/source-only.js",
},
});
fs.writeFileSync(path.join(root, "openclaw.mjs"), "#!/usr/bin/env node\n", "utf8");
const distRoot = path.join(root, "dist");
const pluginSdkDir = path.join(distRoot, "plugin-sdk");
fs.mkdirSync(pluginSdkDir, { recursive: true });
fs.writeFileSync(path.join(pluginSdkDir, "root-alias.cjs"), "module.exports = {};\n", "utf8");
fs.writeFileSync(
path.join(pluginSdkDir, "channel-message.js"),
['export * from "./channel-outbound.js";', ""].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginSdkDir, "channel-outbound.js"),
['export const defineChannelMessageAdapter = () => "adapter";', ""].join("\n"),
"utf8",
);
const loaderModulePath = path.join(distRoot, "plugins", "loader.js");
fs.mkdirSync(path.dirname(loaderModulePath), { recursive: true });
fs.writeFileSync(loaderModulePath, "export default {};\n", "utf8");
return { distRoot, loaderModulePath };
}
function writeExternalPluginEntry(root: string): string {
writeJsonFile(path.join(root, "package.json"), {
name: "external-plugin",
type: "module",
});
const entry = path.join(root, "dist", "runtime-api.js");
fs.mkdirSync(path.dirname(entry), { recursive: true });
fs.writeFileSync(entry, "export default {};\n", "utf8");
return entry;
}
describe("installOpenClawPluginSdkNativeResolver", () => {
it("keeps native aliases on JS dist artifacts when source files exist", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-source-resolver-"));
const { loaderModulePath } = writeFakeOpenClawPackage(root);
const sourceChannelOutboundPath = path.join(root, "src", "plugin-sdk", "channel-outbound.ts");
fs.mkdirSync(path.dirname(sourceChannelOutboundPath), { recursive: true });
fs.writeFileSync(sourceChannelOutboundPath, "export const sourceOnly = true;\n", "utf8");
const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin"));
const installedAliases = installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: externalPluginEntry,
pluginSdkResolution: "src",
});
expect(installedAliases).toContain("openclaw/plugin-sdk/channel-outbound");
const requireFromPlugin = createRequire(externalPluginEntry);
expect(fs.realpathSync(requireFromPlugin.resolve("openclaw/plugin-sdk/channel-outbound"))).toBe(
fs.realpathSync(path.join(root, "dist", "plugin-sdk", "channel-outbound.js")),
);
});
it("lets built external plugins resolve OpenClaw SDK subpaths with createRequire", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-resolver-"));
const { distRoot, loaderModulePath } = writeFakeOpenClawPackage(root);
const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin"));
const distMode = fs.statSync(distRoot).mode;
if (process.platform !== "win32") {
fs.chmodSync(distRoot, 0o555);
}
try {
const installedAliases = installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: externalPluginEntry,
pluginSdkResolution: "dist",
});
expect(installedAliases).toContain("openclaw/plugin-sdk/channel-outbound");
expect(fs.existsSync(path.join(distRoot, "extensions"))).toBe(false);
const requireFromPlugin = createRequire(externalPluginEntry);
expect(
fs.realpathSync(requireFromPlugin.resolve("openclaw/plugin-sdk/channel-outbound")),
).toBe(fs.realpathSync(path.join(root, "dist", "plugin-sdk", "channel-outbound.js")));
const sdk = requireFromPlugin("openclaw/plugin-sdk/channel-outbound") as {
defineChannelMessageAdapter?: () => string;
};
expect(sdk.defineChannelMessageAdapter?.()).toBe("adapter");
expect(() => requireFromPlugin.resolve("openclaw/not-plugin-sdk/channel-message")).toThrow();
} finally {
if (process.platform !== "win32") {
fs.chmodSync(distRoot, distMode);
}
}
});
it("does not resolve SDK aliases for parents outside registered plugin roots", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-guard-"));
const { loaderModulePath } = writeFakeOpenClawPackage(root);
const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin"));
const unrelatedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-outside-"));
const unrelatedEntry = path.join(unrelatedRoot, "runtime-api.js");
fs.mkdirSync(path.dirname(unrelatedEntry), { recursive: true });
fs.writeFileSync(unrelatedEntry, "export default {};\n", "utf8");
installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: externalPluginEntry,
pluginSdkResolution: "dist",
});
const requireFromPlugin = createRequire(externalPluginEntry);
const requireFromOutside = createRequire(unrelatedEntry);
expect(requireFromPlugin.resolve("openclaw/plugin-sdk/channel-outbound")).toBeTruthy();
expect(() => requireFromOutside.resolve("openclaw/plugin-sdk/channel-outbound")).toThrow();
});
it("does not register source-only SDK subpaths for native resolution", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-source-only-"));
const { loaderModulePath } = writeFakeOpenClawPackage(root);
const sourceOnlyPath = path.join(root, "src", "plugin-sdk", "source-only.ts");
fs.mkdirSync(path.dirname(sourceOnlyPath), { recursive: true });
fs.writeFileSync(sourceOnlyPath, "export const sourceOnly = true;\n", "utf8");
const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin"));
const installedAliases = installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: externalPluginEntry,
pluginSdkResolution: "src",
});
expect(installedAliases).toContain("openclaw/plugin-sdk/channel-outbound");
expect(installedAliases).not.toContain("openclaw/plugin-sdk/source-only");
const requireFromPlugin = createRequire(externalPluginEntry);
expect(() => requireFromPlugin.resolve("openclaw/plugin-sdk/source-only")).toThrow();
});
it("scopes private SSRF SDK aliases to bundled local IPC native parents", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-ssrf-"));
const { loaderModulePath } = writeFakeOpenClawPackage(root);
const internalPath = path.join(root, "dist", "plugin-sdk", "ssrf-runtime-internal.js");
fs.writeFileSync(internalPath, "export const ssrfInternal = true;\n", "utf8");
const ollamaEntry = path.join(root, "dist", "extensions", "ollama", "index.js");
const runtimeOllamaEntry = path.join(root, "dist-runtime", "extensions", "ollama", "index.js");
const browserEntry = path.join(root, "dist", "extensions", "browser", "index.js");
const runtimeBrowserEntry = path.join(
root,
"dist-runtime",
"extensions",
"browser",
"index.js",
);
const otherEntry = path.join(root, "dist", "extensions", "demo", "index.js");
fs.mkdirSync(path.dirname(ollamaEntry), { recursive: true });
fs.mkdirSync(path.dirname(runtimeOllamaEntry), { recursive: true });
fs.mkdirSync(path.dirname(browserEntry), { recursive: true });
fs.mkdirSync(path.dirname(runtimeBrowserEntry), { recursive: true });
fs.mkdirSync(path.dirname(otherEntry), { recursive: true });
fs.writeFileSync(ollamaEntry, "export default {};\n", "utf8");
fs.writeFileSync(runtimeOllamaEntry, "export default {};\n", "utf8");
fs.writeFileSync(browserEntry, "export default {};\n", "utf8");
fs.writeFileSync(runtimeBrowserEntry, "export default {};\n", "utf8");
fs.writeFileSync(otherEntry, "export default {};\n", "utf8");
const installedAliases = installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: ollamaEntry,
pluginSdkResolution: "dist",
});
installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: runtimeOllamaEntry,
pluginSdkResolution: "dist",
});
installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: browserEntry,
pluginSdkResolution: "dist",
});
installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: runtimeBrowserEntry,
pluginSdkResolution: "dist",
});
installOpenClawPluginSdkNativeResolver({
modulePath: loaderModulePath,
pluginModulePath: otherEntry,
pluginSdkResolution: "dist",
});
expect(installedAliases).toContain("openclaw/plugin-sdk/ssrf-runtime-internal");
const requireFromOllama = createRequire(ollamaEntry);
expect(
fs.realpathSync(requireFromOllama.resolve("openclaw/plugin-sdk/ssrf-runtime-internal")),
).toBe(fs.realpathSync(internalPath));
const requireFromRuntimeOllama = createRequire(runtimeOllamaEntry);
expect(
fs.realpathSync(
requireFromRuntimeOllama.resolve("openclaw/plugin-sdk/ssrf-runtime-internal"),
),
).toBe(fs.realpathSync(internalPath));
const requireFromBrowser = createRequire(browserEntry);
expect(
fs.realpathSync(requireFromBrowser.resolve("openclaw/plugin-sdk/ssrf-runtime-internal")),
).toBe(fs.realpathSync(internalPath));
const requireFromRuntimeBrowser = createRequire(runtimeBrowserEntry);
expect(
fs.realpathSync(
requireFromRuntimeBrowser.resolve("openclaw/plugin-sdk/ssrf-runtime-internal"),
),
).toBe(fs.realpathSync(internalPath));
const requireFromOther = createRequire(otherEntry);
expect(() => requireFromOther.resolve("openclaw/plugin-sdk/ssrf-runtime-internal")).toThrow();
});
});