diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 5f09ae36e3d..29f1ed94d0b 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -3b9a8841973205560a5396e7a18d301852941a95a561900984ad618e69a99d05 config-baseline.json -089ab9493c8482687f19da89d37e069fc402543696c92e6e3be86072c1e48c68 config-baseline.core.json +f5236ba3f34837485d1e319262d4d73ecd46ea8890d3f4c26a069834f376b796 config-baseline.json +484b36513ecb4a13cc945c3916fbe5ac712b5e0ab2c4ffa2dc811758da4ec7a6 config-baseline.core.json 7cd9c908f066c143eab2a201efbc9640f483ab28bba92ddeca1d18cc2b528bc3 config-baseline.channel.json 17eb3f8887193579ff32e35f9bd520ba2bd6049e52ab18855c5d41fcbf195d83 config-baseline.plugin.json diff --git a/src/commands/doctor-plugin-registry.test.ts b/src/commands/doctor-plugin-registry.test.ts index 8a96ea45fae..43a801330c3 100644 --- a/src/commands/doctor-plugin-registry.test.ts +++ b/src/commands/doctor-plugin-registry.test.ts @@ -2,7 +2,6 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { PluginCandidate } from "../plugins/discovery.js"; -import { readPersistedPluginInstallLedger } from "../plugins/install-ledger-store.js"; import { readPersistedInstalledPluginIndex, writePersistedInstalledPluginIndex, @@ -68,7 +67,7 @@ function createCurrentIndex(): InstalledPluginIndex { version: 1, hostContractVersion: "2026.4.25", compatRegistryVersion: "compat-v1", - migrationVersion: 2, + migrationVersion: 1, policyHash: "policy-v1", generatedAtMs: 1777118400000, plugins: [], @@ -77,30 +76,7 @@ function createCurrentIndex(): InstalledPluginIndex { } describe("maybeRepairPluginRegistryState", () => { - it("reports legacy config install records without mutating state outside repair mode", async () => { - const stateDir = makeTempDir(); - const nextConfig = await maybeRepairPluginRegistryState({ - stateDir, - env: hermeticEnv(), - config: { - plugins: { - installs: { - demo: { - source: "npm", - resolvedName: "@vendor/demo", - }, - }, - }, - }, - prompter: { shouldRepair: false }, - }); - - expect(nextConfig.plugins?.installs?.demo?.resolvedName).toBe("@vendor/demo"); - await expect(readPersistedPluginInstallLedger({ stateDir })).resolves.toBeNull(); - expect(vi.mocked(note).mock.calls.join("\n")).toContain("plugins.installs"); - }); - - it("moves legacy config install records into the ledger and refreshes an existing registry", async () => { + it("refreshes an existing registry during repair", async () => { const stateDir = makeTempDir(); const pluginDir = path.join(stateDir, "plugins", "demo"); fs.mkdirSync(pluginDir, { recursive: true }); @@ -110,45 +86,22 @@ describe("maybeRepairPluginRegistryState", () => { stateDir, candidates: [createCandidate(pluginDir)], env: hermeticEnv(), - config: { - plugins: { - installs: { - demo: { - source: "npm", - resolvedName: "@vendor/demo", - resolvedVersion: "1.0.0", - }, - }, - }, - }, + config: {}, prompter: { shouldRepair: true }, }); - expect(nextConfig.plugins?.installs).toBeUndefined(); - await expect(readPersistedPluginInstallLedger({ stateDir })).resolves.toMatchObject({ - records: { - demo: { - source: "npm", - resolvedName: "@vendor/demo", - resolvedVersion: "1.0.0", - }, - }, - }); + expect(nextConfig).toEqual({}); await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toMatchObject({ refreshReason: "migration", plugins: [ expect.objectContaining({ pluginId: "demo", - installRecord: expect.objectContaining({ - source: "npm", - resolvedName: "@vendor/demo", - }), }), ], }); }); - it("does not mutate legacy install records when registry migration is disabled", async () => { + it("does not repair when registry migration is disabled", async () => { const stateDir = makeTempDir(); const nextConfig = await maybeRepairPluginRegistryState({ @@ -156,21 +109,11 @@ describe("maybeRepairPluginRegistryState", () => { env: hermeticEnv({ [DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV]: "1", }), - config: { - plugins: { - installs: { - demo: { - source: "npm", - resolvedName: "@vendor/demo", - }, - }, - }, - }, + config: {}, prompter: { shouldRepair: true }, }); - expect(nextConfig.plugins?.installs?.demo?.resolvedName).toBe("@vendor/demo"); - await expect(readPersistedPluginInstallLedger({ stateDir })).resolves.toBeNull(); + expect(nextConfig).toEqual({}); expect(vi.mocked(note).mock.calls.join("\n")).toContain(DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV); }); }); diff --git a/src/commands/doctor-plugin-registry.ts b/src/commands/doctor-plugin-registry.ts index a917f67b144..f1d47c773f7 100644 --- a/src/commands/doctor-plugin-registry.ts +++ b/src/commands/doctor-plugin-registry.ts @@ -1,13 +1,6 @@ import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { PluginInstallRecord } from "../config/types.plugins.js"; -import { - readPersistedPluginInstallLedger, - resolvePluginInstallLedgerStorePath, - withoutPluginInstallRecords, - writePersistedPluginInstallLedger, - type PluginInstallLedgerStoreOptions, -} from "../plugins/install-ledger-store.js"; +import type { InstalledPluginIndexRecordStoreOptions } from "../plugins/installed-plugin-index-records.js"; import { refreshPluginRegistry } from "../plugins/plugin-registry.js"; import { note } from "../terminal/note.js"; import { shortenHomePath } from "../utils.js"; @@ -20,74 +13,11 @@ import { } from "./doctor/shared/plugin-registry-migration.js"; type PluginRegistryDoctorRepairParams = Omit & - PluginInstallLedgerStoreOptions & { + InstalledPluginIndexRecordStoreOptions & { config: OpenClawConfig; prompter: Pick; }; -type LegacyInstallLedgerMigrationResult = { - config: OpenClawConfig; - migrated: boolean; -}; - -function countRecords(records: Record | undefined): number { - return Object.keys(records ?? {}).length; -} - -function mergeInstallRecords( - legacyRecords: Record, - ledgerRecords: Record | undefined, -): Record { - return { - ...legacyRecords, - ...ledgerRecords, - }; -} - -async function maybeMigrateLegacyInstallLedger( - params: PluginRegistryDoctorRepairParams, -): Promise { - const legacyRecords = params.config.plugins?.installs; - const legacyCount = countRecords(legacyRecords); - if (!legacyRecords || legacyCount === 0) { - return { - config: params.config, - migrated: false, - }; - } - - const ledgerPath = resolvePluginInstallLedgerStorePath(params); - if (!params.prompter.shouldRepair) { - note( - [ - `Legacy plugin install records still live in config at \`plugins.installs\`.`, - `Repair with ${formatCliCommand("openclaw doctor --fix")} to move them to ${shortenHomePath(ledgerPath)} and remove the config copy.`, - ].join("\n"), - "Plugin registry", - ); - return { - config: params.config, - migrated: false, - }; - } - - const existingLedger = await readPersistedPluginInstallLedger(params); - const nextRecords = mergeInstallRecords(legacyRecords, existingLedger?.records); - await writePersistedPluginInstallLedger(nextRecords, params); - const nextConfig = withoutPluginInstallRecords(params.config); - note( - [ - `Moved ${legacyCount} legacy plugin install record${legacyCount === 1 ? "" : "s"} from config to ${shortenHomePath(ledgerPath)}.`, - "Removed the legacy `plugins.installs` config copy.", - ].join("\n"), - "Plugin registry", - ); - return { - config: nextConfig, - migrated: true, - }; -} - export async function maybeRepairPluginRegistryState( params: PluginRegistryDoctorRepairParams, ): Promise { @@ -103,13 +33,9 @@ export async function maybeRepairPluginRegistryState( return params.config; } - let nextConfig = params.config; - const ledgerMigration = await maybeMigrateLegacyInstallLedger(params); - nextConfig = ledgerMigration.config; - const migrationParams = { ...params, - config: nextConfig, + config: params.config, }; if (!params.prompter.shouldRepair) { if (preflight.action === "migrate") { @@ -121,7 +47,7 @@ export async function maybeRepairPluginRegistryState( "Plugin registry", ); } - return nextConfig; + return params.config; } if (preflight.action === "migrate") { @@ -134,10 +60,10 @@ export async function maybeRepairPluginRegistryState( "Plugin registry", ); } - return nextConfig; + return params.config; } - if (ledgerMigration.migrated) { + if (preflight.action === "skip-existing") { const index = await refreshPluginRegistry({ ...migrationParams, reason: "migration", @@ -150,5 +76,5 @@ export async function maybeRepairPluginRegistryState( ); } - return nextConfig; + return params.config; } diff --git a/src/commands/doctor/shared/plugin-registry-migration.test.ts b/src/commands/doctor/shared/plugin-registry-migration.test.ts index 413a0dfa1f5..5925620e951 100644 --- a/src/commands/doctor/shared/plugin-registry-migration.test.ts +++ b/src/commands/doctor/shared/plugin-registry-migration.test.ts @@ -72,7 +72,7 @@ function createCurrentIndex(): InstalledPluginIndex { version: 1, hostContractVersion: "2026.4.25", compatRegistryVersion: "compat-v1", - migrationVersion: 2, + migrationVersion: 1, policyHash: "policy-v1", generatedAtMs: 1777118400000, plugins: [], @@ -83,7 +83,7 @@ function createCurrentIndex(): InstalledPluginIndex { describe("plugin registry install migration", () => { it("short-circuits when a current registry file already exists", async () => { const stateDir = makeTempDir(); - const filePath = path.join(stateDir, "plugins", "installed-index.json"); + const filePath = path.join(stateDir, "plugins", "installs.json"); await writePersistedInstalledPluginIndex(createCurrentIndex(), { stateDir }); const readConfig = vi.fn(async () => ({})); @@ -106,11 +106,11 @@ describe("plugin registry install migration", () => { it("migrates when an existing registry file is not current", async () => { const stateDir = makeTempDir(); - const filePath = path.join(stateDir, "plugins", "installed-index.json"); + const filePath = path.join(stateDir, "plugins", "installs.json"); const pluginDir = path.join(stateDir, "plugins", "demo"); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.mkdirSync(pluginDir, { recursive: true }); - fs.writeFileSync(filePath, JSON.stringify({ version: 1, migrationVersion: 1 }), "utf8"); + fs.writeFileSync(filePath, JSON.stringify({ version: 1, migrationVersion: 0 }), "utf8"); await expect( migratePluginRegistryForInstall({ @@ -127,7 +127,7 @@ describe("plugin registry install migration", () => { }); await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toMatchObject({ - migrationVersion: 2, + migrationVersion: 1, plugins: [expect.objectContaining({ pluginId: "demo" })], }); }); @@ -202,10 +202,10 @@ describe("plugin registry install migration", () => { }, }); expect(readConfig).not.toHaveBeenCalled(); - expect(fs.existsSync(path.join(stateDir, "plugins", "installed-index.json"))).toBe(false); + expect(fs.existsSync(path.join(stateDir, "plugins", "installs.json"))).toBe(false); }); - it("migrates missing registry state from legacy discovery and config inputs", async () => { + it("builds missing registry state from discovered plugin manifests", async () => { const stateDir = makeTempDir(); const pluginDir = path.join(stateDir, "plugins", "demo"); fs.mkdirSync(pluginDir, { recursive: true }); @@ -215,17 +215,7 @@ describe("plugin registry install migration", () => { migratePluginRegistryForInstall({ stateDir, candidates: [candidate], - readConfig: async () => ({ - plugins: { - installs: { - demo: { - source: "npm", - resolvedName: "@vendor/demo", - resolvedVersion: "1.0.0", - }, - }, - }, - }), + readConfig: async () => ({}), env: hermeticEnv(), }), ).resolves.toMatchObject({ @@ -233,15 +223,10 @@ describe("plugin registry install migration", () => { migrated: true, current: { refreshReason: "migration", - migrationVersion: 2, + migrationVersion: 1, plugins: [ expect.objectContaining({ pluginId: "demo", - installRecord: expect.objectContaining({ - source: "npm", - resolvedName: "@vendor/demo", - resolvedVersion: "1.0.0", - }), }), ], }, diff --git a/src/commands/doctor/shared/plugin-registry-migration.ts b/src/commands/doctor/shared/plugin-registry-migration.ts index f94e34bca5e..0dd76e05c46 100644 --- a/src/commands/doctor/shared/plugin-registry-migration.ts +++ b/src/commands/doctor/shared/plugin-registry-migration.ts @@ -1,10 +1,7 @@ import fs from "node:fs"; import { normalizeProviderId } from "../../../agents/provider-id.js"; import type { OpenClawConfig } from "../../../config/types.openclaw.js"; -import { - loadPluginInstallRecords, - writePersistedPluginInstallLedger, -} from "../../../plugins/install-ledger-store.js"; +import { loadInstalledPluginIndexInstallRecords } from "../../../plugins/installed-plugin-index-records.js"; import { inspectPersistedInstalledPluginIndex, readPersistedInstalledPluginIndexSync, @@ -256,10 +253,11 @@ export async function migratePluginRegistryForInstall( } const config = await readMigrationConfig(params); - const installRecords = await loadPluginInstallRecords({ ...params, config }); + const installRecords = await loadInstalledPluginIndexInstallRecords(params); const migrationParams = { ...params, config, + installRecords, }; const inspection = await inspectPersistedInstalledPluginIndex(migrationParams); const candidateIndex = loadInstalledPluginIndex({ @@ -275,9 +273,6 @@ export async function migratePluginRegistryForInstall( installRecords, }), }; - if (Object.keys(installRecords).length > 0) { - await writePersistedPluginInstallLedger(installRecords, params); - } await writePersistedInstalledPluginIndex(current, params); return { status: "migrated", diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index 0e5fffa15d5..302be1e45d1 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -23023,164 +23023,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { description: "Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.", }, - installs: { - type: "object", - propertyNames: { - type: "string", - }, - additionalProperties: { - type: "object", - properties: { - source: { - anyOf: [ - { - anyOf: [ - { - type: "string", - const: "npm", - }, - { - type: "string", - const: "archive", - }, - { - type: "string", - const: "path", - }, - { - type: "string", - const: "clawhub", - }, - ], - }, - { - type: "string", - const: "marketplace", - }, - ], - title: "Plugin Install Source", - description: 'Install source ("npm", "archive", or "path").', - }, - spec: { - type: "string", - title: "Plugin Install Spec", - description: "Original npm spec used for install (if source is npm).", - }, - sourcePath: { - type: "string", - title: "Plugin Install Source Path", - description: "Original archive/path used for install (if any).", - }, - installPath: { - type: "string", - title: "Plugin Install Path", - description: "Resolved install directory for the installed plugin bundle.", - }, - version: { - type: "string", - title: "Plugin Install Version", - description: "Version recorded at install time (if available).", - }, - resolvedName: { - type: "string", - title: "Plugin Resolved Package Name", - description: "Resolved npm package name from the fetched artifact.", - }, - resolvedVersion: { - type: "string", - title: "Plugin Resolved Package Version", - description: - "Resolved npm package version from the fetched artifact (useful for non-pinned specs).", - }, - resolvedSpec: { - type: "string", - title: "Plugin Resolved Package Spec", - description: - "Resolved exact npm spec (@) from the fetched artifact.", - }, - integrity: { - type: "string", - title: "Plugin Resolved Integrity", - description: - "Resolved npm dist integrity hash for the fetched artifact (if reported by npm).", - }, - shasum: { - type: "string", - title: "Plugin Resolved Shasum", - description: - "Resolved npm dist shasum for the fetched artifact (if reported by npm).", - }, - resolvedAt: { - type: "string", - title: "Plugin Resolution Time", - description: - "ISO timestamp when npm package metadata was last resolved for this install record.", - }, - installedAt: { - type: "string", - title: "Plugin Install Time", - description: "ISO timestamp of last install/update.", - }, - clawhubUrl: { - type: "string", - }, - clawhubPackage: { - type: "string", - }, - clawhubFamily: { - anyOf: [ - { - type: "string", - const: "code-plugin", - }, - { - type: "string", - const: "bundle-plugin", - }, - ], - }, - clawhubChannel: { - anyOf: [ - { - type: "string", - const: "official", - }, - { - type: "string", - const: "community", - }, - { - type: "string", - const: "private", - }, - ], - }, - marketplaceName: { - type: "string", - title: "Plugin Marketplace Name", - description: - "Marketplace display name recorded for marketplace-backed plugin installs (if available).", - }, - marketplaceSource: { - type: "string", - title: "Plugin Marketplace Source", - description: - "Original marketplace source used to resolve the install (for example a repo path or Git URL).", - }, - marketplacePlugin: { - type: "string", - title: "Plugin Marketplace Plugin", - description: - "Plugin entry name inside the source marketplace, used for later updates.", - }, - }, - required: ["source"], - additionalProperties: false, - }, - title: "Plugin Install Records", - description: - "Deprecated compatibility fallback for legacy CLI-managed install metadata. New plugin installs use the state-managed `plugins/installs.json` ledger.", - }, }, additionalProperties: false, title: "Plugins", @@ -27687,86 +27529,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { help: "Plugin-defined configuration payload interpreted by that plugin's own schema and validation rules. Use only documented fields from the plugin to prevent ignored or invalid settings.", tags: ["advanced"], }, - "plugins.installs": { - label: "Plugin Install Records", - help: "Deprecated compatibility fallback for legacy CLI-managed install metadata. New plugin installs use the state-managed `plugins/installs.json` ledger.", - tags: ["advanced"], - }, - "plugins.installs.*.source": { - label: "Plugin Install Source", - help: 'Install source ("npm", "archive", or "path").', - tags: ["advanced"], - }, - "plugins.installs.*.spec": { - label: "Plugin Install Spec", - help: "Original npm spec used for install (if source is npm).", - tags: ["advanced"], - }, - "plugins.installs.*.sourcePath": { - label: "Plugin Install Source Path", - help: "Original archive/path used for install (if any).", - tags: ["storage"], - }, - "plugins.installs.*.installPath": { - label: "Plugin Install Path", - help: "Resolved install directory for the installed plugin bundle.", - tags: ["storage"], - }, - "plugins.installs.*.version": { - label: "Plugin Install Version", - help: "Version recorded at install time (if available).", - tags: ["advanced"], - }, - "plugins.installs.*.resolvedName": { - label: "Plugin Resolved Package Name", - help: "Resolved npm package name from the fetched artifact.", - tags: ["advanced"], - }, - "plugins.installs.*.resolvedVersion": { - label: "Plugin Resolved Package Version", - help: "Resolved npm package version from the fetched artifact (useful for non-pinned specs).", - tags: ["advanced"], - }, - "plugins.installs.*.resolvedSpec": { - label: "Plugin Resolved Package Spec", - help: "Resolved exact npm spec (@) from the fetched artifact.", - tags: ["advanced"], - }, - "plugins.installs.*.integrity": { - label: "Plugin Resolved Integrity", - help: "Resolved npm dist integrity hash for the fetched artifact (if reported by npm).", - tags: ["advanced"], - }, - "plugins.installs.*.shasum": { - label: "Plugin Resolved Shasum", - help: "Resolved npm dist shasum for the fetched artifact (if reported by npm).", - tags: ["advanced"], - }, - "plugins.installs.*.resolvedAt": { - label: "Plugin Resolution Time", - help: "ISO timestamp when npm package metadata was last resolved for this install record.", - tags: ["advanced"], - }, - "plugins.installs.*.installedAt": { - label: "Plugin Install Time", - help: "ISO timestamp of last install/update.", - tags: ["advanced"], - }, - "plugins.installs.*.marketplaceName": { - label: "Plugin Marketplace Name", - help: "Marketplace display name recorded for marketplace-backed plugin installs (if available).", - tags: ["advanced"], - }, - "plugins.installs.*.marketplaceSource": { - label: "Plugin Marketplace Source", - help: "Original marketplace source used to resolve the install (for example a repo path or Git URL).", - tags: ["advanced"], - }, - "plugins.installs.*.marketplacePlugin": { - label: "Plugin Marketplace Plugin", - help: "Plugin entry name inside the source marketplace, used for later updates.", - tags: ["advanced"], - }, "models.providers.*.headers.*": { sensitive: true, tags: ["security", "models"], diff --git a/src/config/schema.help.quality.test.ts b/src/config/schema.help.quality.test.ts index 4603b4a0411..255efa8fc12 100644 --- a/src/config/schema.help.quality.test.ts +++ b/src/config/schema.help.quality.test.ts @@ -360,7 +360,6 @@ const TARGET_KEYS = [ "plugins.entries.*.apiKey", "plugins.entries.*.env", "plugins.entries.*.config", - "plugins.installs", "auth", "auth.cooldowns", "models", diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index 56e03e2b57c..d44e74dbe80 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1170,31 +1170,6 @@ export const FIELD_HELP: Record = { "Per-plugin environment variable map injected for that plugin runtime context only. Use this to scope provider credentials to one plugin instead of sharing global process environment.", "plugins.entries.*.config": "Plugin-defined configuration payload interpreted by that plugin's own schema and validation rules. Use only documented fields from the plugin to prevent ignored or invalid settings.", - "plugins.installs": - "Deprecated compatibility fallback for legacy CLI-managed install metadata. New plugin installs use the state-managed `plugins/installs.json` ledger.", - "plugins.installs.*.source": 'Install source ("npm", "archive", or "path").', - "plugins.installs.*.spec": "Original npm spec used for install (if source is npm).", - "plugins.installs.*.sourcePath": "Original archive/path used for install (if any).", - "plugins.installs.*.installPath": "Resolved install directory for the installed plugin bundle.", - "plugins.installs.*.version": "Version recorded at install time (if available).", - "plugins.installs.*.resolvedName": "Resolved npm package name from the fetched artifact.", - "plugins.installs.*.resolvedVersion": - "Resolved npm package version from the fetched artifact (useful for non-pinned specs).", - "plugins.installs.*.resolvedSpec": - "Resolved exact npm spec (@) from the fetched artifact.", - "plugins.installs.*.integrity": - "Resolved npm dist integrity hash for the fetched artifact (if reported by npm).", - "plugins.installs.*.shasum": - "Resolved npm dist shasum for the fetched artifact (if reported by npm).", - "plugins.installs.*.resolvedAt": - "ISO timestamp when npm package metadata was last resolved for this install record.", - "plugins.installs.*.installedAt": "ISO timestamp of last install/update.", - "plugins.installs.*.marketplaceName": - "Marketplace display name recorded for marketplace-backed plugin installs (if available).", - "plugins.installs.*.marketplaceSource": - "Original marketplace source used to resolve the install (for example a repo path or Git URL).", - "plugins.installs.*.marketplacePlugin": - "Plugin entry name inside the source marketplace, used for later updates.", "agents.list.*.identity.avatar": "Agent avatar (workspace-relative path, http(s) URL, or data URI).", "agents.defaults.model.primary": "Primary model (provider/model).", diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index 3d2b4e0e200..776e02502ef 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -871,20 +871,4 @@ export const FIELD_LABELS: Record = { "plugins.entries.*.apiKey": "Plugin API Key", // pragma: allowlist secret "plugins.entries.*.env": "Plugin Environment Variables", "plugins.entries.*.config": "Plugin Config", - "plugins.installs": "Plugin Install Records", - "plugins.installs.*.source": "Plugin Install Source", - "plugins.installs.*.spec": "Plugin Install Spec", - "plugins.installs.*.sourcePath": "Plugin Install Source Path", - "plugins.installs.*.installPath": "Plugin Install Path", - "plugins.installs.*.version": "Plugin Install Version", - "plugins.installs.*.resolvedName": "Plugin Resolved Package Name", - "plugins.installs.*.resolvedVersion": "Plugin Resolved Package Version", - "plugins.installs.*.resolvedSpec": "Plugin Resolved Package Spec", - "plugins.installs.*.integrity": "Plugin Resolved Integrity", - "plugins.installs.*.shasum": "Plugin Resolved Shasum", - "plugins.installs.*.resolvedAt": "Plugin Resolution Time", - "plugins.installs.*.installedAt": "Plugin Install Time", - "plugins.installs.*.marketplaceName": "Plugin Marketplace Name", - "plugins.installs.*.marketplaceSource": "Plugin Marketplace Source", - "plugins.installs.*.marketplacePlugin": "Plugin Marketplace Plugin", }; diff --git a/src/config/types.plugins.ts b/src/config/types.plugins.ts index 86ecb8de78a..1f20e911279 100644 --- a/src/config/types.plugins.ts +++ b/src/config/types.plugins.ts @@ -50,6 +50,11 @@ export type PluginsConfig = { load?: PluginsLoadConfig; slots?: PluginSlotsConfig; entries?: Record; + /** + * Internal transient carrier for plugin install records during command flows. + * This is intentionally omitted from the config schema and must not be + * persisted to openclaw.json. + */ installs?: Record; }; import type { InstallRecordBase } from "./types.installs.js"; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 26bd902750e..345a20971cd 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -19,7 +19,6 @@ import { SecretsConfigSchema, } from "./zod-schema.core.js"; import { HookMappingSchema, HooksGmailSchema, InternalHooksSchema } from "./zod-schema.hooks.js"; -import { PluginInstallRecordShape } from "./zod-schema.installs.js"; import { ChannelsSchema } from "./zod-schema.providers.js"; import { sensitive } from "./zod-schema.sensitive.js"; import { @@ -1001,16 +1000,6 @@ export const OpenClawSchema = z .strict() .optional(), entries: z.record(z.string(), PluginEntrySchema).optional(), - installs: z - .record( - z.string(), - z - .object({ - ...PluginInstallRecordShape, - }) - .strict(), - ) - .optional(), }) .strict() .optional(), diff --git a/src/gateway/config-reload-plan.ts b/src/gateway/config-reload-plan.ts index 79ce46b2ed0..60ccaaf812c 100644 --- a/src/gateway/config-reload-plan.ts +++ b/src/gateway/config-reload-plan.ts @@ -203,7 +203,7 @@ function matchRule(path: string): ReloadRule | null { function isPluginInstallTimestampPath(path: string): boolean { // Legacy compatibility only: new plugin install metadata lives in the - // managed install ledger, but old config writes may still touch this path. + // managed plugin index, but old config writes may still touch this path. return /^plugins\.installs\..+\.(installedAt|resolvedAt)$/.test(path); } @@ -216,7 +216,7 @@ function getPluginInstallRecords(config: unknown): Record { return {}; } // Keep legacy config install records out of gateway restart decisions while - // migration/doctor moves them into the managed plugin install ledger. + // migration/doctor moves them into the managed plugin index install records. const installs = plugins.installs; return isPlainObject(installs) ? installs : {}; } diff --git a/src/plugins/compat/registry.ts b/src/plugins/compat/registry.ts index a6ccf77dc0f..282da5406fc 100644 --- a/src/plugins/compat/registry.ts +++ b/src/plugins/compat/registry.ts @@ -223,26 +223,6 @@ export const PLUGIN_COMPAT_RECORDS = [ diagnostics: ["persisted-registry-disabled"], tests: ["src/plugins/plugin-registry.test.ts"], }, - { - code: "legacy-config-plugin-installs", - status: "deprecated", - owner: "config", - introduced: "2026-04-25", - deprecated: "2026-04-25", - warningStarts: "2026-04-25", - replacement: "state-managed `plugins/installs.json` plugin install ledger", - docsPath: "/cli/plugins#install-ledger", - surfaces: ["plugins.installs", "plugin install/update/uninstall", "plugin registry migration"], - diagnostics: ["plugin install ledger compatibility"], - tests: [ - "src/plugins/install-ledger-store.test.ts", - "src/cli/plugins-install-persist.test.ts", - "src/cli/plugins-cli.update.test.ts", - "src/cli/plugins-cli.uninstall.test.ts", - ], - releaseNote: - "`plugins.installs` remains readable as a legacy compatibility fallback while new plugin install metadata moves to the state-managed install ledger.", - }, ] as const satisfies readonly PluginCompatRecord[]; export type PluginCompatCode = (typeof PLUGIN_COMPAT_RECORDS)[number]["code"];