fix: reuse extractor manifest resolution pass

This commit is contained in:
Shakker
2026-04-27 08:59:09 +01:00
parent c60581740a
commit 51bd95fff3
4 changed files with 73 additions and 36 deletions

View File

@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
- Gateway/startup: extend `OPENCLAW_GATEWAY_STARTUP_TRACE=1` with per-phase event-loop delay plus plugin lookup-table timing and count metrics for installed-index, manifest, startup-plan, and owner-map work, and include the new timing fields in startup benchmark summaries. Thanks @shakkernerd.
- Plugins/channels: resolve read-only channel command defaults from one plugin index plus manifest pass instead of reloading plugin metadata while checking candidate plugin enablement. Thanks @shakkernerd.
- Plugins/contracts: resolve runtime manifest-contract plugin owners from one plugin index plus manifest pass instead of rebuilding manifest metadata separately for all owners and enabled owners. Thanks @shakkernerd.
- Plugins/extractors: reuse one manifest registry pass while resolving bundled document and web-content extractor plugins instead of rereading manifests for compatibility and enablement filtering. Thanks @shakkernerd.
- Plugins/registry: resolve lookup-table owner maps for providers, CLI backends, setup providers, command aliases, model catalogs, channel configs, and manifest contracts while preserving setup-only CLI backend ownership. Thanks @shakkernerd.
- Process/Windows: decode command stdout and stderr from raw bytes with console-codepage awareness, while preserving valid UTF-8 output and multibyte characters split across chunks. Fixes #50519. Thanks @iready, @kevinten10, @zhangyongjie1997, @knightplat-blip, @heiqishi666, and @slepybear.
- Bonjour/Windows: hide the bundled mDNS advertiser's Windows ARP shell probe so Gateway startup no longer flashes command-prompt windows. Fixes #70238. Thanks @alexandre-leng, @PratikRai0101, @infinitypacific, and @tomerpeled.

View File

