test(plugins): cover config-origin tool cold load diagnostics

This commit is contained in:
Peter Steinberger
2026-05-03 12:57:35 +01:00
parent a3b94f3910
commit d00bcf555b
2 changed files with 106 additions and 11 deletions

View File

@@ -277,7 +277,19 @@ function installToolManifestSnapshots(params: {
policyHash: "test",
generatedAtMs: 0,
installRecords: {},
plugins: [],
plugins: plugins.map((plugin) => ({
pluginId: String(plugin.id),
origin: plugin.origin,
enabled: true,
enabledByDefault: plugin.enabledByDefault,
startup: {
sidecar: false,
memory: false,
deferConfiguredChannelFullLoadUntilAfterListen: false,
agentHarnesses: [],
},
compat: [],
})),
diagnostics: [],
},
registryDiagnostics: [],
@@ -301,7 +313,7 @@ function installToolManifestSnapshots(params: {
manifestRegistryMs: 0,
ownerMapsMs: 0,
totalMs: 0,
indexPluginCount: 0,
indexPluginCount: plugins.length,
manifestPluginCount: plugins.length,
},
} as never,
@@ -491,16 +503,28 @@ describe("resolvePluginTools optional tools", () => {
);
});
it("auto-loads cold registry for path-based (bundled-origin) plugins without pre-warming (#76598)", () => {
const config = createContext().config;
it("auto-loads cold registry for path-based config-origin plugins without pre-warming (#76598)", () => {
const context = {
...createContext(),
config: {
...createContext().config,
plugins: {
...createContext().config.plugins,
entries: {
"optional-demo": { enabled: true },
},
},
},
};
const config = context.config;
const registry = createToolRegistry([createOptionalDemoEntry()]);
loadOpenClawPluginsMock.mockReturnValue(registry);
installToolManifestSnapshot({
config,
plugin: {
id: "optional-demo",
origin: "bundled",
enabledByDefault: true,
origin: "config",
enabledByDefault: undefined,
channels: [],
providers: [],
contracts: {
@@ -514,6 +538,7 @@ describe("resolvePluginTools optional tools", () => {
// This is the regression path from PR #76004 where path-based plugin tools disappeared.
const tools = resolvePluginTools(
createResolveToolsParams({
context,
toolAllowlist: ["optional_tool"],
}),
);
@@ -528,6 +553,50 @@ describe("resolvePluginTools optional tools", () => {
);
});
it("warns when cold registry load still does not provide the selected plugin tools", () => {
const context = {
...createContext(),
config: {
...createContext().config,
plugins: {
...createContext().config.plugins,
entries: {
"optional-demo": { enabled: true },
},
},
},
};
const config = context.config;
const registry = createToolRegistry([]);
loadOpenClawPluginsMock.mockReturnValue(registry);
installToolManifestSnapshot({
config,
plugin: {
id: "optional-demo",
origin: "config",
enabledByDefault: undefined,
channels: [],
providers: [],
contracts: {
tools: ["optional_tool"],
},
},
});
const tools = resolvePluginTools(
createResolveToolsParams({
context,
toolAllowlist: ["optional_tool"],
}),
);
expect(tools).toEqual([]);
expectSingleDiagnosticMessage(
registry.diagnostics,
"plugin tool registry did not include selected plugin tools after cold load (optional-demo)",
);
});
it("does not reuse a pinned gateway registry for manifest-unavailable tools", () => {
const config = createContext().config;
installToolManifestSnapshot({

View File

@@ -812,21 +812,47 @@ export function resolvePluginTools(params: {
// Cold registry: path-based plugins (origin "config") registered via plugins.load.paths
// are not pinned to any active channel/surface registry until explicitly loaded.
// Trigger a standalone load so their tool factories become available, then retry.
ensureStandaloneRuntimePluginRegistryLoaded({
surface: "channel",
requiredPluginIds: runtimePluginIds,
loadOptions,
});
try {
ensureStandaloneRuntimePluginRegistryLoaded({
surface: "channel",
requiredPluginIds: runtimePluginIds,
loadOptions,
});
} catch (error) {
context.logger.error(
`failed to cold-load plugin tool registry for plugin ids [${runtimePluginIds.join(", ")}]: ${
error instanceof Error ? error.message : String(error)
}`,
);
throw error;
}
registry = resolvePluginToolRegistry({
loadOptions,
onlyPluginIds: runtimePluginIds,
});
if (!registry) {
context.logger.warn(
`plugin tool registry still unavailable after cold load for plugin ids [${runtimePluginIds.join(
", ",
)}]`,
);
return tools;
}
}
const scopedPluginIds = new Set(runtimePluginIds);
const registryToolPluginIds = new Set(registry.tools.map((entry) => entry.pluginId));
const missingRegistryToolPluginIds = runtimePluginIds.filter(
(pluginId) => !registryToolPluginIds.has(pluginId),
);
for (const pluginId of missingRegistryToolPluginIds) {
registry.diagnostics.push({
level: "warn",
pluginId,
source: "plugin-tools",
message: `plugin tool registry did not include selected plugin tools after cold load (${pluginId})`,
});
}
const blockedPlugins = new Set<string>();
const factoryTimingStartedAt = Date.now();
const factoryTimings: PluginToolFactoryTiming[] = [];