mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
feat(plugins): add plugin registry facade
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { PluginAutoEnableResult } from "../../config/plugin-auto-enable.js";
|
||||
|
||||
const loadInstalledPluginIndex = vi.hoisted(() => vi.fn());
|
||||
const listInstalledPluginContributionIds = vi.hoisted(() =>
|
||||
const loadPluginRegistrySnapshot = vi.hoisted(() => vi.fn());
|
||||
const listPluginContributionIds = vi.hoisted(() =>
|
||||
vi.fn((_index?: unknown, _contribution?: unknown, _options?: unknown): string[] => []),
|
||||
);
|
||||
const listChannelPluginCatalogEntries = vi.hoisted(() => vi.fn((): unknown[] => []));
|
||||
@@ -17,10 +17,9 @@ const applyPluginAutoEnable = vi.hoisted(() =>
|
||||
),
|
||||
);
|
||||
|
||||
vi.mock("../../plugins/installed-plugin-index.js", () => ({
|
||||
loadInstalledPluginIndex: (...args: unknown[]) => loadInstalledPluginIndex(...args),
|
||||
listInstalledPluginContributionIds: (index: unknown, contribution: unknown, options?: unknown) =>
|
||||
listInstalledPluginContributionIds(index, contribution, options),
|
||||
vi.mock("../../plugins/plugin-registry.js", () => ({
|
||||
loadPluginRegistrySnapshot: (...args: unknown[]) => loadPluginRegistrySnapshot(...args),
|
||||
listPluginContributionIds: (args: unknown) => listPluginContributionIds(args),
|
||||
}));
|
||||
|
||||
vi.mock("../../config/plugin-auto-enable.js", () => ({
|
||||
@@ -40,11 +39,11 @@ import { listManifestInstalledChannelIds, resolveChannelSetupEntries } from "./d
|
||||
|
||||
describe("listManifestInstalledChannelIds", () => {
|
||||
beforeEach(() => {
|
||||
loadInstalledPluginIndex.mockReset().mockReturnValue({
|
||||
loadPluginRegistrySnapshot.mockReset().mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
listInstalledPluginContributionIds.mockReset().mockReturnValue([]);
|
||||
listPluginContributionIds.mockReset().mockReturnValue([]);
|
||||
listChannelPluginCatalogEntries.mockReset().mockReturnValue([]);
|
||||
listChatChannels.mockReset().mockReturnValue([]);
|
||||
applyPluginAutoEnable.mockReset().mockImplementation(({ config }) => ({
|
||||
@@ -67,11 +66,11 @@ describe("listManifestInstalledChannelIds", () => {
|
||||
slack: ["slack configured"],
|
||||
},
|
||||
});
|
||||
loadInstalledPluginIndex.mockReturnValue({
|
||||
loadPluginRegistrySnapshot.mockReturnValue({
|
||||
plugins: [{ pluginId: "slack", contributions: { channels: ["slack"] } }],
|
||||
diagnostics: [],
|
||||
});
|
||||
listInstalledPluginContributionIds.mockReturnValue(["slack"]);
|
||||
listPluginContributionIds.mockReturnValue(["slack"]);
|
||||
|
||||
const installedIds = listManifestInstalledChannelIds({
|
||||
cfg: {} as never,
|
||||
@@ -83,19 +82,18 @@ describe("listManifestInstalledChannelIds", () => {
|
||||
config: {},
|
||||
env: { OPENCLAW_HOME: "/tmp/home" },
|
||||
});
|
||||
expect(loadInstalledPluginIndex).toHaveBeenCalledWith({
|
||||
expect(loadPluginRegistrySnapshot).toHaveBeenCalledWith({
|
||||
config: autoEnabledConfig,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
env: { OPENCLAW_HOME: "/tmp/home" },
|
||||
});
|
||||
expect(listInstalledPluginContributionIds).toHaveBeenCalledWith(
|
||||
{
|
||||
expect(listPluginContributionIds).toHaveBeenCalledWith({
|
||||
index: {
|
||||
plugins: [{ pluginId: "slack", contributions: { channels: ["slack"] } }],
|
||||
diagnostics: [],
|
||||
},
|
||||
"channels",
|
||||
undefined,
|
||||
);
|
||||
contribution: "channels",
|
||||
});
|
||||
expect(installedIds).toEqual(new Set(["slack"]));
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import type { ChannelMeta } from "../../channels/plugins/types.public.js";
|
||||
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import {
|
||||
listInstalledPluginContributionIds,
|
||||
loadInstalledPluginIndex,
|
||||
} from "../../plugins/installed-plugin-index.js";
|
||||
listPluginContributionIds,
|
||||
loadPluginRegistrySnapshot,
|
||||
} from "../../plugins/plugin-registry.js";
|
||||
import type { ChannelChoice } from "../onboard-types.js";
|
||||
import {
|
||||
listSetupDiscoveryChannelPluginCatalogEntries,
|
||||
@@ -50,13 +50,13 @@ export function listManifestInstalledChannelIds(params: {
|
||||
env: params.env ?? process.env,
|
||||
}).config;
|
||||
const workspaceDir = resolveWorkspaceDir(resolvedConfig, params.workspaceDir);
|
||||
const index = loadInstalledPluginIndex({
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
config: resolvedConfig,
|
||||
workspaceDir,
|
||||
env: params.env ?? process.env,
|
||||
});
|
||||
return new Set(
|
||||
listInstalledPluginContributionIds(index, "channels").map(
|
||||
listPluginContributionIds({ index, contribution: "channels" }).map(
|
||||
(channelId) => channelId as ChannelChoice,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -6,18 +6,19 @@ import {
|
||||
} from "./list.provider-catalog.js";
|
||||
|
||||
const providerDiscoveryMocks = vi.hoisted(() => ({
|
||||
loadInstalledPluginIndex: vi.fn(),
|
||||
loadPluginRegistrySnapshot: vi.fn(),
|
||||
resolvePluginContributionOwners: vi.fn(),
|
||||
resolveProviderOwners: vi.fn(),
|
||||
resolveBundledProviderCompatPluginIds: vi.fn(),
|
||||
resolveInstalledPluginContributionOwners: vi.fn(),
|
||||
resolveOwningPluginIdsForProvider: vi.fn(),
|
||||
resolvePluginDiscoveryProviders: vi.fn(),
|
||||
resolveProviderContractPluginIdsForProviderAlias: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/installed-plugin-index.js", () => ({
|
||||
loadInstalledPluginIndex: providerDiscoveryMocks.loadInstalledPluginIndex,
|
||||
resolveInstalledPluginContributionOwners:
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners,
|
||||
vi.mock("../../plugins/plugin-registry.js", () => ({
|
||||
loadPluginRegistrySnapshot: providerDiscoveryMocks.loadPluginRegistrySnapshot,
|
||||
resolvePluginContributionOwners: providerDiscoveryMocks.resolvePluginContributionOwners,
|
||||
resolveProviderOwners: providerDiscoveryMocks.resolveProviderOwners,
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/providers.js", () => ({
|
||||
@@ -113,20 +114,17 @@ const defaultProviders = [chutesProvider, moonshotProvider, openaiProvider];
|
||||
describe("loadProviderCatalogModelsForList", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
providerDiscoveryMocks.loadInstalledPluginIndex.mockReturnValue({
|
||||
providerDiscoveryMocks.loadPluginRegistrySnapshot.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockImplementation(
|
||||
(_index: unknown, contribution: string, matches: (contributionId: string) => boolean) => {
|
||||
if (contribution !== "providers") {
|
||||
return [];
|
||||
}
|
||||
return defaultProviders
|
||||
.filter((provider) => matches(provider.id))
|
||||
.map((provider) => provider.pluginId);
|
||||
},
|
||||
providerDiscoveryMocks.resolveProviderOwners.mockImplementation(
|
||||
({ providerId }: { providerId: string }) =>
|
||||
defaultProviders
|
||||
.filter((provider) => provider.id === providerId)
|
||||
.map((provider) => provider.pluginId),
|
||||
);
|
||||
providerDiscoveryMocks.resolvePluginContributionOwners.mockReturnValue([]);
|
||||
providerDiscoveryMocks.resolveBundledProviderCompatPluginIds.mockReturnValue([
|
||||
"chutes",
|
||||
"moonshot",
|
||||
@@ -198,7 +196,7 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
}),
|
||||
).resolves.toEqual(["moonshot"]);
|
||||
|
||||
expect(providerDiscoveryMocks.loadInstalledPluginIndex).toHaveBeenCalledWith({
|
||||
expect(providerDiscoveryMocks.loadPluginRegistrySnapshot).toHaveBeenCalledWith({
|
||||
config: baseParams.cfg,
|
||||
env: baseParams.env,
|
||||
});
|
||||
@@ -206,11 +204,10 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
});
|
||||
|
||||
it("does not fall back to legacy manifest ownership for disabled installed-index owners", async () => {
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners
|
||||
providerDiscoveryMocks.resolveProviderOwners
|
||||
.mockReturnValueOnce([])
|
||||
.mockReturnValueOnce([])
|
||||
.mockReturnValueOnce(["moonshot"])
|
||||
.mockReturnValueOnce([]);
|
||||
.mockReturnValueOnce(["moonshot"]);
|
||||
providerDiscoveryMocks.resolvePluginContributionOwners.mockReturnValue([]);
|
||||
|
||||
await expect(
|
||||
resolveProviderCatalogPluginIdsForFilter({
|
||||
@@ -280,7 +277,7 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
});
|
||||
|
||||
it("does not skip registry for non-bundled static catalog owners", async () => {
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]);
|
||||
providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]);
|
||||
providerDiscoveryMocks.resolveOwningPluginIdsForProvider.mockReturnValueOnce([
|
||||
"workspace-static-provider",
|
||||
]);
|
||||
@@ -298,7 +295,7 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
});
|
||||
|
||||
it("recognizes bundled provider hook aliases before the unknown-provider short-circuit", async () => {
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]);
|
||||
providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]);
|
||||
|
||||
await expect(
|
||||
resolveProviderCatalogPluginIdsForFilter({
|
||||
@@ -350,7 +347,7 @@ describe("loadProviderCatalogModelsForList", () => {
|
||||
});
|
||||
|
||||
it("keeps unknown provider filters eligible for early empty results", async () => {
|
||||
providerDiscoveryMocks.resolveInstalledPluginContributionOwners.mockReturnValueOnce([]);
|
||||
providerDiscoveryMocks.resolveProviderOwners.mockReturnValueOnce([]);
|
||||
|
||||
await expect(
|
||||
resolveProviderCatalogPluginIdsForFilter({
|
||||
|
||||
@@ -5,10 +5,11 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import {
|
||||
type InstalledPluginIndex,
|
||||
loadInstalledPluginIndex,
|
||||
resolveInstalledPluginContributionOwners,
|
||||
} from "../../plugins/installed-plugin-index.js";
|
||||
loadPluginRegistrySnapshot,
|
||||
resolvePluginContributionOwners,
|
||||
resolveProviderOwners,
|
||||
type PluginRegistrySnapshot,
|
||||
} from "../../plugins/plugin-registry.js";
|
||||
import {
|
||||
groupPluginDiscoveryProvidersByOrder,
|
||||
normalizePluginDiscoveryResult,
|
||||
@@ -37,18 +38,27 @@ function providerMatchesFilter(params: {
|
||||
}
|
||||
|
||||
function collectMatchingContributionOwners(
|
||||
index: InstalledPluginIndex,
|
||||
index: PluginRegistrySnapshot,
|
||||
contribution: "providers" | "cliBackends",
|
||||
providerFilter: string,
|
||||
options: { includeDisabled?: boolean } = {},
|
||||
): string[] {
|
||||
if (contribution === "providers") {
|
||||
return [
|
||||
...resolveProviderOwners({
|
||||
index,
|
||||
providerId: providerFilter,
|
||||
includeDisabled: options.includeDisabled,
|
||||
}),
|
||||
];
|
||||
}
|
||||
return [
|
||||
...resolveInstalledPluginContributionOwners(
|
||||
...resolvePluginContributionOwners({
|
||||
index,
|
||||
contribution,
|
||||
(contributionId) => normalizeProviderId(contributionId) === providerFilter,
|
||||
options,
|
||||
),
|
||||
contribution: "cliBackends",
|
||||
matches: (contributionId) => normalizeProviderId(contributionId) === providerFilter,
|
||||
includeDisabled: options.includeDisabled,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -57,7 +67,7 @@ function resolveInstalledIndexPluginIdsForProviderFilter(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
providerFilter: string;
|
||||
}): string[] | undefined {
|
||||
const index = loadInstalledPluginIndex({
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
config: params.cfg,
|
||||
env: params.env,
|
||||
});
|
||||
|
||||
179
src/plugins/plugin-registry.test.ts
Normal file
179
src/plugins/plugin-registry.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
getPluginRecord,
|
||||
inspectPluginRegistry,
|
||||
isPluginEnabled,
|
||||
listPluginContributionIds,
|
||||
listPluginRecords,
|
||||
loadPluginRegistrySnapshot,
|
||||
refreshPluginRegistry,
|
||||
resolveChannelOwners,
|
||||
resolveCliBackendOwners,
|
||||
resolvePluginContributionOwners,
|
||||
resolveProviderOwners,
|
||||
resolveSetupProviderOwners,
|
||||
} from "./plugin-registry.js";
|
||||
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTrackedTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
function makeTempDir() {
|
||||
return makeTrackedTempDir("openclaw-plugin-registry", tempDirs);
|
||||
}
|
||||
|
||||
function hermeticEnv(overrides: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
|
||||
return {
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||
OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1",
|
||||
OPENCLAW_VERSION: "2026.4.25",
|
||||
VITEST: "true",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createCandidate(rootDir: string): PluginCandidate {
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, "index.ts"),
|
||||
"throw new Error('runtime entry should not load while reading plugin registry');\n",
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: "demo",
|
||||
name: "Demo",
|
||||
configSchema: { type: "object" },
|
||||
providers: ["demo"],
|
||||
channels: ["demo-chat"],
|
||||
cliBackends: ["demo-cli"],
|
||||
setup: {
|
||||
providers: [{ id: "demo-setup", envVars: ["DEMO_API_KEY"] }],
|
||||
cliBackends: ["demo-setup-cli"],
|
||||
},
|
||||
channelConfigs: {
|
||||
"demo-chat": {
|
||||
schema: { type: "object" },
|
||||
},
|
||||
},
|
||||
modelCatalog: {
|
||||
providers: {
|
||||
demo: {
|
||||
models: [{ id: "demo-model" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
commandAliases: [{ name: "demo-command" }],
|
||||
contracts: {
|
||||
tools: ["demo-tool"],
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
return {
|
||||
idHint: "demo",
|
||||
source: path.join(rootDir, "index.ts"),
|
||||
rootDir,
|
||||
origin: "global",
|
||||
};
|
||||
}
|
||||
|
||||
describe("plugin registry facade", () => {
|
||||
it("resolves cold plugin records and contribution owners without loading runtime", () => {
|
||||
const rootDir = makeTempDir();
|
||||
const candidate = createCandidate(rootDir);
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
candidates: [candidate],
|
||||
env: hermeticEnv(),
|
||||
});
|
||||
|
||||
expect(listPluginRecords({ index }).map((plugin) => plugin.pluginId)).toEqual(["demo"]);
|
||||
expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: true,
|
||||
});
|
||||
expect(isPluginEnabled({ index, pluginId: "demo" })).toBe(true);
|
||||
expect(listPluginContributionIds({ index, contribution: "providers" })).toEqual(["demo"]);
|
||||
expect(resolveProviderOwners({ index, providerId: "demo" })).toEqual(["demo"]);
|
||||
expect(resolveChannelOwners({ index, channelId: "demo-chat" })).toEqual(["demo"]);
|
||||
expect(resolveCliBackendOwners({ index, cliBackendId: "demo-cli" })).toEqual(["demo"]);
|
||||
expect(
|
||||
resolvePluginContributionOwners({
|
||||
index,
|
||||
contribution: "cliBackends",
|
||||
matches: (contributionId) => contributionId === "demo-cli",
|
||||
}),
|
||||
).toEqual(["demo"]);
|
||||
expect(resolveSetupProviderOwners({ index, setupProviderId: "demo-setup" })).toEqual(["demo"]);
|
||||
});
|
||||
|
||||
it("keeps disabled records inspectable while excluding owners by default", () => {
|
||||
const rootDir = makeTempDir();
|
||||
const candidate = createCandidate(rootDir);
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
candidates: [candidate],
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
demo: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
env: hermeticEnv(),
|
||||
});
|
||||
|
||||
expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: false,
|
||||
});
|
||||
expect(resolveProviderOwners({ index, providerId: "demo" })).toEqual([]);
|
||||
expect(resolveProviderOwners({ index, providerId: "demo", includeDisabled: true })).toEqual([
|
||||
"demo",
|
||||
]);
|
||||
});
|
||||
|
||||
it("exposes explicit persisted registry inspect and refresh operations", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(stateDir, "plugins", "demo");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
const candidate = createCandidate(pluginDir);
|
||||
const env = hermeticEnv();
|
||||
|
||||
await expect(
|
||||
inspectPluginRegistry({ stateDir, candidates: [candidate], env }),
|
||||
).resolves.toMatchObject({
|
||||
state: "missing",
|
||||
refreshReasons: ["missing"],
|
||||
persisted: null,
|
||||
current: {
|
||||
plugins: [expect.objectContaining({ pluginId: "demo" })],
|
||||
},
|
||||
});
|
||||
|
||||
await refreshPluginRegistry({
|
||||
reason: "manual",
|
||||
stateDir,
|
||||
candidates: [candidate],
|
||||
env,
|
||||
});
|
||||
|
||||
await expect(
|
||||
inspectPluginRegistry({ stateDir, candidates: [candidate], env }),
|
||||
).resolves.toMatchObject({
|
||||
state: "fresh",
|
||||
refreshReasons: [],
|
||||
persisted: {
|
||||
plugins: [expect.objectContaining({ pluginId: "demo" })],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
174
src/plugins/plugin-registry.ts
Normal file
174
src/plugins/plugin-registry.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type {
|
||||
InstalledPluginIndexStoreInspection,
|
||||
InstalledPluginIndexStoreOptions,
|
||||
} from "./installed-plugin-index-store.js";
|
||||
import {
|
||||
getInstalledPluginRecord,
|
||||
isInstalledPluginEnabled,
|
||||
listInstalledPluginContributionIds,
|
||||
listInstalledPluginRecords,
|
||||
loadInstalledPluginIndex,
|
||||
resolveInstalledPluginContributionOwners,
|
||||
type InstalledPluginContributionKey,
|
||||
type InstalledPluginIndex,
|
||||
type InstalledPluginIndexRecord,
|
||||
type LoadInstalledPluginIndexParams,
|
||||
type RefreshInstalledPluginIndexParams,
|
||||
} from "./installed-plugin-index.js";
|
||||
|
||||
export type PluginRegistrySnapshot = InstalledPluginIndex;
|
||||
export type PluginRegistryRecord = InstalledPluginIndexRecord;
|
||||
export type PluginRegistryInspection = InstalledPluginIndexStoreInspection;
|
||||
|
||||
export type LoadPluginRegistryParams = LoadInstalledPluginIndexParams & {
|
||||
index?: PluginRegistrySnapshot;
|
||||
};
|
||||
|
||||
export type PluginRegistryContributionOptions = LoadPluginRegistryParams & {
|
||||
includeDisabled?: boolean;
|
||||
};
|
||||
|
||||
export type GetPluginRecordParams = LoadPluginRegistryParams & {
|
||||
pluginId: string;
|
||||
};
|
||||
|
||||
export type ResolvePluginContributionOwnersParams = PluginRegistryContributionOptions & {
|
||||
contribution: InstalledPluginContributionKey;
|
||||
matches: string | ((contributionId: string) => boolean);
|
||||
};
|
||||
|
||||
export type ListPluginContributionIdsParams = PluginRegistryContributionOptions & {
|
||||
contribution: InstalledPluginContributionKey;
|
||||
};
|
||||
|
||||
export type ResolveProviderOwnersParams = PluginRegistryContributionOptions & {
|
||||
providerId: string;
|
||||
};
|
||||
|
||||
export type ResolveChannelOwnersParams = PluginRegistryContributionOptions & {
|
||||
channelId: string;
|
||||
};
|
||||
|
||||
export type ResolveCliBackendOwnersParams = PluginRegistryContributionOptions & {
|
||||
cliBackendId: string;
|
||||
};
|
||||
|
||||
export type ResolveSetupProviderOwnersParams = PluginRegistryContributionOptions & {
|
||||
setupProviderId: string;
|
||||
};
|
||||
|
||||
function normalizeContributionId(value: string): string {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function resolveSnapshot(params: LoadPluginRegistryParams = {}): PluginRegistrySnapshot {
|
||||
return params.index ?? loadInstalledPluginIndex(params);
|
||||
}
|
||||
|
||||
export function loadPluginRegistrySnapshot(
|
||||
params: LoadPluginRegistryParams = {},
|
||||
): PluginRegistrySnapshot {
|
||||
return resolveSnapshot(params);
|
||||
}
|
||||
|
||||
export function listPluginRecords(
|
||||
params: LoadPluginRegistryParams = {},
|
||||
): readonly PluginRegistryRecord[] {
|
||||
return listInstalledPluginRecords(resolveSnapshot(params));
|
||||
}
|
||||
|
||||
export function getPluginRecord(params: GetPluginRecordParams): PluginRegistryRecord | undefined {
|
||||
return getInstalledPluginRecord(resolveSnapshot(params), params.pluginId);
|
||||
}
|
||||
|
||||
export function isPluginEnabled(params: GetPluginRecordParams): boolean {
|
||||
return isInstalledPluginEnabled(resolveSnapshot(params), params.pluginId);
|
||||
}
|
||||
|
||||
export function listPluginContributionIds(
|
||||
params: ListPluginContributionIdsParams,
|
||||
): readonly string[] {
|
||||
return listInstalledPluginContributionIds(resolveSnapshot(params), params.contribution, {
|
||||
includeDisabled: params.includeDisabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePluginContributionOwners(
|
||||
params: ResolvePluginContributionOwnersParams,
|
||||
): readonly string[] {
|
||||
return resolveInstalledPluginContributionOwners(
|
||||
resolveSnapshot(params),
|
||||
params.contribution,
|
||||
params.matches,
|
||||
{
|
||||
includeDisabled: params.includeDisabled,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProviderOwners(params: ResolveProviderOwnersParams): readonly string[] {
|
||||
const providerId = normalizeProviderId(params.providerId);
|
||||
if (!providerId) {
|
||||
return [];
|
||||
}
|
||||
return resolvePluginContributionOwners({
|
||||
...params,
|
||||
contribution: "providers",
|
||||
matches: (contributionId) => normalizeProviderId(contributionId) === providerId,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveChannelOwners(params: ResolveChannelOwnersParams): readonly string[] {
|
||||
const channelId = normalizeContributionId(params.channelId);
|
||||
if (!channelId) {
|
||||
return [];
|
||||
}
|
||||
return resolvePluginContributionOwners({
|
||||
...params,
|
||||
contribution: "channels",
|
||||
matches: channelId,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveCliBackendOwners(params: ResolveCliBackendOwnersParams): readonly string[] {
|
||||
const cliBackendId = normalizeContributionId(params.cliBackendId);
|
||||
if (!cliBackendId) {
|
||||
return [];
|
||||
}
|
||||
return resolvePluginContributionOwners({
|
||||
...params,
|
||||
contribution: "cliBackends",
|
||||
matches: cliBackendId,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveSetupProviderOwners(
|
||||
params: ResolveSetupProviderOwnersParams,
|
||||
): readonly string[] {
|
||||
const setupProviderId = normalizeContributionId(params.setupProviderId);
|
||||
if (!setupProviderId) {
|
||||
return [];
|
||||
}
|
||||
return resolvePluginContributionOwners({
|
||||
...params,
|
||||
contribution: "setupProviders",
|
||||
matches: setupProviderId,
|
||||
});
|
||||
}
|
||||
|
||||
export function inspectPluginRegistry(
|
||||
params: LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions = {},
|
||||
): Promise<PluginRegistryInspection> {
|
||||
return import("./installed-plugin-index-store.js").then((store) =>
|
||||
store.inspectPersistedInstalledPluginIndex(params),
|
||||
);
|
||||
}
|
||||
|
||||
export function refreshPluginRegistry(
|
||||
params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions,
|
||||
): Promise<PluginRegistrySnapshot> {
|
||||
return import("./installed-plugin-index-store.js").then((store) =>
|
||||
store.refreshPersistedInstalledPluginIndex(params),
|
||||
);
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { ModelProviderConfig } from "../config/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
listInstalledPluginContributionIds,
|
||||
loadInstalledPluginIndex,
|
||||
type InstalledPluginIndex,
|
||||
type LoadInstalledPluginIndexParams,
|
||||
} from "./installed-plugin-index.js";
|
||||
listPluginContributionIds,
|
||||
loadPluginRegistrySnapshot,
|
||||
type LoadPluginRegistryParams,
|
||||
type PluginRegistrySnapshot,
|
||||
} from "./plugin-registry.js";
|
||||
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
||||
|
||||
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
|
||||
@@ -44,8 +44,8 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = {
|
||||
discoveryEntriesOnly?: boolean;
|
||||
};
|
||||
|
||||
export type ResolveInstalledPluginProviderContributionIdsParams = LoadInstalledPluginIndexParams & {
|
||||
index?: InstalledPluginIndex;
|
||||
export type ResolveInstalledPluginProviderContributionIdsParams = LoadPluginRegistryParams & {
|
||||
index?: PluginRegistrySnapshot;
|
||||
includeDisabled?: boolean;
|
||||
};
|
||||
|
||||
@@ -56,9 +56,11 @@ function sortedValues(values: Iterable<string>): string[] {
|
||||
export function resolveInstalledPluginProviderContributionIds(
|
||||
params: ResolveInstalledPluginProviderContributionIdsParams = {},
|
||||
): string[] {
|
||||
const index = params.index ?? loadInstalledPluginIndex(params);
|
||||
const index = params.index ?? loadPluginRegistrySnapshot(params);
|
||||
return sortedValues(
|
||||
listInstalledPluginContributionIds(index, "providers", {
|
||||
listPluginContributionIds({
|
||||
index,
|
||||
contribution: "providers",
|
||||
includeDisabled: params.includeDisabled,
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user