mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:40:43 +00:00
fix: restore bundled plugin SDK alias
This commit is contained in:
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/startup: restore bundled plugin `openclaw/plugin-sdk/*` resolution from packaged installs and external runtime-deps stage roots, so Telegram/Discord no longer crash-loop with `Cannot find package 'openclaw'` after missing dependency repair.
|
||||
- CLI/Claude: run the same prompt-build hooks and trigger/channel context on `claude-cli` turns as on direct embedded runs, keeping Claude Code sessions aligned with OpenClaw workspace identity, routing, and hook-driven prompt mutations. (#70625) Thanks @mbelinky.
|
||||
|
||||
## 2026.4.22
|
||||
|
||||
@@ -9,6 +9,8 @@ const ALLOWED_PLUGIN_SDK_FIXTURE_IMPORTS = new Set([
|
||||
// Intentional jiti alias regression test.
|
||||
'src/plugins/loader.git-path-regression.test.ts:`import { resolveOutboundSendDep } from "openclaw/plugin-sdk/infra-runtime";',
|
||||
'src/plugins/loader.git-path-regression.test.ts: "openclaw/plugin-sdk/infra-runtime": ${JSON.stringify(copiedChannelRuntimeShim)},',
|
||||
// Intentional packaged bundled-plugin SDK alias regression tests.
|
||||
'src/plugins/loader.test.ts:`import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";`,',
|
||||
]);
|
||||
|
||||
const LOADER_FIXTURE_TEST_FILES = [
|
||||
|
||||
@@ -1454,6 +1454,181 @@ module.exports = {
|
||||
expect(registry.plugins.find((entry) => entry.id === "alpha")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads bundled plugins with plugin-sdk imports from an external stage dir", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
const bundledDir = path.join(packageRoot, "dist", "extensions");
|
||||
const pluginRoot = path.join(bundledDir, "telegram");
|
||||
fs.mkdirSync(path.join(packageRoot, "dist", "plugin-sdk"), { recursive: true });
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", version: "2026.4.22", type: "module" }),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "dist", "plugin-sdk", "text-runtime.js"),
|
||||
[
|
||||
`export function normalizeLowercaseStringOrEmpty(value) {`,
|
||||
` return typeof value === "string" ? value.toLowerCase() : "";`,
|
||||
`}`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "index.js"),
|
||||
[
|
||||
`import runtimeDep from "external-runtime";`,
|
||||
`import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";`,
|
||||
`export default {`,
|
||||
` id: "telegram",`,
|
||||
` register(api) {`,
|
||||
` api.registerCommand({`,
|
||||
` name: "external-runtime",`,
|
||||
` handler: () => normalizeLowercaseStringOrEmpty(runtimeDep.marker),`,
|
||||
` });`,
|
||||
` },`,
|
||||
`};`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/telegram",
|
||||
version: "1.0.0",
|
||||
type: "module",
|
||||
dependencies: {
|
||||
"external-runtime": "1.0.0",
|
||||
},
|
||||
openclaw: { extensions: ["./index.js"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "telegram",
|
||||
enabledByDefault: true,
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
process.env.OPENCLAW_PLUGIN_STAGE_DIR = stageDir;
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
bundledRuntimeDepsInstaller: ({ installRoot }) => {
|
||||
const depRoot = path.join(installRoot, "node_modules", "external-runtime");
|
||||
fs.mkdirSync(depRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "external-runtime",
|
||||
version: "1.0.0",
|
||||
type: "module",
|
||||
exports: "./index.js",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "index.js"),
|
||||
"export default { marker: 'SDK-OK' };\n",
|
||||
"utf-8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === "telegram")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads bundled plugins with plugin-sdk imports from a package dist root", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const bundledDir = path.join(packageRoot, "dist", "extensions");
|
||||
const pluginRoot = path.join(bundledDir, "discord");
|
||||
fs.mkdirSync(path.join(packageRoot, "dist", "plugin-sdk"), { recursive: true });
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", version: "2026.4.22", type: "module" }),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "dist", "plugin-sdk", "text-runtime.js"),
|
||||
"export const normalizeLowercaseStringOrEmpty = (value) => String(value).toLowerCase();\n",
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "index.js"),
|
||||
[
|
||||
`import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";`,
|
||||
`export default {`,
|
||||
` id: "discord",`,
|
||||
` register(api) {`,
|
||||
` api.registerCommand({ name: normalizeLowercaseStringOrEmpty("DISCORD"), handler: () => "ok" });`,
|
||||
` },`,
|
||||
`};`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/discord",
|
||||
version: "1.0.0",
|
||||
type: "module",
|
||||
openclaw: { extensions: ["./index.js"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "discord",
|
||||
enabledByDefault: true,
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === "discord")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads dist-runtime wrappers from an external stage dir", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
|
||||
@@ -599,6 +599,7 @@ function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
}
|
||||
}
|
||||
}
|
||||
ensureOpenClawPluginSdkAlias(mirrorDistRoot);
|
||||
return mirrorExtensionsRoot;
|
||||
}
|
||||
|
||||
@@ -631,6 +632,76 @@ function copyBundledPluginRuntimeRoot(sourceRoot: string, targetRoot: string): v
|
||||
}
|
||||
}
|
||||
|
||||
function writeRuntimeJsonFile(targetPath: string, value: unknown): void {
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function hasRuntimeDefaultExport(sourcePath: string): boolean {
|
||||
const text = fs.readFileSync(sourcePath, "utf8");
|
||||
return /\bexport\s+default\b/u.test(text) || /\bas\s+default\b/u.test(text);
|
||||
}
|
||||
|
||||
function writeRuntimeModuleWrapper(sourcePath: string, targetPath: string): void {
|
||||
const specifier = path.relative(path.dirname(targetPath), sourcePath).replaceAll(path.sep, "/");
|
||||
const normalizedSpecifier = specifier.startsWith(".") ? specifier : `./${specifier}`;
|
||||
const defaultForwarder = hasRuntimeDefaultExport(sourcePath)
|
||||
? [
|
||||
`import defaultModule from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
`let defaultExport = defaultModule;`,
|
||||
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
]
|
||||
: [
|
||||
`import * as module from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
`let defaultExport = "default" in module ? module.default : module;`,
|
||||
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
];
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
targetPath,
|
||||
[
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
function ensureOpenClawPluginSdkAlias(distRoot: string): void {
|
||||
const pluginSdkDir = path.join(distRoot, "plugin-sdk");
|
||||
if (!fs.existsSync(pluginSdkDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aliasDir = path.join(distRoot, "extensions", "node_modules", "openclaw");
|
||||
const pluginSdkAliasDir = path.join(aliasDir, "plugin-sdk");
|
||||
writeRuntimeJsonFile(path.join(aliasDir, "package.json"), {
|
||||
name: "openclaw",
|
||||
type: "module",
|
||||
exports: {
|
||||
"./plugin-sdk": "./plugin-sdk/index.js",
|
||||
"./plugin-sdk/*": "./plugin-sdk/*.js",
|
||||
},
|
||||
});
|
||||
fs.rmSync(pluginSdkAliasDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(pluginSdkAliasDir, { recursive: true });
|
||||
for (const entry of fs.readdirSync(pluginSdkDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile() || path.extname(entry.name) !== ".js") {
|
||||
continue;
|
||||
}
|
||||
writeRuntimeModuleWrapper(
|
||||
path.join(pluginSdkDir, entry.name),
|
||||
path.join(pluginSdkAliasDir, entry.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function remapBundledPluginRuntimePath(params: {
|
||||
source: string | undefined;
|
||||
pluginRoot: string;
|
||||
@@ -2137,6 +2208,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
);
|
||||
}
|
||||
}
|
||||
ensureOpenClawPluginSdkAlias(path.dirname(path.dirname(pluginRoot)));
|
||||
if (path.resolve(installRoot) !== path.resolve(pluginRoot)) {
|
||||
registerBundledRuntimeDependencyNodePath(installRoot);
|
||||
runtimePluginRoot = mirrorBundledPluginRuntimeRoot({
|
||||
|
||||
Reference in New Issue
Block a user