mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 13:08:38 +00:00
* refactor: share talk event metric extraction * refactor: reuse shared coercion helpers * refactor: reuse shared primitive guards * refactor: reuse shared record guard * refactor: reuse shared primitive helpers * refactor: reuse shared string guards * refactor: reuse shared non-empty string guard * refactor: share plugin primitive coercion helpers * refactor: reuse plugin coercion helpers * refactor: reuse plugin coercion helpers in more plugins * refactor: reuse channel coercion helpers * refactor: reuse monitor coercion helpers * refactor: reuse provider coercion helpers * refactor: reuse core coercion helpers * refactor: reuse runtime coercion helpers * refactor: reuse helper coercion in codex paths * refactor: reuse helper coercion in runtime paths * refactor: reuse codex app-server coercion helpers * refactor: reuse codex record helpers * refactor: reuse migration and qa record helpers * refactor: reuse feishu and core helper guards * refactor: reuse browser and policy coercion helpers * refactor: reuse memory wiki record helper * refactor: share boolean coercion helpers * refactor: reuse finite number coercion * refactor: reuse trimmed string list helpers * refactor: reuse string list normalization * refactor: reuse remaining string list helpers * refactor: reuse string entry normalizer * refactor: share sorted string helpers * refactor: share string list normalization * test: preserve command registry browser imports * refactor: reuse trimmed list helpers * refactor: reuse string dedupe helpers * refactor: reuse local dedupe helpers * refactor: reuse more string dedupe helpers * refactor: reuse command string dedupe helpers * refactor: dedupe memory path lists with helper * refactor: expose string dedupe helpers to plugins * refactor: reuse core string dedupe helpers * refactor: reuse shared unique value helpers * refactor: reuse unique helpers in agent utilities * refactor: reuse unique helpers in config plumbing * refactor: reuse unique helpers in extensions * refactor: reuse unique helpers in core utilities * refactor: reuse unique helpers in qa plugins * refactor: reuse unique helpers in memory plugins * refactor: reuse unique helpers in channel plugins * refactor: reuse unique helpers in core tails * refactor: reuse unique helper in comfy workflow * refactor: reuse unique helpers in test utilities * refactor: expose unique value helper to plugins * refactor: reuse unique helpers for numeric lists * refactor: replace index dedupe filters * refactor: reuse string entry normalization * refactor: reuse string normalization in plugin helpers * refactor: reuse string normalization in extension helpers * refactor: reuse string normalization in channel parsers * refactor: reuse string normalization in memory search * refactor: reuse string normalization in provider parsers * refactor: reuse string normalization in qa helpers * refactor: reuse string normalization in infra parsers * refactor: reuse string normalization in messaging parsers * refactor: reuse string normalization in core parsers * refactor: reuse string normalization in extension parsers * refactor: reuse string normalization in remaining parsers * refactor: reuse string normalization in final parser spots * refactor: reuse string normalization in qa media helpers * refactor: reuse normalization in provider and media lists * refactor: reuse normalization for remaining set filters * refactor: reuse normalization in policy allowlists * refactor: reuse normalization in session and owner lists * refactor: centralize primitive string lists * refactor: reuse lowercase entry helpers * refactor: reuse sorted string helpers * refactor: reuse unique trimmed helpers * refactor: reuse string normalization helpers * refactor: reuse catalog string helpers * refactor: reuse remaining string helpers * refactor: simplify remaining list normalization * refactor: reuse codex auth order normalization * chore: refresh plugin sdk api baseline * fix: make shared string sorting deterministic * chore: refresh plugin sdk api baseline * fix: align host env security ordering
281 lines
9.2 KiB
TypeScript
281 lines
9.2 KiB
TypeScript
import { normalizeProviderId } from "../agents/provider-id.js";
|
|
import type { OpenClawConfig } from "../config/types.js";
|
|
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
|
import { uniqueStrings } from "../shared/string-normalization.js";
|
|
import { normalizePluginsConfig } from "./config-state.js";
|
|
import { passesManifestOwnerBasePolicy } from "./manifest-owner-policy.js";
|
|
import type { PluginManifestRecord } from "./manifest-registry.js";
|
|
import type { PluginDiagnostic } from "./manifest-types.js";
|
|
import type { PluginManifestActivationCapability } from "./manifest.js";
|
|
import type { PluginOrigin } from "./plugin-origin.types.js";
|
|
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry-contributions.js";
|
|
import { createPluginIdScopeSet, normalizePluginIdScope } from "./plugin-scope.js";
|
|
|
|
export type PluginActivationPlannerTrigger =
|
|
| { kind: "command"; command: string }
|
|
| { kind: "provider"; provider: string }
|
|
| { kind: "agentHarness"; runtime: string }
|
|
| { kind: "channel"; channel: string }
|
|
| { kind: "route"; route: string }
|
|
| { kind: "capability"; capability: PluginManifestActivationCapability };
|
|
|
|
export type PluginActivationPlannerHintReason =
|
|
| "activation-agent-harness-hint"
|
|
| "activation-capability-hint"
|
|
| "activation-channel-hint"
|
|
| "activation-command-hint"
|
|
| "activation-provider-hint"
|
|
| "activation-route-hint";
|
|
|
|
export type PluginActivationPlannerManifestReason =
|
|
| "manifest-channel-owner"
|
|
| "manifest-command-alias"
|
|
| "manifest-hook-owner"
|
|
| "manifest-provider-owner"
|
|
| "manifest-setup-provider-owner"
|
|
| "manifest-tool-contract";
|
|
|
|
export type PluginActivationPlannerReason =
|
|
| PluginActivationPlannerHintReason
|
|
| PluginActivationPlannerManifestReason;
|
|
|
|
export type PluginActivationPlanEntry = {
|
|
pluginId: string;
|
|
origin: PluginOrigin;
|
|
reasons: readonly PluginActivationPlannerReason[];
|
|
};
|
|
|
|
export type PluginActivationPlan = {
|
|
trigger: PluginActivationPlannerTrigger;
|
|
pluginIds: readonly string[];
|
|
entries: readonly PluginActivationPlanEntry[];
|
|
diagnostics: readonly PluginDiagnostic[];
|
|
};
|
|
|
|
type ResolveManifestActivationPlanParams = {
|
|
trigger: PluginActivationPlannerTrigger;
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
origin?: PluginOrigin;
|
|
onlyPluginIds?: readonly string[];
|
|
manifestRecords?: readonly PluginManifestRecord[];
|
|
allowRestrictiveAllowlistBypass?: boolean;
|
|
};
|
|
|
|
export function resolveManifestActivationPlan(
|
|
params: ResolveManifestActivationPlanParams,
|
|
): PluginActivationPlan {
|
|
const onlyPluginIdSet = createPluginIdScopeSet(normalizePluginIdScope(params.onlyPluginIds));
|
|
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
|
const registry = params.manifestRecords
|
|
? { plugins: params.manifestRecords, diagnostics: [] }
|
|
: loadPluginManifestRegistryForPluginRegistry({
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
includeDisabled: true,
|
|
});
|
|
const entries = registry.plugins
|
|
.flatMap((plugin) => {
|
|
if (params.origin && plugin.origin !== params.origin) {
|
|
return [];
|
|
}
|
|
if (onlyPluginIdSet && !onlyPluginIdSet.has(plugin.id)) {
|
|
return [];
|
|
}
|
|
if (
|
|
!passesManifestOwnerBasePolicy({
|
|
plugin,
|
|
normalizedConfig,
|
|
allowRestrictiveAllowlistBypass: params.allowRestrictiveAllowlistBypass,
|
|
})
|
|
) {
|
|
return [];
|
|
}
|
|
const reasons = listManifestActivationTriggerReasons(plugin, params.trigger);
|
|
if (reasons.length === 0) {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
pluginId: plugin.id,
|
|
origin: plugin.origin,
|
|
reasons,
|
|
} satisfies PluginActivationPlanEntry,
|
|
];
|
|
})
|
|
.toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
|
|
return {
|
|
trigger: params.trigger,
|
|
pluginIds: uniqueStrings(entries.map((entry) => entry.pluginId)),
|
|
entries,
|
|
diagnostics: registry.diagnostics,
|
|
};
|
|
}
|
|
|
|
export function resolveManifestActivationPluginIds(
|
|
params: ResolveManifestActivationPlanParams,
|
|
): string[] {
|
|
return [...resolveManifestActivationPlan(params).pluginIds];
|
|
}
|
|
|
|
function listManifestActivationTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
trigger: PluginActivationPlannerTrigger,
|
|
): PluginActivationPlannerReason[] {
|
|
switch (trigger.kind) {
|
|
case "command":
|
|
return listCommandTriggerReasons(plugin, normalizeCommandId(trigger.command));
|
|
case "provider":
|
|
return listProviderTriggerReasons(plugin, normalizeProviderId(trigger.provider));
|
|
case "agentHarness":
|
|
return listAgentHarnessTriggerReasons(plugin, normalizeCommandId(trigger.runtime));
|
|
case "channel":
|
|
return listChannelTriggerReasons(plugin, normalizeCommandId(trigger.channel));
|
|
case "route":
|
|
return listRouteTriggerReasons(plugin, normalizeCommandId(trigger.route));
|
|
case "capability":
|
|
return listCapabilityTriggerReasons(plugin, trigger.capability);
|
|
}
|
|
const unreachableTrigger: never = trigger;
|
|
return unreachableTrigger;
|
|
}
|
|
|
|
function listAgentHarnessTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
runtime: string,
|
|
): PluginActivationPlannerReason[] {
|
|
return listHasNormalizedValue(plugin.activation?.onAgentHarnesses, runtime, normalizeCommandId)
|
|
? ["activation-agent-harness-hint"]
|
|
: [];
|
|
}
|
|
|
|
function listCommandTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
command: string,
|
|
): PluginActivationPlannerReason[] {
|
|
return dedupeReasons([
|
|
listHasNormalizedValue(plugin.activation?.onCommands, command, normalizeCommandId)
|
|
? "activation-command-hint"
|
|
: null,
|
|
listHasNormalizedValue(
|
|
(plugin.commandAliases ?? []).flatMap((alias) => alias.cliCommand ?? alias.name),
|
|
command,
|
|
normalizeCommandId,
|
|
)
|
|
? "manifest-command-alias"
|
|
: null,
|
|
]);
|
|
}
|
|
|
|
function listProviderTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
provider: string,
|
|
): PluginActivationPlannerReason[] {
|
|
return dedupeReasons([
|
|
listHasNormalizedValue(plugin.activation?.onProviders, provider, normalizeProviderId)
|
|
? "activation-provider-hint"
|
|
: null,
|
|
listHasNormalizedValue(plugin.providers, provider, normalizeProviderId)
|
|
? "manifest-provider-owner"
|
|
: null,
|
|
listHasNormalizedValue(
|
|
plugin.setup?.providers?.map((setupProvider) => setupProvider.id),
|
|
provider,
|
|
normalizeProviderId,
|
|
)
|
|
? "manifest-setup-provider-owner"
|
|
: null,
|
|
]);
|
|
}
|
|
|
|
function listChannelTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
channel: string,
|
|
): PluginActivationPlannerReason[] {
|
|
return dedupeReasons([
|
|
listHasNormalizedValue(plugin.activation?.onChannels, channel, normalizeCommandId)
|
|
? "activation-channel-hint"
|
|
: null,
|
|
listHasNormalizedValue(plugin.channels, channel, normalizeCommandId)
|
|
? "manifest-channel-owner"
|
|
: null,
|
|
]);
|
|
}
|
|
|
|
function listRouteTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
route: string,
|
|
): PluginActivationPlannerReason[] {
|
|
return listHasNormalizedValue(plugin.activation?.onRoutes, route, normalizeCommandId)
|
|
? ["activation-route-hint"]
|
|
: [];
|
|
}
|
|
|
|
function listCapabilityTriggerReasons(
|
|
plugin: PluginManifestRecord,
|
|
capability: PluginManifestActivationCapability,
|
|
): PluginActivationPlannerReason[] {
|
|
switch (capability) {
|
|
case "provider":
|
|
return dedupeReasons([
|
|
plugin.activation?.onCapabilities?.includes(capability)
|
|
? "activation-capability-hint"
|
|
: null,
|
|
hasValues(plugin.activation?.onProviders) ? "activation-provider-hint" : null,
|
|
hasValues(plugin.providers) ? "manifest-provider-owner" : null,
|
|
hasValues(plugin.setup?.providers) ? "manifest-setup-provider-owner" : null,
|
|
]);
|
|
case "channel":
|
|
return dedupeReasons([
|
|
plugin.activation?.onCapabilities?.includes(capability)
|
|
? "activation-capability-hint"
|
|
: null,
|
|
hasValues(plugin.activation?.onChannels) ? "activation-channel-hint" : null,
|
|
hasValues(plugin.channels) ? "manifest-channel-owner" : null,
|
|
]);
|
|
case "tool":
|
|
return dedupeReasons([
|
|
plugin.activation?.onCapabilities?.includes(capability)
|
|
? "activation-capability-hint"
|
|
: null,
|
|
hasValues(plugin.contracts?.tools) ? "manifest-tool-contract" : null,
|
|
]);
|
|
case "hook":
|
|
return dedupeReasons([
|
|
plugin.activation?.onCapabilities?.includes(capability)
|
|
? "activation-capability-hint"
|
|
: null,
|
|
hasValues(plugin.hooks) ? "manifest-hook-owner" : null,
|
|
]);
|
|
}
|
|
const unreachableCapability: never = capability;
|
|
return unreachableCapability;
|
|
}
|
|
|
|
function listHasNormalizedValue(
|
|
values: readonly string[] | undefined,
|
|
expected: string,
|
|
normalize: (value: string) => string,
|
|
): boolean {
|
|
return values?.some((value) => normalize(value) === expected) ?? false;
|
|
}
|
|
|
|
function hasValues(values: readonly unknown[] | undefined): boolean {
|
|
return (values?.length ?? 0) > 0;
|
|
}
|
|
|
|
function dedupeReasons(
|
|
reasons: readonly (PluginActivationPlannerReason | null)[],
|
|
): PluginActivationPlannerReason[] {
|
|
return [
|
|
...new Set(reasons.filter((reason): reason is PluginActivationPlannerReason => !!reason)),
|
|
];
|
|
}
|
|
|
|
function normalizeCommandId(value: string | undefined): string {
|
|
return normalizeOptionalLowercaseString(value) ?? "";
|
|
}
|