mirror of https://github.com/openclaw/openclaw.git
Plugins: extract registry compatibility policy
This commit is contained in:
parent
33ab19e61e
commit
944d787df1
|
|
@ -0,0 +1,92 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { clearPluginCommands } from "../plugins/commands.js";
|
||||
import { createEmptyPluginRegistry, type PluginRecord } from "../plugins/registry.js";
|
||||
import {
|
||||
resolveExtensionHostCommandCompatibility,
|
||||
resolveExtensionHostProviderCompatibility,
|
||||
} from "./plugin-registry-compat.js";
|
||||
|
||||
function createRecord(): PluginRecord {
|
||||
return {
|
||||
id: "demo",
|
||||
name: "Demo",
|
||||
source: "/plugins/demo.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
};
|
||||
}
|
||||
|
||||
describe("extension host plugin registry compatibility", () => {
|
||||
it("normalizes provider registration through the host-owned compatibility helper", () => {
|
||||
const result = resolveExtensionHostProviderCompatibility({
|
||||
registry: createEmptyPluginRegistry(),
|
||||
record: createRecord(),
|
||||
provider: {
|
||||
id: " demo-provider ",
|
||||
label: " Demo Provider ",
|
||||
auth: [{ id: " api-key ", label: " API Key " }],
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
providerId: "demo-provider",
|
||||
entry: {
|
||||
provider: {
|
||||
id: "demo-provider",
|
||||
label: "Demo Provider",
|
||||
auth: [{ id: "api-key", label: "API Key" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reports duplicate command registration through the host-owned compatibility helper", () => {
|
||||
clearPluginCommands();
|
||||
const registry = createEmptyPluginRegistry();
|
||||
const record = createRecord();
|
||||
|
||||
const first = resolveExtensionHostCommandCompatibility({
|
||||
registry,
|
||||
record,
|
||||
command: {
|
||||
name: "demo",
|
||||
description: "first",
|
||||
handler: vi.fn(async () => ({ handled: true })),
|
||||
},
|
||||
});
|
||||
const second = resolveExtensionHostCommandCompatibility({
|
||||
registry,
|
||||
record,
|
||||
command: {
|
||||
name: "demo",
|
||||
description: "second",
|
||||
handler: vi.fn(async () => ({ handled: true })),
|
||||
},
|
||||
});
|
||||
|
||||
expect(first.ok).toBe(true);
|
||||
expect(second.ok).toBe(false);
|
||||
expect(registry.diagnostics).toContainEqual(
|
||||
expect.objectContaining({
|
||||
level: "error",
|
||||
pluginId: "demo",
|
||||
message: 'command registration failed: Command "demo" already registered by plugin "demo"',
|
||||
}),
|
||||
);
|
||||
|
||||
clearPluginCommands();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import { registerPluginCommand } from "../plugins/commands.js";
|
||||
import { normalizeRegisteredProvider } from "../plugins/provider-validation.js";
|
||||
import type { PluginRecord, PluginRegistry } from "../plugins/registry.js";
|
||||
import type {
|
||||
OpenClawPluginCommandDefinition,
|
||||
PluginDiagnostic,
|
||||
ProviderPlugin,
|
||||
} from "../plugins/types.js";
|
||||
import {
|
||||
type ExtensionHostCommandRegistration,
|
||||
type ExtensionHostProviderRegistration,
|
||||
resolveExtensionCommandRegistration,
|
||||
resolveExtensionProviderRegistration,
|
||||
} from "./runtime-registrations.js";
|
||||
|
||||
export function pushExtensionHostRegistryDiagnostic(params: {
|
||||
registry: PluginRegistry;
|
||||
level: PluginDiagnostic["level"];
|
||||
pluginId: string;
|
||||
source: string;
|
||||
message: string;
|
||||
}) {
|
||||
params.registry.diagnostics.push({
|
||||
level: params.level,
|
||||
pluginId: params.pluginId,
|
||||
source: params.source,
|
||||
message: params.message,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveExtensionHostProviderCompatibility(params: {
|
||||
registry: PluginRegistry;
|
||||
record: PluginRecord;
|
||||
provider: ProviderPlugin;
|
||||
}):
|
||||
| {
|
||||
ok: true;
|
||||
providerId: string;
|
||||
entry: ExtensionHostProviderRegistration;
|
||||
}
|
||||
| { ok: false } {
|
||||
const pushDiagnostic = (diag: PluginDiagnostic) => {
|
||||
params.registry.diagnostics.push(diag);
|
||||
};
|
||||
|
||||
const normalizedProvider = normalizeRegisteredProvider({
|
||||
pluginId: params.record.id,
|
||||
source: params.record.source,
|
||||
provider: params.provider,
|
||||
pushDiagnostic,
|
||||
});
|
||||
if (!normalizedProvider) {
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
const result = resolveExtensionProviderRegistration({
|
||||
existing: params.registry.providers,
|
||||
ownerPluginId: params.record.id,
|
||||
ownerSource: params.record.source,
|
||||
provider: normalizedProvider,
|
||||
});
|
||||
if (!result.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry: params.registry,
|
||||
level: "error",
|
||||
pluginId: params.record.id,
|
||||
source: params.record.source,
|
||||
message: result.message,
|
||||
});
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function resolveExtensionHostCommandCompatibility(params: {
|
||||
registry: PluginRegistry;
|
||||
record: PluginRecord;
|
||||
command: OpenClawPluginCommandDefinition;
|
||||
}):
|
||||
| {
|
||||
ok: true;
|
||||
commandName: string;
|
||||
entry: ExtensionHostCommandRegistration;
|
||||
}
|
||||
| { ok: false } {
|
||||
const normalized = resolveExtensionCommandRegistration({
|
||||
ownerPluginId: params.record.id,
|
||||
ownerSource: params.record.source,
|
||||
command: params.command,
|
||||
});
|
||||
if (!normalized.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry: params.registry,
|
||||
level: "error",
|
||||
pluginId: params.record.id,
|
||||
source: params.record.source,
|
||||
message: normalized.message,
|
||||
});
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
const result = registerPluginCommand(params.record.id, normalized.entry.command);
|
||||
if (!result.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry: params.registry,
|
||||
level: "error",
|
||||
pluginId: params.record.id,
|
||||
source: params.record.source,
|
||||
message: `command registration failed: ${result.error}`,
|
||||
});
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
|
@ -22,20 +22,16 @@ import {
|
|||
import {
|
||||
resolveExtensionChannelRegistration,
|
||||
resolveExtensionCliRegistration,
|
||||
resolveExtensionCommandRegistration,
|
||||
resolveExtensionContextEngineRegistration,
|
||||
resolveExtensionGatewayMethodRegistration,
|
||||
resolveExtensionLegacyHookRegistration,
|
||||
resolveExtensionHttpRouteRegistration,
|
||||
resolveExtensionProviderRegistration,
|
||||
resolveExtensionServiceRegistration,
|
||||
resolveExtensionToolRegistration,
|
||||
resolveExtensionTypedHookRegistration,
|
||||
} from "../extension-host/runtime-registrations.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { registerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import { registerPluginCommand } from "../plugins/commands.js";
|
||||
import { normalizeRegisteredProvider } from "../plugins/provider-validation.js";
|
||||
import type { PluginRecord, PluginRegistry, PluginRegistryParams } from "../plugins/registry.js";
|
||||
import type {
|
||||
PluginDiagnostic,
|
||||
|
|
@ -52,33 +48,22 @@ import type {
|
|||
ProviderPlugin,
|
||||
PluginHookRegistration as TypedPluginHookRegistration,
|
||||
} from "../plugins/types.js";
|
||||
import {
|
||||
pushExtensionHostRegistryDiagnostic,
|
||||
resolveExtensionHostCommandCompatibility,
|
||||
resolveExtensionHostProviderCompatibility,
|
||||
} from "./plugin-registry-compat.js";
|
||||
|
||||
type PluginTypedHookPolicy = {
|
||||
allowPromptInjection?: boolean;
|
||||
};
|
||||
|
||||
function pushExtensionHostRegistryDiagnostic(params: {
|
||||
registry: PluginRegistry;
|
||||
level: PluginDiagnostic["level"];
|
||||
pluginId: string;
|
||||
source: string;
|
||||
message: string;
|
||||
}) {
|
||||
params.registry.diagnostics.push({
|
||||
level: params.level,
|
||||
pluginId: params.pluginId,
|
||||
source: params.source,
|
||||
message: params.message,
|
||||
});
|
||||
}
|
||||
|
||||
export function createExtensionHostPluginRegistry(params: {
|
||||
registry: PluginRegistry;
|
||||
registryParams: PluginRegistryParams;
|
||||
}) {
|
||||
const { registry, registryParams } = params;
|
||||
const coreGatewayMethods = new Set(Object.keys(registryParams.coreGatewayHandlers ?? {}));
|
||||
|
||||
const pushDiagnostic = (diag: PluginDiagnostic) => {
|
||||
registry.diagnostics.push(diag);
|
||||
};
|
||||
|
|
@ -231,29 +216,12 @@ export function createExtensionHostPluginRegistry(params: {
|
|||
};
|
||||
|
||||
const registerProvider = (record: PluginRecord, provider: ProviderPlugin) => {
|
||||
const normalizedProvider = normalizeRegisteredProvider({
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
const result = resolveExtensionHostProviderCompatibility({
|
||||
registry,
|
||||
record,
|
||||
provider,
|
||||
pushDiagnostic,
|
||||
});
|
||||
if (!normalizedProvider) {
|
||||
return;
|
||||
}
|
||||
const result = resolveExtensionProviderRegistration({
|
||||
existing: registry.providers,
|
||||
ownerPluginId: record.id,
|
||||
ownerSource: record.source,
|
||||
provider: normalizedProvider,
|
||||
});
|
||||
if (!result.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry,
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: result.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
addExtensionProviderRegistration({
|
||||
|
|
@ -301,34 +269,10 @@ export function createExtensionHostPluginRegistry(params: {
|
|||
};
|
||||
|
||||
const registerCommand = (record: PluginRecord, command: OpenClawPluginCommandDefinition) => {
|
||||
const normalized = resolveExtensionCommandRegistration({
|
||||
ownerPluginId: record.id,
|
||||
ownerSource: record.source,
|
||||
command,
|
||||
});
|
||||
const normalized = resolveExtensionHostCommandCompatibility({ registry, record, command });
|
||||
if (!normalized.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry,
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: normalized.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = registerPluginCommand(record.id, normalized.entry.command);
|
||||
if (!result.ok) {
|
||||
pushExtensionHostRegistryDiagnostic({
|
||||
registry,
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `command registration failed: ${result.error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
addExtensionCommandRegistration({
|
||||
registry,
|
||||
record,
|
||||
|
|
|
|||
Loading…
Reference in New Issue