@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { resolvePluginDocumentExtractors } from "./document-extractors.runtime.js";
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
vi.mock("./document-extractor-public-artifacts.js", () => ({
loadBundledDocumentExtractorEntriesFromDir: vi.fn(
@@ -78,6 +79,13 @@ vi.mock("./manifest-registry.js", () => ({
}));
describe("resolvePluginDocumentExtractors", () => {
it("reuses one manifest registry pass for compat and enabled bundled extractors", () => {
vi.mocked(loadPluginManifestRegistryForPluginRegistry).mockClear();
expect(resolvePluginDocumentExtractors().map((extractor) => extractor.id)).toEqual(["pdf"]);
expect(loadPluginManifestRegistryForPluginRegistry).toHaveBeenCalledOnce();
});
it("respects global plugin disablement", () => {
expect(
resolvePluginDocumentExtractors({

View File

@@ -22,21 +22,14 @@ function compareExtractors(
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
function resolveBundledDocumentExtractorCompatPluginIds(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
function listDocumentExtractorPluginIds(params: {
plugins: readonly PluginManifestRecord[];
onlyPluginIds?: readonly string[];
}): string[] {
const onlyPluginIdSet =
params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null;
return loadPluginManifestRegistryForPluginRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
})
.plugins.filter(
return params.plugins
.filter(
(plugin) =>
plugin.origin === "bundled" &&
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)) &&
@@ -46,6 +39,19 @@ function resolveBundledDocumentExtractorCompatPluginIds(params: {
.toSorted((left, right) => left.localeCompare(right));
}
function loadDocumentExtractorManifestRecords(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): readonly PluginManifestRecord[] {
return loadPluginManifestRegistryForPluginRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
}).plugins;
}
function resolveEnabledBundledDocumentExtractorPlugins(params: {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -55,6 +61,15 @@ function resolveEnabledBundledDocumentExtractorPlugins(params: {
if (params.config?.plugins?.enabled === false) {
return [];
}
let manifestRecords: readonly PluginManifestRecord[] | undefined;
const loadManifestRecords = (config?: OpenClawConfig) => {
manifestRecords ??= loadDocumentExtractorManifestRecords({
config,
workspaceDir: params.workspaceDir,
env: params.env,
});
return manifestRecords;
};
const activation = resolveBundledPluginCompatibleLoadValues({
rawConfig: params.config,
@@ -67,7 +82,11 @@ function resolveEnabledBundledDocumentExtractorPlugins(params: {
enablement: "allowlist",
vitest: true,
},
resolveCompatPluginIds: resolveBundledDocumentExtractorCompatPluginIds,
resolveCompatPluginIds: (compatParams) =>
listDocumentExtractorPluginIds({
plugins: loadManifestRecords(compatParams.config),
onlyPluginIds: compatParams.onlyPluginIds,
}),
});
const normalizedPlugins = normalizePluginsConfig(activation.config?.plugins);
const activationSource = createPluginActivationSource({
@@ -75,12 +94,7 @@ function resolveEnabledBundledDocumentExtractorPlugins(params: {
});
const onlyPluginIdSet =
params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null;
return loadPluginManifestRegistryForPluginRegistry({
config: activation.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
}).plugins.filter((plugin) => {
return loadManifestRecords(activation.config).filter((plugin) => {
if (
plugin.origin !== "bundled" ||
(onlyPluginIdSet && !onlyPluginIdSet.has(plugin.id)) ||

View File

@@ -22,21 +22,14 @@ function compareExtractors(
return left.id.localeCompare(right.id) || left.pluginId.localeCompare(right.pluginId);
}
function resolveBundledWebContentExtractorCompatPluginIds(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
function listWebContentExtractorPluginIds(params: {
plugins: readonly PluginManifestRecord[];
onlyPluginIds?: readonly string[];
}): string[] {
const onlyPluginIdSet =
params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null;
return loadPluginManifestRegistryForPluginRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
})
.plugins.filter(
return params.plugins
.filter(
(plugin) =>
plugin.origin === "bundled" &&
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)) &&
@@ -46,6 +39,19 @@ function resolveBundledWebContentExtractorCompatPluginIds(params: {
.toSorted((left, right) => left.localeCompare(right));
}
function loadWebContentExtractorManifestRecords(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): readonly PluginManifestRecord[] {
return loadPluginManifestRegistryForPluginRegistry({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
}).plugins;
}
function resolveEnabledBundledExtractorPlugins(params: {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -55,6 +61,15 @@ function resolveEnabledBundledExtractorPlugins(params: {
if (params.config?.plugins?.enabled === false) {
return [];
}
let manifestRecords: readonly PluginManifestRecord[] | undefined;
const loadManifestRecords = (config?: OpenClawConfig) => {
manifestRecords ??= loadWebContentExtractorManifestRecords({
config,
workspaceDir: params.workspaceDir,
env: params.env,
});
return manifestRecords;
};
const activation = resolveBundledPluginCompatibleLoadValues({
rawConfig: params.config,
@@ -67,7 +82,11 @@ function resolveEnabledBundledExtractorPlugins(params: {
enablement: "always",
vitest: true,
},
resolveCompatPluginIds: resolveBundledWebContentExtractorCompatPluginIds,
resolveCompatPluginIds: (compatParams) =>
listWebContentExtractorPluginIds({
plugins: loadManifestRecords(compatParams.config),
onlyPluginIds: compatParams.onlyPluginIds,
}),
});
const normalizedPlugins = normalizePluginsConfig(activation.config?.plugins);
const activationSource = createPluginActivationSource({
@@ -75,12 +94,7 @@ function resolveEnabledBundledExtractorPlugins(params: {
});
const onlyPluginIdSet =
params.onlyPluginIds && params.onlyPluginIds.length > 0 ? new Set(params.onlyPluginIds) : null;
return loadPluginManifestRegistryForPluginRegistry({
config: activation.config,
workspaceDir: params.workspaceDir,
env: params.env,
includeDisabled: true,
}).plugins.filter((plugin) => {
return loadManifestRecords(activation.config).filter((plugin) => {
if (
plugin.origin !== "bundled" ||
(onlyPluginIdSet && !onlyPluginIdSet.has(plugin.id)) ||