refactor(plugins): unify plugin entry ordering (#128170)

This commit is contained in:
Vincent Koc
2026-08-23 03:40:21 -07:00
committed by GitHub
parent 5e1a28bb56
commit 2bbe3efcbf
7 changed files with 96 additions and 73 deletions
+2 -13
View File
@@ -7,20 +7,9 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveEnabledBundledManifestContractPlugins } from "./bundled-manifest-contract-plugins.js";
import { loadBundledDocumentExtractorEntriesFromDir } from "./document-extractor-public-artifacts.js";
import type { PluginDocumentExtractorEntry } from "./document-extractor-types.js";
import { sortPluginEntriesForAutoDetect } from "./plugin-entry-order.js";
import { createPluginIdScopeSet } from "./plugin-scope.js";
function compareExtractors(
left: PluginDocumentExtractorEntry,
right: PluginDocumentExtractorEntry,
): number {
const leftOrder = left.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
function resolveExplicitAllowedDocumentExtractorPluginIds(params: {
config?: OpenClawConfig;
onlyPluginIds?: readonly string[];
@@ -82,5 +71,5 @@ export function resolvePluginDocumentExtractors(params?: {
cause: loadErrors.length === 1 ? loadErrors[0] : new AggregateError(loadErrors),
});
}
return extractors.toSorted(compareExtractors);
return sortPluginEntriesForAutoDetect(extractors);
}
+54
View File
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { sortPluginEntriesById, sortPluginEntriesForAutoDetect } from "./plugin-entry-order.js";
type TestEntry = {
id: string;
pluginId: string;
autoDetectOrder?: number;
};
function toKeys(entries: readonly TestEntry[]): string[] {
return entries.map((entry) => `${entry.id}:${entry.pluginId}`);
}
describe("plugin entry order", () => {
it("sorts identities without mutating the input", () => {
const entries = [
{ id: "beta", pluginId: "plugin-b" },
{ id: "alpha", pluginId: "plugin-z" },
{ id: "alpha", pluginId: "plugin-a" },
] satisfies TestEntry[];
expect(toKeys(sortPluginEntriesById(entries))).toEqual([
"alpha:plugin-a",
"alpha:plugin-z",
"beta:plugin-b",
]);
expect(toKeys(entries)).toEqual(["beta:plugin-b", "alpha:plugin-z", "alpha:plugin-a"]);
});
it("sorts auto-detect priorities before deterministic identity ties", () => {
const entries = [
{ id: "unordered", pluginId: "plugin-u" },
{ id: "beta", pluginId: "plugin-b", autoDetectOrder: 10 },
{ id: "alpha", pluginId: "plugin-z", autoDetectOrder: 10 },
{ id: "alpha", pluginId: "plugin-a", autoDetectOrder: 10 },
{ id: "first", pluginId: "plugin-f", autoDetectOrder: 1 },
] satisfies TestEntry[];
expect(toKeys(sortPluginEntriesForAutoDetect(entries))).toEqual([
"first:plugin-f",
"alpha:plugin-a",
"alpha:plugin-z",
"beta:plugin-b",
"unordered:plugin-u",
]);
expect(toKeys(entries)).toEqual([
"unordered:plugin-u",
"beta:plugin-b",
"alpha:plugin-z",
"alpha:plugin-a",
"first:plugin-f",
]);
});
});
+30
View File
@@ -0,0 +1,30 @@
type PluginEntryIdentity = {
id: string;
pluginId: string;
};
type PluginAutoDetectEntry = PluginEntryIdentity & {
autoDetectOrder?: number;
};
function comparePluginEntryIdentity(left: PluginEntryIdentity, right: PluginEntryIdentity): number {
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
export function sortPluginEntriesById<T extends PluginEntryIdentity>(entries: readonly T[]): T[] {
return entries.toSorted(comparePluginEntryIdentity);
}
/** Sorts auto-detect candidates by priority, then stable plugin identity. */
export function sortPluginEntriesForAutoDetect<T extends PluginAutoDetectEntry>(
entries: readonly T[],
): T[] {
return entries.toSorted((left, right) => {
const leftOrder = left.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return comparePluginEntryIdentity(left, right);
});
}
+2 -13
View File
@@ -1,21 +1,10 @@
// Runtime bridge for web content extractors supplied by plugins.
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveEnabledBundledManifestContractPlugins } from "./bundled-manifest-contract-plugins.js";
import { sortPluginEntriesForAutoDetect } from "./plugin-entry-order.js";
import { loadBundledWebContentExtractorEntriesFromDir } from "./web-content-extractor-public-artifacts.js";
import type { PluginWebContentExtractorEntry } from "./web-content-extractor-types.js";
function compareExtractors(
left: PluginWebContentExtractorEntry,
right: PluginWebContentExtractorEntry,
): number {
const leftOrder = left.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
export function resolvePluginWebContentExtractors(params?: {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -38,5 +27,5 @@ export function resolvePluginWebContentExtractors(params?: {
extractors.push(...loaded);
}
}
return extractors.toSorted(compareExtractors);
return sortPluginEntriesForAutoDetect(extractors);
}
+4 -7
View File
@@ -1,23 +1,20 @@
// Shares web fetch provider loading helpers across provider plugins.
import type { PluginLoadOptions } from "./loader.js";
import type { PluginManifestRecord } from "./manifest-registry.js";
import { sortPluginEntriesById, sortPluginEntriesForAutoDetect } from "./plugin-entry-order.js";
import type { PluginWebFetchProviderEntry } from "./types.js";
import {
resolveBundledWebProviderResolutionConfig,
sortPluginProviders,
sortPluginProvidersForAutoDetect,
} from "./web-provider-resolution-shared.js";
import { resolveBundledWebProviderResolutionConfig } from "./web-provider-resolution-shared.js";
export function sortWebFetchProviders(
providers: PluginWebFetchProviderEntry[],
): PluginWebFetchProviderEntry[] {
return sortPluginProviders(providers);
return sortPluginEntriesById(providers);
}
export function sortWebFetchProvidersForAutoDetect(
providers: PluginWebFetchProviderEntry[],
): PluginWebFetchProviderEntry[] {
return sortPluginProvidersForAutoDetect(providers);
return sortPluginEntriesForAutoDetect(providers);
}
export function resolveBundledWebFetchResolutionConfig(params: {
@@ -15,39 +15,6 @@ type WebProviderCandidateResolution = {
manifestRecords?: readonly PluginManifestRecord[];
};
type WebProviderSortEntry = {
id: string;
pluginId: string;
autoDetectOrder?: number;
};
function comparePluginProvidersAlphabetically(
left: Pick<WebProviderSortEntry, "id" | "pluginId">,
right: Pick<WebProviderSortEntry, "id" | "pluginId">,
): number {
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
export function sortPluginProviders<T extends Pick<WebProviderSortEntry, "id" | "pluginId">>(
providers: T[],
): T[] {
return providers.toSorted(comparePluginProvidersAlphabetically);
}
/** Sorts provider candidates for auto-detect while keeping equal priorities deterministic. */
export function sortPluginProvidersForAutoDetect<T extends WebProviderSortEntry>(
providers: T[],
): T[] {
return providers.toSorted((left, right) => {
const leftOrder = left.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoDetectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return comparePluginProvidersAlphabetically(left, right);
});
}
function pluginManifestDeclaresProviderConfig(
record: PluginManifestRecord,
configKey: WebProviderConfigKey,
+4 -7
View File
@@ -1,23 +1,20 @@
// Shares web-search provider loading helpers across runtime paths.
import type { PluginLoadOptions } from "./loader.js";
import type { PluginManifestRecord } from "./manifest-registry.js";
import { sortPluginEntriesById, sortPluginEntriesForAutoDetect } from "./plugin-entry-order.js";
import type { PluginWebSearchProviderEntry } from "./types.js";
import {
resolveBundledWebProviderResolutionConfig,
sortPluginProviders,
sortPluginProvidersForAutoDetect,
} from "./web-provider-resolution-shared.js";
import { resolveBundledWebProviderResolutionConfig } from "./web-provider-resolution-shared.js";
export function sortWebSearchProviders(
providers: PluginWebSearchProviderEntry[],
): PluginWebSearchProviderEntry[] {
return sortPluginProviders(providers);
return sortPluginEntriesById(providers);
}
export function sortWebSearchProvidersForAutoDetect(
providers: PluginWebSearchProviderEntry[],
): PluginWebSearchProviderEntry[] {
return sortPluginProvidersForAutoDetect(providers);
return sortPluginEntriesForAutoDetect(providers);
}
export function resolveBundledWebSearchResolutionConfig(params: {