docs: document plugin scope state helpers

This commit is contained in:
Peter Steinberger
2026-06-03 20:30:38 -04:00
parent 0ad13b714e
commit 58f7d7e5f8
8 changed files with 35 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
/** Channel presence and gateway startup plugin id helpers. */
export {
hasConfiguredChannelsForReadOnlyScope,
hasExplicitChannelConfig,

View File

@@ -1,3 +1,4 @@
/** Runtime shape needed to expose an active plugin channel registration. */
export type ActiveChannelPluginRuntimeShape = {
id?: string | null;
meta?: {
@@ -16,12 +17,14 @@ export type ActiveChannelPluginRuntimeShape = {
} | null;
};
/** Active channel registration with owning plugin metadata. */
export type ActivePluginChannelRegistration = {
plugin: ActiveChannelPluginRuntimeShape;
pluginId?: string | null;
origin?: string | null;
};
/** Active runtime channel registry snapshot. */
export type ActivePluginChannelRegistry = {
channels: ActivePluginChannelRegistration[];
};

View File

@@ -7,10 +7,12 @@ import {
} from "../infra/install-safe-path.js";
import { resolveConfigDir, resolveUserPath } from "../utils.js";
/** Encodes arbitrary input as a safe plugin install filename. */
export function safePluginInstallFileName(input: string): string {
return safeDirName(input);
}
/** Encodes a plugin id for use as an install directory name. */
export function encodePluginInstallDirName(pluginId: string): string {
const trimmed = pluginId.trim();
if (!trimmed.includes("/")) {
@@ -21,6 +23,7 @@ export function encodePluginInstallDirName(pluginId: string): string {
return `@${safePathSegmentHashed(trimmed)}`;
}
/** Validates a plugin id for install path safety. */
export function validatePluginId(pluginId: string): string | null {
const trimmed = pluginId.trim();
if (!trimmed) {
@@ -51,6 +54,7 @@ export function validatePluginId(pluginId: string): string | null {
return null;
}
/** Checks whether an installed plugin id matches the expected id, including old npm keying. */
export function matchesExpectedPluginId(params: {
expectedPluginId?: string;
pluginId: string;
@@ -73,6 +77,7 @@ export function matchesExpectedPluginId(params: {
);
}
/** Resolves the default directory for path-installed plugin extensions. */
export function resolveDefaultPluginExtensionsDir(
env: NodeJS.ProcessEnv = process.env,
homedir?: () => string,
@@ -80,6 +85,7 @@ export function resolveDefaultPluginExtensionsDir(
return path.join(resolveConfigDir(env, homedir), "extensions");
}
/** Resolves the default directory for managed npm plugin installs. */
export function resolveDefaultPluginNpmDir(
env: NodeJS.ProcessEnv = process.env,
homedir?: () => string,
@@ -87,6 +93,7 @@ export function resolveDefaultPluginNpmDir(
return path.join(resolveConfigDir(env, homedir), "npm");
}
/** Encodes an npm package name into a managed npm project directory name. */
export function encodePluginNpmProjectDirName(packageName: string): string {
const trimmed = packageName.trim();
if (!trimmed) {
@@ -95,11 +102,13 @@ export function encodePluginNpmProjectDirName(packageName: string): string {
return safePathSegmentHashed(trimmed);
}
/** Resolves the directory containing managed npm plugin projects. */
export function resolvePluginNpmProjectsDir(npmDir?: string): string {
const npmBase = npmDir ? resolveUserPath(npmDir) : resolveDefaultPluginNpmDir();
return path.join(npmBase, "projects");
}
/** Resolves the managed npm project directory for a package name. */
export function resolvePluginNpmProjectDir(params: {
packageName: string;
npmDir?: string;
@@ -110,6 +119,7 @@ export function resolvePluginNpmProjectDir(params: {
);
}
/** Resolves the installed node_modules package directory for a managed npm plugin. */
export function resolvePluginNpmPackageDir(params: {
packageName: string;
npmDir?: string;
@@ -121,6 +131,7 @@ export function resolvePluginNpmPackageDir(params: {
);
}
/** Resolves the default directory for git-installed plugins. */
export function resolveDefaultPluginGitDir(
env: NodeJS.ProcessEnv = process.env,
homedir?: () => string,
@@ -128,6 +139,7 @@ export function resolveDefaultPluginGitDir(
return path.join(resolveConfigDir(env, homedir), "git");
}
/** Resolves the safe install directory for one plugin id. */
export function resolvePluginInstallDir(pluginId: string, extensionsDir?: string): string {
const extensionsBase = extensionsDir
? resolveUserPath(extensionsDir)

View File

@@ -2,6 +2,7 @@ import { createDedupeCache, resolveGlobalDedupeCache } from "../infra/dedupe.js"
import type { DedupeCache } from "../infra/dedupe.js";
import type { PluginInteractiveHandlerRegistration } from "./types.js";
/** Registered interactive handler with owning plugin metadata. */
export type RegisteredInteractiveHandler = PluginInteractiveHandlerRegistration & {
pluginId: string;
pluginName?: string;
@@ -67,6 +68,7 @@ function getState() {
return created;
}
/** Returns the process-global plugin interactive handler registry. */
export function getPluginInteractiveHandlersState() {
return getState().interactiveHandlers;
}
@@ -75,6 +77,7 @@ function getPluginInteractiveCallbackDedupeState() {
return getState().callbackDedupe;
}
/** Claims an interactive callback dedupe key while the callback is in flight. */
export function claimPluginInteractiveCallbackDedupe(
dedupeKey: string | undefined,
now = Date.now(),
@@ -90,6 +93,7 @@ export function claimPluginInteractiveCallbackDedupe(
return true;
}
/** Commits an interactive callback dedupe key after successful handling. */
export function commitPluginInteractiveCallbackDedupe(
dedupeKey: string | undefined,
now = Date.now(),
@@ -102,6 +106,7 @@ export function commitPluginInteractiveCallbackDedupe(
state.callbackDedupe.check(dedupeKey, now);
}
/** Releases an in-flight interactive callback dedupe claim without committing it. */
export function releasePluginInteractiveCallbackDedupe(dedupeKey: string | undefined): void {
if (!dedupeKey) {
return;
@@ -109,12 +114,14 @@ export function releasePluginInteractiveCallbackDedupe(dedupeKey: string | undef
getState().inflightCallbackDedupe.delete(dedupeKey);
}
/** Clears plugin interactive handlers and callback dedupe state. */
export function clearPluginInteractiveHandlersState(): void {
clearPluginInteractiveHandlerRegistrationsState();
getPluginInteractiveCallbackDedupeState().clear();
getState().inflightCallbackDedupe.clear();
}
/** Clears only plugin interactive handler registrations. */
export function clearPluginInteractiveHandlerRegistrationsState(): void {
getPluginInteractiveHandlersState().clear();
}

View File

@@ -1 +1,2 @@
/** Plugin kind labels for non-provider plugin capability groups. */
export type PluginKind = "memory" | "context-engine";

View File

@@ -1,7 +1,9 @@
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
/** Optional scoped plugin id list; undefined means unscoped. */
export type PluginIdScope = readonly string[] | undefined;
/** Normalizes plugin id scope input into a sorted unique string list. */
export function normalizePluginIdScope(ids?: readonly unknown[]): string[] | undefined {
if (ids === undefined) {
return undefined;
@@ -11,14 +13,17 @@ export function normalizePluginIdScope(ids?: readonly unknown[]): string[] | und
).toSorted();
}
/** True when plugin scope was explicitly provided, including an empty scope. */
export function hasExplicitPluginIdScope(ids?: readonly string[]): boolean {
return ids !== undefined;
}
/** True when plugin scope was explicitly provided with at least one id. */
export function hasNonEmptyPluginIdScope(ids?: readonly string[]): boolean {
return ids !== undefined && ids.length > 0;
}
/** Creates a lookup set for explicit plugin scope, or null when unscoped. */
export function createPluginIdScopeSet(ids?: readonly string[]): ReadonlySet<string> | null {
if (ids === undefined) {
return null;
@@ -26,6 +31,7 @@ export function createPluginIdScopeSet(ids?: readonly string[]): ReadonlySet<str
return new Set(ids);
}
/** Serializes plugin scope for cache keys. */
export function serializePluginIdScope(ids?: readonly string[]): string {
return ids === undefined ? "__unscoped__" : JSON.stringify(ids);
}

View File

@@ -6,6 +6,7 @@ type SetupDescriptorRecord = Pick<
"providers" | "cliBackends" | "providerAuthAliases" | "setup"
>;
/** Lists setup provider ids and auth aliases owned by one plugin manifest. */
export function listSetupProviderIds(record: SetupDescriptorRecord): readonly string[] {
const providerIds = record.setup?.providers?.map((entry) => entry.id) ?? record.providers;
const normalizedProviderIds = new Set(providerIds.map(normalizeProviderId));
@@ -15,6 +16,7 @@ export function listSetupProviderIds(record: SetupDescriptorRecord): readonly st
return [...providerIds, ...aliases];
}
/** Lists setup CLI backend ids from setup metadata or manifest contribution ids. */
export function listSetupCliBackendIds(record: SetupDescriptorRecord): readonly string[] {
return record.setup?.cliBackends ?? record.cliBackends;
}

View File

@@ -4,6 +4,7 @@ import { resolveUserPath } from "../utils.js";
import { normalizeBundledLookupPath } from "./bundled-load-path-aliases.js";
import { resolveBundledPluginSources, type BundledPluginSource } from "./bundled-sources.js";
/** Stale install record that points at old compiled bundled plugin output. */
export type StaleLocalBundledPluginInstallRecord = {
pluginId: string;
record: PluginInstallRecord;
@@ -49,6 +50,7 @@ function hasStaleBundledVersion(
return Boolean(recordVersion && bundledVersion && recordVersion !== bundledVersion);
}
/** Lists path install records that still point at stale compiled bundled plugin output. */
export function listStaleLocalBundledPluginInstallRecords(params: {
installRecords: Record<string, PluginInstallRecord>;
workspaceDir?: string;
@@ -100,6 +102,7 @@ export function listStaleLocalBundledPluginInstallRecords(params: {
return stale;
}
/** Removes stale compiled bundled plugin path records from an install record map. */
export function pruneStaleLocalBundledPluginInstallRecords(params: {
installRecords: Record<string, PluginInstallRecord>;
workspaceDir?: string;