mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: preserve runtime kind install fallback
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
9a688c953f0108f85f58c173e79c28363d846a592130abec04cafbcabbb22dcc plugin-sdk-api-baseline.json
|
||||
010252e56202abde0816787588239c41b4bfb710b930a5454848a5ae76ad6dae plugin-sdk-api-baseline.jsonl
|
||||
a55e260675c0f02be5fe0444138683f6a0cb96d6bc6f0fa2b7026df2ea8165b2 plugin-sdk-api-baseline.json
|
||||
d4e9dea5aaa8a63d0f609acab2ac2962ee57ffa8bc6e5dd313a00f32cfd54b40 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -1136,6 +1136,7 @@ See [Configuration reference](/gateway/configuration) for the full `plugins.*` s
|
||||
- `channels`, `providers`, `cliBackends`, and `skills` can all be omitted when a plugin does not need them.
|
||||
- `providerDiscoveryEntry` must stay lightweight and should not import broad runtime code; use it for static provider catalog metadata or narrow discovery descriptors, not request-time execution.
|
||||
- Exclusive plugin kinds are selected through `plugins.slots.*`: `kind: "memory"` via `plugins.slots.memory`, `kind: "context-engine"` via `plugins.slots.contextEngine` (default `legacy`).
|
||||
- Declare exclusive plugin kind in this manifest. Runtime-entry `OpenClawPluginDefinition.kind` is deprecated and remains only as a compatibility fallback for older plugins.
|
||||
- Env-var metadata (`setup.providers[].envVars`, deprecated `providerAuthEnvVars`, and `channelEnvVars`) is declarative only. Status, audit, cron delivery validation, and other read-only surfaces still apply plugin trust and effective activation policy before treating an env var as configured.
|
||||
- For runtime wizard metadata that requires provider code, see [Provider runtime hooks](/plugins/architecture-internals#provider-runtime-hooks).
|
||||
- If your plugin depends on native modules, document the build steps and any package-manager allowlist requirements (for example, pnpm `allow-build-scripts` + `pnpm rebuild <package>`).
|
||||
|
||||
@@ -6,7 +6,6 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
applyExclusiveSlotSelection,
|
||||
buildPluginDiagnosticsReport,
|
||||
buildPluginSnapshotReport,
|
||||
clearPluginManifestRegistryCache,
|
||||
enablePluginInConfig,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub.js";
|
||||
import { applyExclusiveSlotSelection } from "../plugins/slots.js";
|
||||
import { buildPluginSnapshotReport } from "../plugins/status.js";
|
||||
import { applyExclusiveSlotSelection, slotKeysForPluginKind } from "../plugins/slots.js";
|
||||
import { buildPluginDiagnosticsReport, buildPluginSnapshotReport } from "../plugins/status.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
@@ -44,6 +44,33 @@ export function applySlotSelectionForPlugin(
|
||||
if (!plugin) {
|
||||
return { config, warnings: [] };
|
||||
}
|
||||
if (
|
||||
plugin.kind &&
|
||||
slotKeysForPluginKind(plugin.kind).length > 0 &&
|
||||
report.plugins.some((entry) => entry.id !== plugin.id && !entry.kind)
|
||||
) {
|
||||
const runtimeReport = buildPluginDiagnosticsReport({ config });
|
||||
const result = applyExclusiveSlotSelection({
|
||||
config,
|
||||
selectedId: plugin.id,
|
||||
selectedKind: plugin.kind,
|
||||
registry: runtimeReport,
|
||||
});
|
||||
return { config: result.config, warnings: result.warnings };
|
||||
}
|
||||
if (!plugin.kind) {
|
||||
const runtimeReport = buildPluginDiagnosticsReport({ config });
|
||||
const runtimePlugin = runtimeReport.plugins.find((entry) => entry.id === plugin.id);
|
||||
if (runtimePlugin?.kind) {
|
||||
const result = applyExclusiveSlotSelection({
|
||||
config,
|
||||
selectedId: runtimePlugin.id,
|
||||
selectedKind: runtimePlugin.kind,
|
||||
registry: runtimeReport,
|
||||
});
|
||||
return { config: result.config, warnings: result.warnings };
|
||||
}
|
||||
}
|
||||
const result = applyExclusiveSlotSelection({
|
||||
config,
|
||||
selectedId: plugin.id,
|
||||
|
||||
@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
applyExclusiveSlotSelection,
|
||||
buildPluginDiagnosticsReport,
|
||||
buildPluginSnapshotReport,
|
||||
enablePluginInConfig,
|
||||
refreshPluginRegistry,
|
||||
resetPluginsCliTestState,
|
||||
@@ -109,6 +111,168 @@ describe("persistPluginInstall", () => {
|
||||
expect(next).toEqual(enabledConfig);
|
||||
});
|
||||
|
||||
it("falls back to runtime kind registry cleanup when metadata omits kind", async () => {
|
||||
const { persistPluginInstall } = await import("./plugins-install-persist.js");
|
||||
const baseConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"legacy-memory-a": { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const enabledConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"legacy-memory-a": { enabled: true },
|
||||
"legacy-memory": { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
enablePluginInConfig.mockReturnValue({ config: enabledConfig });
|
||||
buildPluginSnapshotReport.mockReturnValue({
|
||||
plugins: [{ id: "legacy-memory-a" }, { id: "legacy-memory" }],
|
||||
diagnostics: [],
|
||||
});
|
||||
buildPluginDiagnosticsReport.mockReturnValue({
|
||||
plugins: [
|
||||
{ id: "legacy-memory-a", kind: "memory" },
|
||||
{ id: "legacy-memory", kind: "memory" },
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
applyExclusiveSlotSelection.mockImplementation(((params: {
|
||||
config: OpenClawConfig;
|
||||
selectedId: string;
|
||||
selectedKind?: string;
|
||||
registry?: { plugins: Array<{ id: string; kind?: string }> };
|
||||
}) => {
|
||||
expect(params.selectedId).toBe("legacy-memory");
|
||||
expect(params.selectedKind).toBe("memory");
|
||||
expect(params.registry?.plugins).toEqual([
|
||||
{ id: "legacy-memory-a", kind: "memory" },
|
||||
{ id: "legacy-memory", kind: "memory" },
|
||||
]);
|
||||
return {
|
||||
config: {
|
||||
...params.config,
|
||||
plugins: {
|
||||
...params.config.plugins,
|
||||
entries: {
|
||||
...params.config.plugins?.entries,
|
||||
"legacy-memory-a": { enabled: false },
|
||||
},
|
||||
slots: {
|
||||
...params.config.plugins?.slots,
|
||||
memory: "legacy-memory",
|
||||
},
|
||||
},
|
||||
},
|
||||
warnings: [],
|
||||
changed: true,
|
||||
};
|
||||
}) as (...args: unknown[]) => unknown);
|
||||
|
||||
const next = await persistPluginInstall({
|
||||
snapshot: {
|
||||
config: baseConfig,
|
||||
baseHash: "config-1",
|
||||
},
|
||||
pluginId: "legacy-memory",
|
||||
install: {
|
||||
source: "path",
|
||||
sourcePath: "/tmp/legacy-memory",
|
||||
installPath: "/tmp/legacy-memory",
|
||||
},
|
||||
});
|
||||
|
||||
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({
|
||||
config: enabledConfig,
|
||||
});
|
||||
expect(next.plugins?.entries?.["legacy-memory-a"]?.enabled).toBe(false);
|
||||
expect(next.plugins?.slots?.memory).toBe("legacy-memory");
|
||||
});
|
||||
|
||||
it("uses runtime registry cleanup when a manifest-kind plugin has runtime-kind siblings", async () => {
|
||||
const { persistPluginInstall } = await import("./plugins-install-persist.js");
|
||||
const baseConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"legacy-memory-a": { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const enabledConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"legacy-memory-a": { enabled: true },
|
||||
"memory-b": { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
enablePluginInConfig.mockReturnValue({ config: enabledConfig });
|
||||
buildPluginSnapshotReport.mockReturnValue({
|
||||
plugins: [{ id: "legacy-memory-a" }, { id: "memory-b", kind: "memory" }],
|
||||
diagnostics: [],
|
||||
});
|
||||
buildPluginDiagnosticsReport.mockReturnValue({
|
||||
plugins: [
|
||||
{ id: "legacy-memory-a", kind: "memory" },
|
||||
{ id: "memory-b", kind: "memory" },
|
||||
],
|
||||
diagnostics: [],
|
||||
});
|
||||
applyExclusiveSlotSelection.mockImplementation(((params: {
|
||||
config: OpenClawConfig;
|
||||
selectedId: string;
|
||||
selectedKind?: string;
|
||||
registry?: { plugins: Array<{ id: string; kind?: string }> };
|
||||
}) => {
|
||||
expect(params.selectedId).toBe("memory-b");
|
||||
expect(params.selectedKind).toBe("memory");
|
||||
expect(params.registry?.plugins).toEqual([
|
||||
{ id: "legacy-memory-a", kind: "memory" },
|
||||
{ id: "memory-b", kind: "memory" },
|
||||
]);
|
||||
return {
|
||||
config: {
|
||||
...params.config,
|
||||
plugins: {
|
||||
...params.config.plugins,
|
||||
entries: {
|
||||
...params.config.plugins?.entries,
|
||||
"legacy-memory-a": { enabled: false },
|
||||
},
|
||||
slots: {
|
||||
...params.config.plugins?.slots,
|
||||
memory: "memory-b",
|
||||
},
|
||||
},
|
||||
},
|
||||
warnings: [],
|
||||
changed: true,
|
||||
};
|
||||
}) as (...args: unknown[]) => unknown);
|
||||
|
||||
const next = await persistPluginInstall({
|
||||
snapshot: {
|
||||
config: baseConfig,
|
||||
baseHash: "config-1",
|
||||
},
|
||||
pluginId: "memory-b",
|
||||
install: {
|
||||
source: "path",
|
||||
sourcePath: "/tmp/memory-b",
|
||||
installPath: "/tmp/memory-b",
|
||||
},
|
||||
});
|
||||
|
||||
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({
|
||||
config: enabledConfig,
|
||||
});
|
||||
expect(next.plugins?.entries?.["legacy-memory-a"]?.enabled).toBe(false);
|
||||
expect(next.plugins?.slots?.memory).toBe("memory-b");
|
||||
});
|
||||
|
||||
it("can persist an install record without enabling a plugin that needs config first", async () => {
|
||||
const { persistPluginInstall } = await import("./plugins-install-persist.js");
|
||||
const baseConfig = {
|
||||
|
||||
@@ -224,6 +224,11 @@ type DefinePluginEntryOptions = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
/**
|
||||
* @deprecated Declare exclusive plugin kind in `openclaw.plugin.json` via
|
||||
* manifest `kind`. Runtime-entry `kind` remains only as a compatibility
|
||||
* fallback for older plugins.
|
||||
*/
|
||||
kind?: OpenClawPluginDefinition["kind"];
|
||||
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
|
||||
reload?: OpenClawPluginDefinition["reload"];
|
||||
|
||||
@@ -46,6 +46,11 @@ export type SingleProviderPluginOptions = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
/**
|
||||
* @deprecated Declare exclusive plugin kind in `openclaw.plugin.json` via
|
||||
* manifest `kind`. Runtime-entry `kind` remains only as a compatibility
|
||||
* fallback for older plugins.
|
||||
*/
|
||||
kind?: OpenClawPluginDefinition["kind"];
|
||||
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
|
||||
provider?: {
|
||||
|
||||
@@ -2063,6 +2063,12 @@ export type OpenClawPluginDefinition = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
/**
|
||||
* @deprecated Declare exclusive plugin kind in `openclaw.plugin.json` via
|
||||
* manifest `kind`. Runtime-exported `kind` is kept as a compatibility
|
||||
* fallback for older plugins and may require loading plugin runtime on
|
||||
* metadata-only command paths.
|
||||
*/
|
||||
kind?: PluginKind | PluginKind[];
|
||||
configSchema?: OpenClawPluginConfigSchema;
|
||||
reload?: OpenClawPluginReloadRegistration;
|
||||
|
||||
Reference in New Issue
Block a user