mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
1ca60fbc3a
* refactor(agents): make roster ownership explicit * feat(config): materialize legacy agent roles * fix(cron): migrate legacy owners at startup * feat(gateway): expose agent selection contracts * fix(gateway): enforce agent-scoped authorization * docs(config): document explicit agent ownership * fix(config): pin retained owner workspace * fix(gateway): target hook wakes at effective agent * fix(sessions): preserve fixed-store ownership * fix: preserve retained agent ownership * fix: preserve legacy agent ownership across runtime surfaces * fix: fail closed on ambiguous session ownership * fix: preserve compatibility owners across dispatch and writes * fix: preserve retained agent projections * fix: preserve agent ownership compatibility * fix: preserve per-agent heartbeat guidance * fix: preserve compatibility owners in generic paths * fix: enforce configured ownership in session paths * fix: defer remote roster selection * fix: preserve ownership across session and config writes * fix: fail closed on ambiguous restored ownership * fix: preserve explicit ACP and legacy ownership * fix: honor durable fixed-store ownership * fix: enforce fixed-store owner authority * fix: preserve ownership evidence boundaries * fix: honor resolved session ownership * fix: align compatibility ownership paths * fix: persist legacy main store ownership * fix: close ownership fallback gaps * fix(agents): close retained owner compatibility gaps * fix(agents): enforce session owner resolution * fix(agents): complete session owner resolution sweep * fix(agents): preserve durable session ownership * fix: complete persisted session owner routing * fix: thread prepared session owners * fix: preserve stable session ownership * fix: enforce session ownership boundaries * fix: close session ownership delta gaps * fix: reconcile session ownership after rebase * fix: reconcile ownership with current main * fix: align session store path imports * fix: align session store config path import * fix: reconcile explicit ownership CI * fix: reconcile ownership rebase checks * fix: align ownership ci contracts * fix: align ownership rebase checks * fix: preserve compatibility owner during setup * fix(doctor): migrate ownerless heartbeat monitors * fix(gateway): preserve explicit session ownership * test: align ownership fixtures after rebase * test: complete plugin manifest fixture * test: align runtime context mocks * fix(gateway): preserve alias routing for existing sessions * style: format agent routing update * fix(gateway): preserve selected owner during alias routing * style: normalize rebased ownership files * fix(gateway): preserve owner through global alias routing * fix(gateway): preserve explicit ownership at HTTP boundaries * fix(gateway): validate compatibility model ownership * fix(agents): reconcile strict session ownership * fix(agents): contain media yield callback failures * fix(agents): avoid eager bare-key owner resolution * chore: refresh rebased ownership baselines * chore: align hosted plugin SDK baseline * chore: refresh ownership baselines after main sync * chore: refresh ownership baselines after main sync * test: align routed event owner fixtures * chore: retrigger CI after runner startup failure * chore: refresh ownership SDK budgets after main sync * fix(tasks): require agent identity for bare owners * chore: align Linux plugin SDK baseline * chore: remove release-owned changelog entry
158 lines
6.0 KiB
TypeScript
158 lines
6.0 KiB
TypeScript
/** Resolves plugin config contract metadata for scanners and secret/config policy checks. */
|
|
import { normalizeSortedUniqueStringEntries } from "@openclaw/normalization-core/string-normalization";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { findBundledPluginMetadataById } from "./bundled-plugin-metadata.js";
|
|
import { discoverOpenClawPlugins, type PluginDiscoveryResult } from "./discovery.js";
|
|
import {
|
|
loadPluginManifestRegistryCore,
|
|
type PluginManifestRegistry,
|
|
} from "./manifest-registry.js";
|
|
import type { PluginManifestConfigContracts } from "./manifest.js";
|
|
import type { PluginOrigin } from "./plugin-origin.types.js";
|
|
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
|
export { collectPluginConfigContractMatches } from "./config-contract-matches.js";
|
|
|
|
type PluginConfigContractMetadata = {
|
|
/** Runtime origin that supplied the contract metadata. */
|
|
origin: PluginOrigin;
|
|
/** Manifest-declared config contract paths used by secret/security/config scanners. */
|
|
configContracts: PluginManifestConfigContracts;
|
|
};
|
|
|
|
/** Resolve config contract metadata for plugin ids through the runtime registry and bundled fallback. */
|
|
export function resolvePluginConfigContractsById(params: {
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
fallbackToBundledMetadata?: boolean;
|
|
fallbackToBundledMetadataForResolvedBundled?: boolean;
|
|
fallbackBundledPluginIds?: readonly string[];
|
|
pluginIds: readonly string[];
|
|
discovery?: PluginDiscoveryResult;
|
|
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
|
|
}): ReadonlyMap<string, PluginConfigContractMetadata> {
|
|
const matches = new Map<string, PluginConfigContractMetadata>();
|
|
const pluginIds = normalizeSortedUniqueStringEntries(params.pluginIds);
|
|
if (pluginIds.length === 0) {
|
|
return matches;
|
|
}
|
|
const fallbackBundledPluginIds = new Set(
|
|
normalizeSortedUniqueStringEntries(params.fallbackBundledPluginIds),
|
|
);
|
|
const bundledContractFallbacks = new Map<string, PluginManifestConfigContracts | undefined>();
|
|
const findBundledConfigContracts = (
|
|
pluginId: string,
|
|
): PluginManifestConfigContracts | undefined => {
|
|
if (bundledContractFallbacks.has(pluginId)) {
|
|
return bundledContractFallbacks.get(pluginId);
|
|
}
|
|
const discovery =
|
|
params.discovery ??
|
|
discoverOpenClawPlugins({
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
});
|
|
const registry = loadPluginManifestRegistryCore({
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
candidates: discovery.candidates.filter((candidate) => candidate.origin === "bundled"),
|
|
diagnostics: discovery.diagnostics,
|
|
});
|
|
for (const plugin of registry.plugins) {
|
|
bundledContractFallbacks.set(plugin.id, plugin.configContracts);
|
|
}
|
|
if (bundledContractFallbacks.get(pluginId) === undefined) {
|
|
const bundledMetadata = findBundledPluginMetadataById(pluginId, {
|
|
includeChannelConfigs: false,
|
|
includeSyntheticChannelConfigs: false,
|
|
});
|
|
if (bundledMetadata?.manifest.configContracts) {
|
|
bundledContractFallbacks.set(pluginId, bundledMetadata.manifest.configContracts);
|
|
}
|
|
}
|
|
if (!bundledContractFallbacks.has(pluginId)) {
|
|
bundledContractFallbacks.set(pluginId, undefined);
|
|
}
|
|
return bundledContractFallbacks.get(pluginId);
|
|
};
|
|
|
|
const resolvedPluginOrigins = new Map<string, PluginOrigin>();
|
|
const registry =
|
|
params.manifestRegistry ??
|
|
loadPluginManifestRegistryForPluginRegistry({
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
includeDisabled: true,
|
|
});
|
|
for (const plugin of registry.plugins) {
|
|
if (!pluginIds.includes(plugin.id)) {
|
|
continue;
|
|
}
|
|
resolvedPluginOrigins.set(plugin.id, plugin.origin);
|
|
if (!plugin.configContracts) {
|
|
continue;
|
|
}
|
|
matches.set(plugin.id, {
|
|
origin: plugin.origin,
|
|
configContracts: plugin.configContracts,
|
|
});
|
|
}
|
|
|
|
if (params.fallbackToBundledMetadata ?? true) {
|
|
for (const pluginId of pluginIds) {
|
|
const existing = matches.get(pluginId);
|
|
const shouldHydrateBundledMatch =
|
|
existing &&
|
|
((params.fallbackToBundledMetadataForResolvedBundled && existing.origin === "bundled") ||
|
|
(!params.manifestRegistry && fallbackBundledPluginIds.has(pluginId)));
|
|
if (shouldHydrateBundledMatch) {
|
|
const bundledConfigContracts = findBundledConfigContracts(pluginId);
|
|
if (bundledConfigContracts) {
|
|
// Bundled metadata can carry richer contract declarations than installed registry entries;
|
|
// installed declarations still win except for bundled secret input coverage.
|
|
matches.set(pluginId, {
|
|
origin: fallbackBundledPluginIds.has(pluginId) ? "bundled" : existing.origin,
|
|
configContracts: {
|
|
...bundledConfigContracts,
|
|
...existing.configContracts,
|
|
...(bundledConfigContracts.secretInputs
|
|
? { secretInputs: bundledConfigContracts.secretInputs }
|
|
: {}),
|
|
},
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
if (matches.has(pluginId)) {
|
|
continue;
|
|
}
|
|
const resolvedOrigin = resolvedPluginOrigins.get(pluginId);
|
|
if (
|
|
resolvedOrigin &&
|
|
!(params.fallbackToBundledMetadataForResolvedBundled && resolvedOrigin === "bundled") &&
|
|
!fallbackBundledPluginIds.has(pluginId)
|
|
) {
|
|
continue;
|
|
}
|
|
if (params.manifestRegistry && resolvedOrigin && resolvedOrigin !== "bundled") {
|
|
continue;
|
|
}
|
|
if (params.manifestRegistry && !fallbackBundledPluginIds.has(pluginId)) {
|
|
continue;
|
|
}
|
|
const bundledConfigContracts = findBundledConfigContracts(pluginId);
|
|
if (!bundledConfigContracts) {
|
|
continue;
|
|
}
|
|
matches.set(pluginId, {
|
|
origin: "bundled",
|
|
configContracts: bundledConfigContracts,
|
|
});
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|