






















@@ -20,6 +20,7 @@ import {
2020} from "../plugins/doctor-contract-registry.js";
2121import {
2222loadInstalledPluginIndexInstallRecordsSync,
23+resolveInstalledPluginIndexRecordsStorePath,
2324writePersistedInstalledPluginIndexInstallRecordsSync,
2425} from "../plugins/installed-plugin-index-records.js";
2526import { sanitizeTerminalText } from "../terminal/safe-text.js";
@@ -118,6 +119,23 @@ export { CircularIncludeError, ConfigIncludeError } from "./includes.js";
118119export { MissingEnvVarError } from "./env-substitution.js";
119120export { resolveShellEnvExpectedKeys } from "./shell-env-expected-keys.js";
120121122+type ShippedPluginInstallConfigWriteMigration =
123+| {
124+migrated: false;
125+}
126+| {
127+migrated: true;
128+filePath: string;
129+previousFile:
130+| {
131+existed: false;
132+}
133+| {
134+existed: true;
135+raw: string;
136+};
137+};
138+121139const CONFIG_HEALTH_STATE_FILENAME = "config-health.json";
122140const loggedInvalidConfigs = new Set<string>();
123141@@ -1210,12 +1228,18 @@ export function createConfigIO(
12101228return applyConfigOverrides(cfgWithOwnerDisplaySecret);
12111229}
121212301213-function migrateAndStripShippedPluginInstallConfigRecords(configRaw: unknown): unknown {
1231+function migrateAndStripShippedPluginInstallConfigRecords(
1232+configRaw: unknown,
1233+options: { persist?: boolean } = {},
1234+): unknown {
12141235const installRecords = extractShippedPluginInstallConfigRecords(configRaw);
12151236const stripped = stripShippedPluginInstallConfigRecords(configRaw);
12161237if (Object.keys(installRecords).length === 0) {
12171238return stripped;
12181239}
1240+if (options.persist === false) {
1241+return stripped;
1242+}
1219124312201244try {
12211245const stateDir = resolveStateDir(deps.env, deps.homedir);
@@ -1248,24 +1272,34 @@ export function createConfigIO(
1248127212491273function ensureShippedPluginInstallConfigRecordsMigratedForWrite(
12501274snapshot: ConfigFileSnapshot,
1251-): void {
1275+): ShippedPluginInstallConfigWriteMigration {
12521276const installRecords = {
12531277 ...extractShippedPluginInstallConfigRecords(snapshot.sourceConfig),
12541278 ...extractShippedPluginInstallConfigRecords(snapshot.parsed),
12551279};
12561280if (Object.keys(installRecords).length === 0) {
1257-return;
1281+return { migrated: false };
12581282}
1259128312601284const stateDir = resolveStateDir(deps.env, deps.homedir);
1285+const filePath = resolveInstalledPluginIndexRecordsStorePath({
1286+env: deps.env,
1287+ stateDir,
1288+});
12611289const existingRecords = loadInstalledPluginIndexInstallRecordsSync({
12621290env: deps.env,
12631291 stateDir,
12641292});
12651293if (Object.keys(installRecords).every((pluginId) => pluginId in existingRecords)) {
1266-return;
1294+return { migrated: false };
12671295}
126812961297+const previousFile = deps.fs.existsSync(filePath)
1298+ ? ({
1299+existed: true,
1300+raw: deps.fs.readFileSync(filePath, "utf-8"),
1301+} as const)
1302+ : ({ existed: false } as const);
12691303try {
12701304writePersistedInstalledPluginIndexInstallRecordsSync(
12711305{
@@ -1278,6 +1312,11 @@ export function createConfigIO(
12781312 stateDir,
12791313},
12801314);
1315+return {
1316+migrated: true,
1317+ filePath,
1318+ previousFile,
1319+};
12811320} catch (err) {
12821321throw new Error(
12831322`Config write blocked: shipped plugins.installs records in ${configPath} could not be migrated into the plugin index. Fix state directory permissions or run openclaw plugins registry --refresh, then retry. ${formatErrorMessage(
@@ -1288,6 +1327,28 @@ export function createConfigIO(
12881327}
12891328}
129013291330+function rollbackShippedPluginInstallConfigWriteMigration(
1331+migration: ShippedPluginInstallConfigWriteMigration,
1332+): void {
1333+if (!migration.migrated) {
1334+return;
1335+}
1336+if (migration.previousFile.existed) {
1337+deps.fs.writeFileSync(migration.filePath, migration.previousFile.raw, {
1338+encoding: "utf-8",
1339+mode: 0o600,
1340+});
1341+return;
1342+}
1343+try {
1344+deps.fs.unlinkSync(migration.filePath);
1345+} catch (err) {
1346+if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") {
1347+throw err;
1348+}
1349+}
1350+}
1351+12911352function loadConfig(): OpenClawConfig {
12921353try {
12931354maybeLoadDotEnvForConfig(deps.env);
@@ -1423,7 +1484,9 @@ export function createConfigIO(
14231484}
14241485}
142514861426-async function readConfigFileSnapshotInternal(): Promise<ReadConfigFileSnapshotInternalResult> {
1487+async function readConfigFileSnapshotInternal(
1488+options: { persistShippedPluginInstallMigration?: boolean } = {},
1489+): Promise<ReadConfigFileSnapshotInternalResult> {
14271490maybeLoadDotEnvForConfig(deps.env);
14281491const exists = deps.fs.existsSync(configPath);
14291492if (!exists) {
@@ -1533,6 +1596,7 @@ export function createConfigIO(
15331596const legacyResolution = resolveLegacyConfigForRead(resolvedConfigRaw, effectiveParsed);
15341597const effectiveConfigRaw = migrateAndStripShippedPluginInstallConfigRecords(
15351598legacyResolution.effectiveConfigRaw,
1599+{ persist: options.persistShippedPluginInstallMigration !== false },
15361600);
15371601fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
15381602const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, {
@@ -1650,7 +1714,9 @@ export function createConfigIO(
16501714}
1651171516521716async function readConfigFileSnapshotForWrite(): Promise<ReadConfigFileSnapshotForWriteResult> {
1653-const result = await readConfigFileSnapshotInternal();
1717+const result = await readConfigFileSnapshotInternal({
1718+persistShippedPluginInstallMigration: false,
1719+});
16541720return {
16551721snapshot: result.snapshot,
16561722writeOptions: {
@@ -1719,8 +1785,13 @@ export function createConfigIO(
17191785clearConfigCache();
17201786const unsetPaths = resolveManagedUnsetPathsForWrite(options.unsetPaths);
17211787let persistCandidate: unknown = cfg;
1722-const snapshot = options.baseSnapshot ?? (await readConfigFileSnapshotInternal()).snapshot;
1723-ensureShippedPluginInstallConfigRecordsMigratedForWrite(snapshot);
1788+const snapshot =
1789+options.baseSnapshot ??
1790+(
1791+await readConfigFileSnapshotInternal({
1792+persistShippedPluginInstallMigration: false,
1793+})
1794+).snapshot;
17241795let envRefMap: Map<string, string> | null = null;
17251796let changedPaths: Set<string> | null = null;
17261797if (snapshot.valid && snapshot.exists) {
@@ -1938,6 +2009,9 @@ export function createConfigIO(
19382009`${path.basename(configPath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
19392010);
194020112012+const pluginInstallConfigMigration =
2013+ensureShippedPluginInstallConfigRecordsMigratedForWrite(snapshot);
2014+let configCommitted = false;
19412015try {
19422016await deps.fs.promises.writeFile(tmp, json, {
19432017encoding: "utf-8",
@@ -1961,6 +2035,7 @@ export function createConfigIO(
19612035await deps.fs.promises.unlink(tmp).catch(() => {
19622036// best-effort
19632037});
2038+configCommitted = true;
19642039logConfigOverwrite();
19652040logConfigWriteAnomalies();
19662041await appendWriteAudit(
@@ -1975,6 +2050,7 @@ export function createConfigIO(
19752050});
19762051throw err;
19772052}
2053+configCommitted = true;
19782054logConfigOverwrite();
19792055logConfigWriteAnomalies();
19802056await appendWriteAudit(
@@ -1984,6 +2060,9 @@ export function createConfigIO(
19842060);
19852061return { persistedHash: nextHash, persistedConfig: stampedOutputConfig };
19862062} catch (err) {
2063+if (!configCommitted) {
2064+rollbackShippedPluginInstallConfigWriteMigration(pluginInstallConfigMigration);
2065+}
19872066await appendWriteAudit("failed", err);
19882067throw err;
19892068}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。