mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
fix(plugins): reject blank runtime entries
This commit is contained in:
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc.
|
||||
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.
|
||||
- Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc.
|
||||
- Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis.
|
||||
|
||||
@@ -928,6 +928,34 @@ describe("discoverOpenClawPlugins", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects blank package runtimeExtensions before falling back to inferred entries", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "extensions", "runtime-blank-pack");
|
||||
mkdirSafe(path.join(pluginDir, "src"));
|
||||
mkdirSafe(path.join(pluginDir, "dist"));
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: pluginDir,
|
||||
packageName: "@openclaw/runtime-blank-pack",
|
||||
extensions: ["./src/index.ts"],
|
||||
runtimeExtensions: [" "],
|
||||
});
|
||||
writePluginEntry(path.join(pluginDir, "src", "index.ts"));
|
||||
writePluginEntry(path.join(pluginDir, "dist", "index.js"));
|
||||
|
||||
const result = await discoverWithStateDir(stateDir, {});
|
||||
|
||||
expectCandidatePresence(result, { absent: ["runtime-blank-pack"] });
|
||||
expect(
|
||||
result.diagnostics.some(
|
||||
(entry) =>
|
||||
entry.level === "error" &&
|
||||
entry.message.includes("openclaw.runtimeExtensions[0]") &&
|
||||
entry.message.includes("non-empty string"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("infers built dist entries for installed TypeScript package plugins", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "extensions", "built-peer-pack");
|
||||
|
||||
@@ -985,6 +985,37 @@ describe("installPluginFromArchive", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects package installs when runtimeExtensions contains a blank entry", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true });
|
||||
fs.mkdirSync(path.join(pluginDir, "dist"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "runtime-blank-plugin",
|
||||
version: "1.0.0",
|
||||
openclaw: {
|
||||
extensions: ["./src/index.ts"],
|
||||
runtimeExtensions: [" "],
|
||||
},
|
||||
}),
|
||||
);
|
||||
fs.writeFileSync(path.join(pluginDir, "src", "index.ts"), "export {};\n");
|
||||
fs.writeFileSync(path.join(pluginDir, "dist", "index.js"), "export {};\n");
|
||||
|
||||
const result = await installPluginFromDir({
|
||||
dirPath: pluginDir,
|
||||
extensionsDir,
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.INVALID_OPENCLAW_EXTENSIONS);
|
||||
expect(result.error).toContain("openclaw.runtimeExtensions[0]");
|
||||
expect(result.error).toContain("non-empty string");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects package installs when runtimeSetupEntry is missing", async () => {
|
||||
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
|
||||
fs.mkdirSync(path.join(pluginDir, "src"), { recursive: true });
|
||||
|
||||
@@ -21,6 +21,8 @@ type RuntimeExtensionsResolution =
|
||||
| { ok: true; runtimeExtensions: string[] }
|
||||
| { ok: false; error: string };
|
||||
|
||||
type PackageManifestStringList = { ok: true; entries: string[] } | { ok: false; error: string };
|
||||
|
||||
function runtimeExtensionsLengthMismatchMessage(params: {
|
||||
runtimeExtensionsLength: number;
|
||||
extensionsLength: number;
|
||||
@@ -31,11 +33,25 @@ function runtimeExtensionsLengthMismatchMessage(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizePackageManifestStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
function readPackageManifestStringList(params: {
|
||||
fieldName: string;
|
||||
value: unknown;
|
||||
}): PackageManifestStringList {
|
||||
if (!Array.isArray(params.value)) {
|
||||
return { ok: true, entries: [] };
|
||||
}
|
||||
return value.map((entry) => normalizeOptionalString(entry) ?? "").filter(Boolean);
|
||||
const entries: string[] = [];
|
||||
for (const [index, entry] of params.value.entries()) {
|
||||
const normalized = normalizeOptionalString(entry);
|
||||
if (!normalized) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `package.json ${params.fieldName}[${index}] must be a non-empty string`,
|
||||
};
|
||||
}
|
||||
entries.push(normalized);
|
||||
}
|
||||
return { ok: true, entries };
|
||||
}
|
||||
|
||||
function resolvePackageRuntimeExtensionEntries(params: {
|
||||
@@ -43,7 +59,14 @@ function resolvePackageRuntimeExtensionEntries(params: {
|
||||
extensions: readonly string[];
|
||||
}): RuntimeExtensionsResolution {
|
||||
const packageManifest = getPackageManifestMetadata(params.manifest ?? undefined);
|
||||
const runtimeExtensions = normalizePackageManifestStringList(packageManifest?.runtimeExtensions);
|
||||
const runtimeExtensionsResult = readPackageManifestStringList({
|
||||
fieldName: "openclaw.runtimeExtensions",
|
||||
value: packageManifest?.runtimeExtensions,
|
||||
});
|
||||
if (!runtimeExtensionsResult.ok) {
|
||||
return runtimeExtensionsResult;
|
||||
}
|
||||
const runtimeExtensions = runtimeExtensionsResult.entries;
|
||||
if (runtimeExtensions.length === 0) {
|
||||
return { ok: true, runtimeExtensions: [] };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user