fix: scope web provider ownership to plugin index

This commit is contained in:
Shakker
2026-04-26 04:30:22 +01:00
parent 6d4f65c9d4
commit 2e7635f4f9
8 changed files with 121 additions and 6 deletions

View File

@@ -66,6 +66,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Providers/Z.AI: map OpenClaw thinking controls to Z.AI's `thinking` payload and add opt-in preserved thinking replay via `params.preserveThinking`, so GLM 5.x can keep prior `reasoning_content` when requested. Fixes #58680. Thanks @xuanmingguo.
- Plugins/registry: resolve web provider ownership from the installed plugin index instead of broad manifest scans on secret, tool, and pricing paths. Thanks @shakkernerd.
- TTS: strip model-emitted TTS directives from streamed block text before channel
delivery, including directives split across adjacent blocks, while preserving
the accumulated raw reply for final-mode synthesis. Fixes #38937.

View File

@@ -1,5 +1,5 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveManifestContractOwnerPluginId } from "../../plugins/manifest-registry.js";
import { resolveManifestContractOwnerPluginId } from "../../plugins/plugin-registry.js";
import type { RuntimeWebSearchMetadata } from "../../secrets/runtime-web-tools.types.js";
import {
resolveWebSearchDefinition,

View File

@@ -4,7 +4,7 @@ import { callGateway } from "../gateway/call.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../gateway/protocol/client-info.js";
import { validateSecretsResolveResult } from "../gateway/protocol/index.js";
import { formatErrorMessage } from "../infra/errors.js";
import { resolveManifestContractOwnerPluginId } from "../plugins/manifest-registry.js";
import { resolveManifestContractOwnerPluginId } from "../plugins/plugin-registry.js";
import {
analyzeCommandSecretAssignmentsFromSnapshot,
type UnresolvedCommandSecretAssignment,

View File

@@ -10,7 +10,7 @@ import {
import { resolvePluginWebSearchConfig } from "../config/plugin-web-search-config.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveManifestContractPluginIds } from "../plugins/manifest-registry.js";
import { resolveManifestContractPluginIds } from "../plugins/plugin-registry.js";
import { normalizeProviderModelIdWithPlugin } from "../plugins/provider-runtime.js";
import { normalizeOptionalString, resolvePrimaryStringValue } from "../shared/string-coerce.js";
import {

View File

@@ -68,7 +68,7 @@ function resolvePluginSourcePath(sourcePath: string): string {
return sourcePath;
}
type PluginManifestContractListKey =
export type PluginManifestContractListKey =
| "speechProviders"
| "externalAuthProviders"
| "mediaUnderstandingProviders"

View File

@@ -24,6 +24,9 @@ import {
refreshPluginRegistry,
resolveChannelOwners,
resolveCliBackendOwners,
resolveManifestContractOwnerPluginId,
resolveManifestContractPluginIds,
resolveManifestContractPluginIdsByCompatibilityRuntimePath,
resolvePluginContributionOwners,
resolveProviderOwners,
resolveSetupProviderOwners,
@@ -85,6 +88,10 @@ function createCandidate(rootDir: string): PluginCandidate {
commandAliases: [{ name: "demo-command" }],
contracts: {
tools: ["demo-tool"],
webSearchProviders: ["demo-search"],
},
configContracts: {
compatibilityRuntimePaths: ["tools.web.search.demo-search.apiKey"],
},
}),
"utf8",
@@ -159,6 +166,23 @@ describe("plugin registry facade", () => {
}),
).toEqual(["demo"]);
expect(resolveSetupProviderOwners({ index, setupProviderId: "demo-setup" })).toEqual(["demo"]);
expect(resolveManifestContractPluginIds({ index, contract: "webSearchProviders" })).toEqual([
"demo",
]);
expect(
resolveManifestContractOwnerPluginId({
index,
contract: "webSearchProviders",
value: "demo-search",
}),
).toBe("demo");
expect(
resolveManifestContractPluginIdsByCompatibilityRuntimePath({
index,
contract: "webSearchProviders",
path: "tools.web.search.demo-search.apiKey",
}),
).toEqual(["demo"]);
});
it("keeps disabled records inspectable while excluding owners by default", () => {

View File

@@ -24,7 +24,12 @@ import {
type RefreshInstalledPluginIndexParams,
} from "./installed-plugin-index.js";
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import type {
PluginManifestContractListKey,
PluginManifestRecord,
PluginManifestRegistry,
} from "./manifest-registry.js";
import type { PluginOrigin } from "./plugin-origin.types.js";
export type PluginRegistrySnapshot = InstalledPluginIndex;
export type PluginRegistryRecord = InstalledPluginIndexRecord;
@@ -107,6 +112,25 @@ export type ResolveSetupProviderOwnersParams = PluginRegistryContributionOptions
setupProviderId: string;
};
export type ResolveManifestContractPluginIdsParams = LoadPluginRegistryParams & {
contract: PluginManifestContractListKey;
origin?: PluginOrigin;
onlyPluginIds?: readonly string[];
};
export type ResolveManifestContractOwnerPluginIdParams = LoadPluginRegistryParams & {
contract: PluginManifestContractListKey;
value: string | undefined;
origin?: PluginOrigin;
};
export type ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams =
LoadPluginRegistryParams & {
contract: PluginManifestContractListKey;
path: string | undefined;
origin?: PluginOrigin;
};
function normalizeContributionId(value: string): string {
return value.trim();
}
@@ -139,6 +163,25 @@ function collectContractKeys(plugin: PluginManifestRecord): readonly string[] {
);
}
function listManifestContractValues(
plugin: PluginManifestRecord,
contract: PluginManifestContractListKey,
): readonly string[] {
return plugin.contracts?.[contract] ?? [];
}
function loadManifestContractRegistry(
params: LoadPluginRegistryParams & {
onlyPluginIds?: readonly string[];
},
): PluginManifestRegistry {
return loadPluginManifestRegistryForPluginRegistry({
...params,
pluginIds: params.onlyPluginIds,
includeDisabled: true,
});
}
function listManifestContributionIds(
plugin: PluginManifestRecord,
contribution: PluginRegistryContributionKey,
@@ -440,6 +483,53 @@ export function resolveSetupProviderOwners(
});
}
export function resolveManifestContractPluginIds(
params: ResolveManifestContractPluginIdsParams,
): string[] {
return loadManifestContractRegistry(params)
.plugins.filter(
(plugin) =>
(!params.origin || plugin.origin === params.origin) &&
listManifestContractValues(plugin, params.contract).length > 0,
)
.map((plugin) => plugin.id)
.toSorted((left, right) => left.localeCompare(right));
}
export function resolveManifestContractPluginIdsByCompatibilityRuntimePath(
params: ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams,
): string[] {
const normalizedPath = params.path?.trim();
if (!normalizedPath) {
return [];
}
return loadManifestContractRegistry(params)
.plugins.filter(
(plugin) =>
(!params.origin || plugin.origin === params.origin) &&
listManifestContractValues(plugin, params.contract).length > 0 &&
(plugin.configContracts?.compatibilityRuntimePaths ?? []).includes(normalizedPath),
)
.map((plugin) => plugin.id)
.toSorted((left, right) => left.localeCompare(right));
}
export function resolveManifestContractOwnerPluginId(
params: ResolveManifestContractOwnerPluginIdParams,
): string | undefined {
const normalizedValue = normalizeContributionId(params.value ?? "").toLowerCase();
if (!normalizedValue) {
return undefined;
}
return loadManifestContractRegistry(params).plugins.find(
(plugin) =>
(!params.origin || plugin.origin === params.origin) &&
listManifestContractValues(plugin, params.contract).some(
(candidate) => normalizeContributionId(candidate).toLowerCase() === normalizedValue,
),
)?.id;
}
export function inspectPluginRegistry(
params: LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions = {},
): Promise<PluginRegistryInspection> {

View File

@@ -2,4 +2,4 @@ export {
resolveManifestContractOwnerPluginId,
resolveManifestContractPluginIds,
resolveManifestContractPluginIdsByCompatibilityRuntimePath,
} from "../plugins/manifest-registry.js";
} from "../plugins/plugin-registry.js";