@@ -18,6 +18,10 @@ import {
1818 collectRelevantDoctorPluginIds ,
1919 listPluginDoctorLegacyConfigRules ,
2020} from "../plugins/doctor-contract-registry.js" ;
21+ import {
22+ loadInstalledPluginIndexInstallRecordsSync ,
23+ writePersistedInstalledPluginIndexInstallRecordsSync ,
24+ } from "../plugins/installed-plugin-index-records.js" ;
2125import { sanitizeTerminalText } from "../terminal/safe-text.js" ;
2226import { isRecord } from "../utils.js" ;
2327import { VERSION } from "../version.js" ;
@@ -60,6 +64,7 @@ import {
6064 projectSourceOntoRuntimeShape ,
6165 restoreEnvRefsFromMap ,
6266 resolvePersistCandidateForWrite ,
67+ resolveManagedUnsetPathsForWrite ,
6368 resolveWriteEnvSnapshotForPath ,
6469} from "./io.write-prepare.js" ;
6570import { findLegacyConfigIssues } from "./legacy.js" ;
@@ -70,6 +75,10 @@ import {
7075} from "./materialize.js" ;
7176import { applyMergePatch } from "./merge-patch.js" ;
7277import { resolveConfigPath , resolveStateDir } from "./paths.js" ;
78+ import {
79+ extractShippedPluginInstallConfigRecords ,
80+ stripShippedPluginInstallConfigRecords ,
81+ } from "./plugin-install-config-migration.js" ;
7382import { applyConfigOverrides } from "./runtime-overrides.js" ;
7483import {
7584 clearRuntimeConfigSnapshot as clearRuntimeConfigSnapshotState ,
@@ -1009,9 +1018,12 @@ async function recoverConfigFromJsonRootSuffixWithDeps(params: {
10091018 readResolution . resolvedConfigRaw ,
10101019 suffixRecovery . parsed ,
10111020 ) ;
1012- const validated = validateConfigObjectWithPlugins ( legacyResolution . effectiveConfigRaw , {
1013- env : params . deps . env ,
1014- } ) ;
1021+ const validated = validateConfigObjectWithPlugins (
1022+ stripShippedPluginInstallConfigRecords ( legacyResolution . effectiveConfigRaw ) ,
1023+ {
1024+ env : params . deps . env ,
1025+ } ,
1026+ ) ;
10151027 if ( ! validated . ok ) {
10161028 return false ;
10171029 }
@@ -1198,6 +1210,41 @@ export function createConfigIO(
11981210 return applyConfigOverrides ( cfgWithOwnerDisplaySecret ) ;
11991211 }
12001212
1213+ function migrateAndStripShippedPluginInstallConfigRecords ( configRaw : unknown ) : unknown {
1214+ const installRecords = extractShippedPluginInstallConfigRecords ( configRaw ) ;
1215+ const stripped = stripShippedPluginInstallConfigRecords ( configRaw ) ;
1216+ if ( Object . keys ( installRecords ) . length === 0 ) {
1217+ return stripped ;
1218+ }
1219+
1220+ try {
1221+ const stateDir = resolveStateDir ( deps . env , deps . homedir ) ;
1222+ const existingRecords = loadInstalledPluginIndexInstallRecordsSync ( {
1223+ env : deps . env ,
1224+ stateDir,
1225+ } ) ;
1226+ const nextRecords = {
1227+ ...installRecords ,
1228+ ...existingRecords ,
1229+ } ;
1230+ if ( Object . keys ( installRecords ) . some ( ( pluginId ) => ! ( pluginId in existingRecords ) ) ) {
1231+ writePersistedInstalledPluginIndexInstallRecordsSync ( nextRecords , {
1232+ config : coerceConfig ( stripped ) ,
1233+ env : deps . env ,
1234+ stateDir,
1235+ } ) ;
1236+ }
1237+ } catch ( err ) {
1238+ deps . logger . warn (
1239+ `Config (${ configPath } ): could not migrate shipped plugins.installs records into the plugin index: ${ formatErrorMessage (
1240+ err ,
1241+ ) } `,
1242+ ) ;
1243+ }
1244+
1245+ return stripped ;
1246+ }
1247+
12011248 function loadConfig ( ) : OpenClawConfig {
12021249 try {
12031250 maybeLoadDotEnvForConfig ( deps . env ) ;
@@ -1230,7 +1277,9 @@ export function createConfigIO(
12301277 ) ;
12311278 const resolvedConfig = readResolution . resolvedConfigRaw ;
12321279 const legacyResolution = resolveLegacyConfigForRead ( resolvedConfig , effectiveParsed ) ;
1233- const effectiveConfigRaw = legacyResolution . effectiveConfigRaw ;
1280+ const effectiveConfigRaw = migrateAndStripShippedPluginInstallConfigRecords (
1281+ legacyResolution . effectiveConfigRaw ,
1282+ ) ;
12341283 for ( const w of readResolution . envWarnings ) {
12351284 deps . logger . warn (
12361285 `Config (${ configPath } ): missing env var "${ w . varName } " at ${ w . configPath } - feature using this value will be unavailable` ,
@@ -1439,7 +1488,9 @@ export function createConfigIO(
14391488
14401489 const resolvedConfigRaw = readResolution . resolvedConfigRaw ;
14411490 const legacyResolution = resolveLegacyConfigForRead ( resolvedConfigRaw , effectiveParsed ) ;
1442- const effectiveConfigRaw = legacyResolution . effectiveConfigRaw ;
1491+ const effectiveConfigRaw = migrateAndStripShippedPluginInstallConfigRecords (
1492+ legacyResolution . effectiveConfigRaw ,
1493+ ) ;
14431494 fallbackSourceConfig = coerceConfig ( effectiveConfigRaw ) ;
14441495 const validated = validateConfigObjectWithPlugins ( effectiveConfigRaw , {
14451496 env : deps . env ,
@@ -1562,6 +1613,7 @@ export function createConfigIO(
15621613 writeOptions : {
15631614 envSnapshotForRestore : result . envSnapshotForRestore ,
15641615 expectedConfigPath : configPath ,
1616+ unsetPaths : resolveManagedUnsetPathsForWrite ( undefined ) ,
15651617 } ,
15661618 } ;
15671619 }
@@ -1609,7 +1661,9 @@ export function createConfigIO(
16091661 readResolution . resolvedConfigRaw ,
16101662 recovered . parsed ,
16111663 ) ;
1612- return coerceConfig ( legacyResolution . effectiveConfigRaw ) ;
1664+ return coerceConfig (
1665+ stripShippedPluginInstallConfigRecords ( legacyResolution . effectiveConfigRaw ) ,
1666+ ) ;
16131667 } catch {
16141668 return { } ;
16151669 }
@@ -1620,6 +1674,7 @@ export function createConfigIO(
16201674 options : ConfigWriteOptions = { } ,
16211675 ) : Promise < { persistedHash : string ; persistedConfig : OpenClawConfig } > {
16221676 clearConfigCache ( ) ;
1677+ const unsetPaths = resolveManagedUnsetPathsForWrite ( options . unsetPaths ) ;
16231678 let persistCandidate : unknown = cfg ;
16241679 const snapshot = options . baseSnapshot ?? ( await readConfigFileSnapshotInternal ( ) ) . snapshot ;
16251680 let envRefMap : Map < string , string > | null = null ;
@@ -1655,10 +1710,7 @@ export function createConfigIO(
16551710 }
16561711 }
16571712
1658- persistCandidate = applyUnsetPathsForWrite (
1659- persistCandidate as OpenClawConfig ,
1660- options . unsetPaths ,
1661- ) ;
1713+ persistCandidate = applyUnsetPathsForWrite ( persistCandidate as OpenClawConfig , unsetPaths ) ;
16621714
16631715 const validated = validateConfigObjectRawWithPlugins ( persistCandidate , { env : deps . env } ) ;
16641716 if ( ! validated . ok ) {
@@ -1720,7 +1772,7 @@ export function createConfigIO(
17201772 envRefMap && changedPaths
17211773 ? ( restoreEnvRefsFromMap ( cfgToWrite , "" , envRefMap , changedPaths ) as OpenClawConfig )
17221774 : cfgToWrite ;
1723- const outputConfig = applyUnsetPathsForWrite ( outputConfigBase , options . unsetPaths ) ;
1775+ const outputConfig = applyUnsetPathsForWrite ( outputConfigBase , unsetPaths ) ;
17241776 // Do NOT apply runtime defaults when writing - user config should only contain
17251777 // explicitly set values. Runtime defaults are applied when loading (issue #6070).
17261778 const stampedOutputConfig = stampConfigVersion ( outputConfig ) ;
@@ -2054,7 +2106,7 @@ export async function writeConfigFile(
20542106 expectedConfigPath : options . expectedConfigPath ,
20552107 envSnapshotForRestore : options . envSnapshotForRestore ,
20562108 } ) ,
2057- unsetPaths : options . unsetPaths ,
2109+ unsetPaths : resolveManagedUnsetPathsForWrite ( options . unsetPaths ) ,
20582110 allowDestructiveWrite : options . allowDestructiveWrite ,
20592111 skipRuntimeSnapshotRefresh : options . skipRuntimeSnapshotRefresh ,
20602112 skipOutputLogs : options . skipOutputLogs ,
0 commit comments