fix(plugins): treat context-engine plugins as capabilities in status/inspect (#58766)

Merged via squash.

Prepared head SHA: 23269d2db5
Co-authored-by: zhuisDEV <95547369+zhuisDEV@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
Brian
2026-04-14 00:32:24 +10:00
committed by GitHub
parent 6c12ec1ed2
commit 143c1e81a2
8 changed files with 49 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
- Agents/context engines: run opt-in turn maintenance as idle-aware background work so the next foreground turn no longer waits on proactive maintenance. (#65233) thanks @100yenadmin
- Plugins/status: report the registered context-engine IDs in `plugins inspect` instead of the owning plugin ID, so non-matching engine IDs and multi-engine plugins are classified correctly. (#58766) thanks @zhuisDEV
## 2026.4.12
### Changes

View File

@@ -2608,6 +2608,12 @@ module.exports = { id: "throws-after-import", register() {} };`,
selectCount: () => 1,
duplicateMessage:
"context engine already registered: shared-context-engine-loader-test (plugin:context-engine-owner-a)",
assertPrimaryOwner: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
expect(
registry.plugins.find((entry) => entry.id === "context-engine-owner-a")
?.contextEngineIds,
).toEqual(["shared-context-engine-loader-test"]);
},
assert: expectDuplicateRegistrationResult,
},
{

View File

@@ -733,6 +733,7 @@ function createPluginRecord(params: {
musicGenerationProviderIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
contextEngineIds: [],
memoryEmbeddingProviderIds: [],
agentHarnessIds: [],
gatewayMethods: [],

View File

@@ -246,6 +246,7 @@ export type PluginRecord = {
musicGenerationProviderIds: string[];
webFetchProviderIds: string[];
webSearchProviderIds: string[];
contextEngineIds?: string[];
memoryEmbeddingProviderIds: string[];
agentHarnessIds: string[];
gatewayMethods: string[];

View File

@@ -1220,6 +1220,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
source: record.source,
message: `context engine already registered: ${id} (${result.existingOwner})`,
});
return;
}
if (!record.contextEngineIds?.includes(id)) {
record.contextEngineIds = [...(record.contextEngineIds ?? []), id];
}
},
registerCompactionProvider: (

View File

@@ -59,6 +59,7 @@ export function createPluginRecord(
musicGenerationProviderIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
contextEngineIds: [],
memoryEmbeddingProviderIds: [],
agentHarnessIds: [],
gatewayMethods: [],

View File

@@ -656,6 +656,32 @@ describe("plugin status reports", () => {
expect(inspect.capabilities).toEqual([{ kind: "cli-backend", ids: ["claude-cli"] }]);
});
it("treats a context-engine plugin as a plain capability", () => {
setPluginLoadResult({
plugins: [
createPluginRecord({
id: "moon",
name: "Moon",
kind: "context-engine",
contextEngineIds: ["moon-engine"],
hookCount: 1,
}),
],
hooks: [createCustomHook({ pluginId: "moon", events: ["message"] })],
});
const inspect = expectInspectReport("moon");
expectInspectShape(inspect, {
shape: "plain-capability",
capabilityMode: "plain",
capabilityKinds: ["context-engine"],
});
expect(inspect.capabilities).toEqual([{ kind: "context-engine", ids: ["moon-engine"] }]);
expect(inspect.compatibility).toEqual([]);
expectNoCompatibilityWarnings();
});
it("builds compatibility warnings for legacy compatibility paths", () => {
setPluginLoadResult({
plugins: [

View File

@@ -21,6 +21,7 @@ import {
resolvePluginRuntimeLoadContext,
} from "./runtime/load-context.js";
import { loadPluginMetadataRegistrySnapshot } from "./runtime/metadata-registry-loader.js";
import { hasKind } from "./slots.js";
import type { PluginHookName } from "./types.js";
export type PluginStatusReport = PluginRegistry & {
@@ -37,6 +38,7 @@ export type PluginCapabilityKind =
| "image-generation"
| "web-search"
| "agent-harness"
| "context-engine"
| "channel";
export type PluginInspectShape =
@@ -249,6 +251,13 @@ function buildCapabilityEntries(plugin: PluginRegistry["plugins"][number]) {
{ kind: "image-generation" as const, ids: plugin.imageGenerationProviderIds },
{ kind: "web-search" as const, ids: plugin.webSearchProviderIds },
{ kind: "agent-harness" as const, ids: plugin.agentHarnessIds },
{
kind: "context-engine" as const,
ids:
plugin.status === "loaded" && hasKind(plugin.kind, "context-engine")
? (plugin.contextEngineIds ?? [])
: [],
},
{ kind: "channel" as const, ids: plugin.channelIds },
].filter((entry) => entry.ids.length > 0);
}