diff --git a/packages/acp-core/src/index.ts b/packages/acp-core/src/index.ts index 98a73b8b6cd0..6ee833caecbe 100644 --- a/packages/acp-core/src/index.ts +++ b/packages/acp-core/src/index.ts @@ -1,3 +1,5 @@ +// Public barrel for shared ACP session, metadata, and runtime helper contracts. + export * from "./error-format.js"; export * from "./meta.js"; export * from "./normalize-text.js"; diff --git a/packages/acp-core/src/normalize-text.ts b/packages/acp-core/src/normalize-text.ts index e74e66769f98..0725ac92bbed 100644 --- a/packages/acp-core/src/normalize-text.ts +++ b/packages/acp-core/src/normalize-text.ts @@ -1 +1,3 @@ +// ACP text normalization facade shared with older imports. + export { normalizeOptionalString as normalizeText } from "@openclaw/normalization-core/string-coerce"; diff --git a/packages/acp-core/src/record-shared.ts b/packages/acp-core/src/record-shared.ts index 981b6fbfb620..0a9cb347425d 100644 --- a/packages/acp-core/src/record-shared.ts +++ b/packages/acp-core/src/record-shared.ts @@ -1 +1,3 @@ +// ACP record normalization facade shared with older imports. + export { asOptionalRecord as asRecord } from "@openclaw/normalization-core/record-coerce"; diff --git a/packages/acp-core/src/runtime/session-identity.ts b/packages/acp-core/src/runtime/session-identity.ts index 1f8bb4f67f02..f6b00bbae747 100644 --- a/packages/acp-core/src/runtime/session-identity.ts +++ b/packages/acp-core/src/runtime/session-identity.ts @@ -2,6 +2,9 @@ import { normalizeText } from "../normalize-text.js"; import type { SessionAcpIdentity, SessionAcpIdentitySource, SessionAcpMeta } from "../types.js"; import type { AcpRuntimeHandle, AcpRuntimeStatus } from "./types.js"; +// ACP session identity merge and extraction helpers for resume-safe runtime state. + +/** Normalize a stored identity state value from metadata. */ function normalizeIdentityState(value: unknown): SessionAcpIdentity["state"] | undefined { if (value !== "pending" && value !== "resolved") { return undefined; @@ -9,6 +12,7 @@ function normalizeIdentityState(value: unknown): SessionAcpIdentity["state"] | u return value; } +/** Normalize where an ACP identity observation came from. */ function normalizeIdentitySource(value: unknown): SessionAcpIdentitySource | undefined { if (value !== "ensure" && value !== "status" && value !== "event") { return undefined; @@ -16,6 +20,7 @@ function normalizeIdentitySource(value: unknown): SessionAcpIdentitySource | und return value; } +/** Normalize an identity object and infer pending/resolved state from stable ids. */ function normalizeIdentity( identity: SessionAcpIdentity | undefined, ): SessionAcpIdentity | undefined { @@ -49,6 +54,7 @@ function normalizeIdentity( type IdentityIds = Pick; +/** Read identity ids from a runtime handle shape. */ function readIdentityIdsFromHandle(handle: AcpRuntimeHandle): IdentityIds { return { acpxRecordId: normalizeText((handle as { acpxRecordId?: unknown }).acpxRecordId), @@ -57,6 +63,7 @@ function readIdentityIdsFromHandle(handle: AcpRuntimeHandle): IdentityIds { }; } +/** Build an identity only when at least one stable id is known. */ function buildSessionIdentity(params: { ids: IdentityIds; state: SessionAcpIdentity["state"]; @@ -77,6 +84,7 @@ function buildSessionIdentity(params: { }; } +/** Resolve normalized ACP identity from persisted session metadata. */ export function resolveSessionIdentityFromMeta( meta: SessionAcpMeta | undefined, ): SessionAcpIdentity | undefined { @@ -86,10 +94,12 @@ export function resolveSessionIdentityFromMeta( return normalizeIdentity(meta.identity); } +/** Return true when an identity has a backend or agent session id. */ export function identityHasStableSessionId(identity: SessionAcpIdentity | undefined): boolean { return Boolean(identity?.acpxSessionId || identity?.agentSessionId); } +/** Resolve the runtime resume id, preferring agent session id over ACP backend id. */ export function resolveRuntimeResumeSessionId( identity: SessionAcpIdentity | undefined, ): string | undefined { @@ -99,6 +109,7 @@ export function resolveRuntimeResumeSessionId( return normalizeText(identity.agentSessionId) ?? normalizeText(identity.acpxSessionId); } +/** Return true when identity is absent or still pending. */ export function isSessionIdentityPending(identity: SessionAcpIdentity | undefined): boolean { if (!identity) { return true; @@ -106,6 +117,7 @@ export function isSessionIdentityPending(identity: SessionAcpIdentity | undefine return identity.state === "pending"; } +/** Compare identities ignoring lastUpdatedAt timestamp churn. */ export function identityEquals( left: SessionAcpIdentity | undefined, right: SessionAcpIdentity | undefined, @@ -127,6 +139,7 @@ export function identityEquals( ); } +/** Merge current and incoming identity observations without downgrading resolved ids. */ export function mergeSessionIdentity(params: { current: SessionAcpIdentity | undefined; incoming: SessionAcpIdentity | undefined; @@ -174,6 +187,7 @@ export function mergeSessionIdentity(params: { return next; } +/** Create a pending identity from an ensure-session handle. */ export function createIdentityFromEnsure(params: { handle: AcpRuntimeHandle; now: number; @@ -186,6 +200,7 @@ export function createIdentityFromEnsure(params: { }); } +/** Create an identity from a runtime event handle. */ export function createIdentityFromHandleEvent(params: { handle: AcpRuntimeHandle; now: number; @@ -199,6 +214,7 @@ export function createIdentityFromHandleEvent(params: { }); } +/** Create an identity from runtime status output. */ export function createIdentityFromStatus(params: { status: AcpRuntimeStatus | undefined; now: number; @@ -230,6 +246,7 @@ export function createIdentityFromStatus(params: { }; } +/** Convert ACP identity ids into runtime handle resume identifiers. */ export function resolveRuntimeHandleIdentifiersFromIdentity( identity: SessionAcpIdentity | undefined, ): { backendSessionId?: string; agentSessionId?: string } { diff --git a/packages/media-core/src/index.ts b/packages/media-core/src/index.ts index b1c134644697..8c0ef4b106db 100644 --- a/packages/media-core/src/index.ts +++ b/packages/media-core/src/index.ts @@ -1,3 +1,5 @@ +// Public barrel for media URL, MIME, path, and bounded-read helpers. + export * from "./base64.js"; export * from "./constants.js"; export * from "./content-length.js"; diff --git a/packages/model-catalog-core/src/configured-model-refs.ts b/packages/model-catalog-core/src/configured-model-refs.ts index 8e05d3cb70af..6b799ab8f32c 100644 --- a/packages/model-catalog-core/src/configured-model-refs.ts +++ b/packages/model-catalog-core/src/configured-model-refs.ts @@ -1,14 +1,19 @@ import { normalizeProviderId } from "./provider-id.js"; +// Collects configured model references from OpenClaw config-shaped objects. + +/** Narrow unknown values to plain records. */ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } +/** One configured model reference plus its config path. */ export type ConfiguredModelRef = { path: string; value: string; }; +/** Agent config keys that can contain direct model references. */ export const AGENT_MODEL_CONFIG_KEYS = [ "model", "imageModel", @@ -19,6 +24,7 @@ export const AGENT_MODEL_CONFIG_KEYS = [ "pdfModel", ] as const; +/** Collect configured model references from agents, channels, hooks, and message config. */ export function collectConfiguredModelRefs( config: unknown, options: { includeChannelModelOverrides?: boolean } = {}, @@ -117,6 +123,7 @@ export function collectConfiguredModelRefs( return refs; } +/** Collect only configured model reference values. */ export function collectConfiguredModelRefValues( config: unknown, options?: { includeChannelModelOverrides?: boolean }, @@ -124,6 +131,7 @@ export function collectConfiguredModelRefValues( return collectConfiguredModelRefs(config, options).map((ref) => ref.value); } +/** Extract a normalized provider id from a provider/model reference. */ export function extractProviderFromModelRef(value: string): string | null { const trimmed = value.trim(); const slash = trimmed.indexOf("/"); diff --git a/packages/model-catalog-core/src/index.ts b/packages/model-catalog-core/src/index.ts index 4208e2c0060d..5e29a1af7432 100644 --- a/packages/model-catalog-core/src/index.ts +++ b/packages/model-catalog-core/src/index.ts @@ -1,3 +1,5 @@ +// Public barrel for model catalog normalization, ids, refs, and types. + export * from "./configured-model-refs.js"; export * from "./model-catalog-normalize.js"; export * from "./model-catalog-refs.js"; diff --git a/packages/model-catalog-core/src/model-catalog-normalize.ts b/packages/model-catalog-core/src/model-catalog-normalize.ts index 781f89ab3b53..4524893aced7 100644 --- a/packages/model-catalog-core/src/model-catalog-normalize.ts +++ b/packages/model-catalog-core/src/model-catalog-normalize.ts @@ -26,6 +26,8 @@ import { type NormalizedModelCatalogRow, } from "./model-catalog-types.js"; +// Normalizes raw provider model catalogs into stable rows for lookup and merging. + const MODEL_CATALOG_INPUTS = new Set(["text", "image", "document"]); const MODEL_CATALOG_DISCOVERY_MODES = new Set(["static", "refreshable", "runtime"]); const MODEL_CATALOG_STATUSES = new Set(["available", "preview", "deprecated", "disabled"]); @@ -33,14 +35,17 @@ const MODEL_CATALOG_API_SET = new Set(MODEL_CATALOG_APIS); const DEFAULT_MODEL_INPUT: ModelCatalogInput[] = ["text"]; const DEFAULT_MODEL_STATUS: ModelCatalogStatus = "available"; +/** Narrow unknown catalog payloads to plain records. */ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } +/** Reject object keys that can mutate prototypes when copied into records. */ function isBlockedObjectKey(key: string): boolean { return key === "__proto__" || key === "prototype" || key === "constructor"; } +/** Normalize optional catalog strings. */ function normalizeOptionalString(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; @@ -49,6 +54,7 @@ function normalizeOptionalString(value: unknown): string | undefined { return trimmed ? trimmed : undefined; } +/** Normalize arrays of trimmed strings, dropping invalid entries. */ function normalizeTrimmedStringList(value: unknown): string[] { if (!Array.isArray(value)) { return []; @@ -638,6 +644,7 @@ function normalizeModelCatalogDiscovery( return Object.keys(discovery).length > 0 ? discovery : undefined; } +/** Normalize a raw model catalog object for the set of providers owned by a plugin/manifest. */ export function normalizeModelCatalog( value: unknown, params: { ownedProviders: ReadonlySet }, @@ -661,6 +668,7 @@ export function normalizeModelCatalog( return Object.keys(catalog).length > 0 ? catalog : undefined; } +/** Normalize one provider catalog into sorted runtime rows. */ export function normalizeModelCatalogProviderRows(params: { provider: string; providerCatalog: ModelCatalogProvider; @@ -722,6 +730,7 @@ export function normalizeModelCatalogProviderRows(params: { return rows.toSorted((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id)); } +/** Normalize all provider catalogs into sorted runtime rows. */ export function normalizeModelCatalogRows(params: { providers: Record; source: ModelCatalogSource; diff --git a/packages/model-catalog-core/src/model-catalog-refs.ts b/packages/model-catalog-core/src/model-catalog-refs.ts index 9845ab8b540e..0cc2b8f62e2c 100644 --- a/packages/model-catalog-core/src/model-catalog-refs.ts +++ b/packages/model-catalog-core/src/model-catalog-refs.ts @@ -1,13 +1,18 @@ import { normalizeLowercaseStringOrEmpty } from "./provider-id.js"; +// Stable model catalog ref and merge-key builders. + +/** Normalize provider ids for catalog refs. */ export function normalizeModelCatalogProviderId(provider: string): string { return normalizeLowercaseStringOrEmpty(provider); } +/** Build a provider/model catalog reference. */ export function buildModelCatalogRef(provider: string, modelId: string): string { return `${normalizeModelCatalogProviderId(provider)}/${modelId}`; } +/** Build a case-insensitive merge key for provider/model rows. */ export function buildModelCatalogMergeKey(provider: string, modelId: string): string { return `${normalizeModelCatalogProviderId(provider)}::${normalizeLowercaseStringOrEmpty(modelId)}`; } diff --git a/packages/model-catalog-core/src/model-catalog-types.ts b/packages/model-catalog-core/src/model-catalog-types.ts index b56a81b8425f..89b8e802ae05 100644 --- a/packages/model-catalog-core/src/model-catalog-types.ts +++ b/packages/model-catalog-core/src/model-catalog-types.ts @@ -1,3 +1,6 @@ +// Shared model catalog data contracts for provider manifests and normalized rows. + +/** Supported API protocols for model catalog entries. */ export const MODEL_CATALOG_APIS = [ "openai-completions", "openai-responses", @@ -11,8 +14,10 @@ export const MODEL_CATALOG_APIS = [ "azure-openai-responses", ] as const; +/** API protocol for a model catalog entry. */ export type ModelCatalogApi = (typeof MODEL_CATALOG_APIS)[number]; +/** Supported model thinking/reasoning wire formats. */ export const MODEL_CATALOG_THINKING_FORMATS = [ "openai", "openrouter", @@ -23,12 +28,15 @@ export const MODEL_CATALOG_THINKING_FORMATS = [ "zai", ] as const; +/** Thinking/reasoning wire format for model compatibility. */ export type ModelCatalogThinkingFormat = (typeof MODEL_CATALOG_THINKING_FORMATS)[number]; +/** Narrow a string to a supported model catalog thinking format. */ export function isModelCatalogThinkingFormat(value: string): value is ModelCatalogThinkingFormat { return (MODEL_CATALOG_THINKING_FORMATS as readonly string[]).includes(value); } +/** Compatibility flags and provider-specific routing metadata for one model. */ export type ModelCatalogCompatConfig = { supportsStore?: boolean; supportsDeveloperRole?: boolean; @@ -63,6 +71,7 @@ export type ModelCatalogCompatConfig = { visibleReasoningDetailTypes?: string[]; }; +/** OpenRouter routing preferences copied into request metadata. */ export type ModelCatalogOpenRouterRouting = { allow_fallbacks?: boolean; require_parameters?: boolean; @@ -104,11 +113,13 @@ export type ModelCatalogOpenRouterRouting = { }; }; +/** Vercel AI Gateway routing preferences. */ export type ModelCatalogVercelGatewayRouting = { only?: string[]; order?: string[]; }; +/** Image input limits for a model. */ export type ModelCatalogImageInputConfig = { maxBytes?: number; maxPixels?: number; @@ -117,13 +128,18 @@ export type ModelCatalogImageInputConfig = { tokenMode?: "tile" | "detail" | "provider"; }; +/** Media input limits for a model. */ export type ModelCatalogMediaInputConfig = { image?: ModelCatalogImageInputConfig; }; +/** Supported input modality for a model. */ export type ModelCatalogInput = "text" | "image" | "document"; +/** Discovery lifecycle for a provider catalog. */ export type ModelCatalogDiscovery = "static" | "refreshable" | "runtime"; +/** Availability state for a model. */ export type ModelCatalogStatus = "available" | "preview" | "deprecated" | "disabled"; +/** Source of a model catalog row. */ export type ModelCatalogSource = | "manifest" | "provider-index" @@ -131,6 +147,7 @@ export type ModelCatalogSource = | "config" | "runtime-refresh"; +/** Unified catalog kind across text and generated media models. */ export type UnifiedModelCatalogKind = | "text" | "voice" @@ -138,6 +155,7 @@ export type UnifiedModelCatalogKind = | "video_generation" | "music_generation"; +/** Source for unified model catalog entries. */ export type UnifiedModelCatalogSource = | "manifest" | "provider-index" @@ -147,6 +165,7 @@ export type UnifiedModelCatalogSource = | "configured" | "runtime-refresh"; +/** Unified model catalog entry for provider/model pickers. */ export type UnifiedModelCatalogEntry = { kind: UnifiedModelCatalogKind; provider: string; @@ -164,6 +183,7 @@ export type UnifiedModelCatalogEntry = { warnings?: readonly string[]; }; +/** Tiered token cost row. */ export type ModelCatalogTieredCost = { input: number; output: number; @@ -172,6 +192,7 @@ export type ModelCatalogTieredCost = { range: [number, number] | [number]; }; +/** Token cost metadata for one model. */ export type ModelCatalogCost = { input?: number; output?: number; @@ -180,6 +201,7 @@ export type ModelCatalogCost = { tieredPricing?: ModelCatalogTieredCost[]; }; +/** Provider manifest model entry. */ export type ModelCatalogModel = { id: string; name?: string; @@ -201,6 +223,7 @@ export type ModelCatalogModel = { tags?: string[]; }; +/** Provider manifest catalog entry. */ export type ModelCatalogProvider = { baseUrl?: string; api?: ModelCatalogApi; @@ -208,12 +231,14 @@ export type ModelCatalogProvider = { models: ModelCatalogModel[]; }; +/** Provider alias entry. */ export type ModelCatalogAlias = { provider: string; api?: ModelCatalogApi; baseUrl?: string; }; +/** Suppression rule for hiding a provider/model under matching config. */ export type ModelCatalogSuppression = { provider: string; model: string; @@ -224,6 +249,7 @@ export type ModelCatalogSuppression = { }; }; +/** Raw model catalog manifest shape. */ export type ModelCatalog = { providers?: Record; aliases?: Record; @@ -232,6 +258,7 @@ export type ModelCatalog = { runtimeAugment?: boolean; }; +/** Normalized model catalog row used by runtime lookup and UI surfaces. */ export type NormalizedModelCatalogRow = { provider: string; id: string; diff --git a/packages/model-catalog-core/src/provider-model-id-normalization.ts b/packages/model-catalog-core/src/provider-model-id-normalization.ts index 8a66b92ed1af..0e45b8c4fb6e 100644 --- a/packages/model-catalog-core/src/provider-model-id-normalization.ts +++ b/packages/model-catalog-core/src/provider-model-id-normalization.ts @@ -4,6 +4,9 @@ import { normalizeTogetherModelId, } from "./provider-model-id-normalize.js"; +// Provider model-id normalization policies from manifests plus built-in provider rules. + +/** Manifest-defined normalization rules for one provider. */ export type ManifestModelIdNormalizationProvider = { aliases?: Record; stripPrefixes?: string[]; @@ -14,6 +17,7 @@ export type ManifestModelIdNormalizationProvider = { }[]; }; +/** Manifest fragment that can define provider model-id normalization policies. */ export type ManifestModelIdNormalizationRecord = { modelIdNormalization?: { providers?: Record; @@ -24,6 +28,7 @@ let currentManifestModelIdNormalizationPolicies: | ReadonlyMap | undefined; +/** Collect provider model-id normalization policies from plugin manifests. */ export function collectManifestModelIdNormalizationPolicies( plugins: readonly ManifestModelIdNormalizationRecord[], ): Map { @@ -36,6 +41,7 @@ export function collectManifestModelIdNormalizationPolicies( return policies; } +/** Replace the process-local manifest normalization policy snapshot. */ export function setCurrentManifestModelIdNormalizationRecords( plugins: readonly ManifestModelIdNormalizationRecord[] | undefined, ): void { @@ -44,20 +50,24 @@ export function setCurrentManifestModelIdNormalizationRecords( : undefined; } +/** Return the current process-local manifest normalization policy snapshot. */ export function getCurrentManifestModelIdNormalizationPolicies(): | ReadonlyMap | undefined { return currentManifestModelIdNormalizationPolicies; } +/** Return true when a model id already includes a provider namespace. */ function hasProviderPrefix(modelId: string): boolean { return modelId.includes("/"); } +/** Join a provider prefix and model id with exactly one slash. */ function formatPrefixedModelId(prefix: string, modelId: string): string { return `${prefix.replace(/\/+$/u, "")}/${modelId.replace(/^\/+/u, "")}`; } +/** Strip a duplicated self-provider prefix from a model id. */ export function stripSelfProviderModelPrefix(provider: string, model: string): string { const prefix = `${normalizeLowercaseStringOrEmpty(provider)}/`; const trimmed = model.trim(); @@ -66,6 +76,7 @@ export function stripSelfProviderModelPrefix(provider: string, model: string): s : model; } +/** Apply manifest normalization policies for one provider/model id. */ export function normalizeProviderModelIdWithPolicies(params: { provider: string; policies: ReadonlyMap; @@ -107,6 +118,7 @@ export function normalizeProviderModelIdWithPolicies(params: { return modelId; } +/** Apply built-in provider-specific model id normalization rules. */ export function normalizeBuiltInProviderModelId(provider: string, model: string): string { const normalizedProvider = normalizeLowercaseStringOrEmpty(provider); if ( @@ -174,6 +186,7 @@ export function normalizeBuiltInProviderModelId(provider: string, model: string) return model; } +/** Apply manifest policies and built-in normalization to a static provider/model id. */ export function normalizeStaticProviderModelIdWithPolicies( provider: string, model: string, @@ -192,6 +205,7 @@ export function normalizeStaticProviderModelIdWithPolicies( return normalizeBuiltInProviderModelId(normalizedProvider, manifestModelId); } +/** Normalize a configured provider/model catalog reference using current policies. */ export function normalizeConfiguredProviderCatalogModelId( provider: string, model: string, @@ -201,6 +215,7 @@ export function normalizeConfiguredProviderCatalogModelId( return normalizeConfiguredProviderCatalogModelRef(providerModel); } +/** Normalize embedded Google model aliases inside provider/model catalog refs. */ export function normalizeConfiguredProviderCatalogModelRef(providerModel: string): string { const googlePrefix = "google/"; if (!providerModel.startsWith(googlePrefix)) { diff --git a/packages/normalization-core/src/index.ts b/packages/normalization-core/src/index.ts index dfae88b2012a..b45dfcc77fc4 100644 --- a/packages/normalization-core/src/index.ts +++ b/packages/normalization-core/src/index.ts @@ -1,3 +1,5 @@ +// Public barrel for shared coercion and normalization helpers. + export * from "./number-coercion.js"; export * from "./record-coerce.js"; export * from "./string-coerce.js"; diff --git a/packages/plugin-package-contract/src/index.ts b/packages/plugin-package-contract/src/index.ts index 05ba87e5ea60..0dec6966bc3e 100644 --- a/packages/plugin-package-contract/src/index.ts +++ b/packages/plugin-package-contract/src/index.ts @@ -1,5 +1,9 @@ +// External code plugin package.json compatibility and validation contracts. + +/** JSON object shape accepted by package contract helpers. */ export type JsonObject = Record; +/** Compatibility metadata extracted from an external plugin package. */ export type ExternalPluginCompatibility = { pluginApiRange?: string; builtWithOpenClawVersion?: string; @@ -7,25 +11,30 @@ export type ExternalPluginCompatibility = { minGatewayVersion?: string; }; +/** One validation issue for an external plugin package. */ export type ExternalPluginValidationIssue = { fieldPath: string; message: string; }; +/** Validation result plus any normalized compatibility metadata. */ export type ExternalCodePluginValidationResult = { compatibility?: ExternalPluginCompatibility; issues: ExternalPluginValidationIssue[]; }; +/** Required package.json field paths for external code plugin packages. */ export const EXTERNAL_CODE_PLUGIN_REQUIRED_FIELD_PATHS = [ "openclaw.compat.pluginApi", "openclaw.build.openclawVersion", ] as const; +/** Narrow unknown values to plain records. */ function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } +/** Normalize optional package metadata strings. */ function normalizeOptionalString(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; @@ -34,6 +43,7 @@ function normalizeOptionalString(value: unknown): string | undefined { return trimmed ? trimmed : undefined; } +/** Read OpenClaw package.json blocks without trusting caller input shape. */ function readOpenClawBlock(packageJson: unknown) { const root = isRecord(packageJson) ? packageJson : undefined; const openclaw = isRecord(root?.openclaw) ? root.openclaw : undefined; @@ -43,6 +53,7 @@ function readOpenClawBlock(packageJson: unknown) { return { root, openclaw, compat, build, install }; } +/** Normalize compatibility metadata from an external plugin package.json. */ export function normalizeExternalPluginCompatibility( packageJson: unknown, ): ExternalPluginCompatibility | undefined { @@ -74,6 +85,7 @@ export function normalizeExternalPluginCompatibility( return Object.keys(compatibility).length > 0 ? compatibility : undefined; } +/** List missing required field paths for an external code plugin package.json. */ export function listMissingExternalCodePluginFieldPaths(packageJson: unknown): string[] { const { compat, build } = readOpenClawBlock(packageJson); const missing: string[] = []; @@ -86,6 +98,7 @@ export function listMissingExternalCodePluginFieldPaths(packageJson: unknown): s return missing; } +/** Validate an external code plugin package.json against required compatibility fields. */ export function validateExternalCodePluginPackageJson( packageJson: unknown, ): ExternalCodePluginValidationResult {