fix(onboard): preserve rerun config migrations

Fix non-interactive and wizard onboarding reruns so existing agent lists and bindings are preserved unless the user explicitly resets config.

Isolate legacy `plugins.installs` migration into its own write so the config size-drop allowance cannot mask unrelated config loss, while preserving new or repaired install records for the final plugin-index commit. Also keep shrinkwrap generation pinned to pnpm-locked transitive patch versions only when the dependency edge still allows that version, and isolate the tooling Vitest shard that mutates process state.

Fixes #84692.
Replaces #84748.

Co-authored-by: yetval <yetvald@gmail.com>
This commit is contained in:
Peter Steinberger
2026-05-27 18:05:07 +01:00
committed by GitHub
parent 11dfef201f
commit de5971eedc
19 changed files with 693 additions and 38 deletions

View File

@@ -28,6 +28,46 @@ function mergeUnsetPaths(
return merged.length > 0 ? merged : undefined;
}
export function hasPendingPluginInstallRecords(config: OpenClawConfig): boolean {
return Object.keys(config.plugins?.installs ?? {}).length > 0;
}
export function unchangedPendingPluginInstallRecordIds(
config: OpenClawConfig,
baseConfig: OpenClawConfig,
): string[] {
const pendingInstalls = config.plugins?.installs ?? {};
return Object.entries(baseConfig.plugins?.installs ?? {})
.filter(([pluginId, baseInstall]) => isDeepStrictEqual(pendingInstalls[pluginId], baseInstall))
.map(([pluginId]) => pluginId);
}
export function stripPendingPluginInstallRecords(
config: OpenClawConfig,
pluginIds?: Iterable<string>,
): OpenClawConfig {
if (!pluginIds) {
return withoutPluginInstallRecords(config);
}
const removeIds = new Set(pluginIds);
if (removeIds.size === 0 || !config.plugins?.installs) {
return config;
}
const remainingInstalls = Object.fromEntries(
Object.entries(config.plugins.installs).filter(([pluginId]) => !removeIds.has(pluginId)),
);
if (Object.keys(remainingInstalls).length === 0) {
return withoutPluginInstallRecords(config);
}
return {
...config,
plugins: {
...config.plugins,
installs: remainingInstalls,
},
};
}
type ConfigCommit = (
config: OpenClawConfig,
writeOptions?: ConfigWriteOptions,
@@ -113,8 +153,7 @@ export async function commitConfigWriteWithPendingPluginInstalls(params: {
movedInstallRecords: boolean;
persistedHash: string | null;
}> {
const pendingInstallRecords = params.nextConfig.plugins?.installs ?? {};
if (Object.keys(pendingInstallRecords).length === 0) {
if (!hasPendingPluginInstallRecords(params.nextConfig)) {
const committed = params.writeOptions
? await params.commit(params.nextConfig, params.writeOptions)
: await params.commit(params.nextConfig);
@@ -126,6 +165,7 @@ export async function commitConfigWriteWithPendingPluginInstalls(params: {
};
}
const pendingInstallRecords = params.nextConfig.plugins?.installs ?? {};
const previousInstallRecords = await loadInstalledPluginIndexInstallRecords();
const nextInstallRecords = {
...previousInstallRecords,