mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(config): materialize fresh-install defaults for missing config (#125110)
* fix(config): materialize fresh-install defaults for missing config
A missing config file (fresh install) skipped runtime-defaults
materialization on both loadConfig and readConfigFileSnapshot, while an
existing empty {} config got the full defaults (compaction safeguard,
session/cron/model defaults). Out-of-box behavior silently diverged from
the documented defaults until the first config write created the file.
The drift dates to bc75968074, which dropped the missing-branch
materializeRuntimeConfig call during an import-trim.
Route the missing-file branches through the same load/snapshot
materialization as existing configs, and delete the now-unreferenced
per-mode defaults profile table in materialize.ts — load and snapshot
must materialize identically anyway (prepared-runtime exact-config
resolution depends on it), so the profile indirection only invited
drift.
* chore(ratchet): shrink io.load.ts assertion baseline to 2
The missing-config fix removed an 'as OpenClawConfig' cast; the
assertion-safety ratchet requires the baseline to shrink with it.
This commit is contained in:
committed by
GitHub
parent
b1e76c4640
commit
7b18510e6c
@@ -2714,7 +2714,7 @@ src/config/io.clobber-snapshot.ts 2
|
||||
src/config/io.context.ts 1
|
||||
src/config/io.health-state.ts 1
|
||||
src/config/io.invalid-config.ts 1
|
||||
src/config/io.load.ts 3
|
||||
src/config/io.load.ts 2
|
||||
src/config/io.observe-recovery.ts 11
|
||||
src/config/io.observe.ts 1
|
||||
src/config/io.read-helpers.ts 3
|
||||
|
||||
@@ -227,6 +227,22 @@ describe("readBestEffortConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("materializes fresh-install defaults when the config file is missing", async () => {
|
||||
await withTempHome(async () => {
|
||||
const { loadConfig } = await import("./io.runtime.js");
|
||||
|
||||
const snapshot = await readConfigFileSnapshot({ observe: false });
|
||||
const loaded = loadConfig({ pin: false, skipPluginValidation: true });
|
||||
|
||||
expect(snapshot.exists).toBe(false);
|
||||
// Missing config = fresh install; snapshot and load must produce the same
|
||||
// out-of-box defaults an existing empty {} config gets (contextPruning
|
||||
// stays provider-conditional, so compaction is the parity signal here).
|
||||
expect(snapshot.config.agents?.defaults?.compaction?.mode).toBe("safeguard");
|
||||
expect(loaded.agents?.defaults?.compaction?.mode).toBe("safeguard");
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses valid snapshots while preserving load-time defaults", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
|
||||
@@ -55,7 +55,15 @@ export function loadConfigFromContext(
|
||||
timeoutMs: resolveShellEnvFallbackTimeoutMs(deps.env),
|
||||
});
|
||||
}
|
||||
return migratePersistedImplicitMainRoster({}).config as OpenClawConfig;
|
||||
// A missing config is the fresh-install default path: materialize the
|
||||
// same runtime defaults an empty {} config gets, or out-of-box behavior
|
||||
// (compaction safeguard, session/cron defaults) silently diverges.
|
||||
return materializeConfigForLoad(
|
||||
context,
|
||||
coerceConfig(migratePersistedImplicitMainRoster({}).config),
|
||||
{},
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
const raw = deps.fs.readFileSync(configPath, "utf-8");
|
||||
const parsed = deps.json5.parse(raw);
|
||||
|
||||
@@ -71,7 +71,13 @@ export async function readConfigFileSnapshotInternal(
|
||||
parsed: {},
|
||||
sourceConfig: config,
|
||||
valid: true,
|
||||
runtimeConfig: config,
|
||||
// Missing config is the fresh-install default path: materialize the
|
||||
// same runtime defaults an existing empty {} config gets, so snapshot
|
||||
// consumers see identical out-of-box behavior either way.
|
||||
runtimeConfig: materializeRuntimeConfig(config, "snapshot", {
|
||||
manifestRegistry:
|
||||
context.options.pluginValidation === "core-only" ? { plugins: [] } : undefined,
|
||||
}),
|
||||
hash: hashConfigRaw(null),
|
||||
issues: [],
|
||||
warnings: [],
|
||||
|
||||
@@ -16,36 +16,12 @@ import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.
|
||||
import { normalizeConfigPaths } from "./normalize-paths.js";
|
||||
import type { OpenClawConfig, ResolvedSourceConfig, RuntimeConfig } from "./types.js";
|
||||
|
||||
type ConfigMaterializationMode = "load" | "missing" | "snapshot";
|
||||
|
||||
/** Defaults profile selected for config load, missing-file, or snapshot materialization. */
|
||||
type MaterializationProfile = {
|
||||
includeCompactionDefaults: boolean;
|
||||
includeContextPruningDefaults: boolean;
|
||||
includeLoggingDefaults: boolean;
|
||||
normalizePaths: boolean;
|
||||
};
|
||||
|
||||
// Snapshot and load must materialize identically: prepared-runtime exact-config
|
||||
// resolution compares the startup-published (snapshot) config against the reply-path
|
||||
// (load) config, and any divergence permanently fails that resolve for affected configs.
|
||||
const FULL_MATERIALIZATION_PROFILE: MaterializationProfile = {
|
||||
includeCompactionDefaults: true,
|
||||
includeContextPruningDefaults: true,
|
||||
includeLoggingDefaults: true,
|
||||
normalizePaths: true,
|
||||
};
|
||||
|
||||
const MATERIALIZATION_PROFILES: Record<ConfigMaterializationMode, MaterializationProfile> = {
|
||||
load: FULL_MATERIALIZATION_PROFILE,
|
||||
missing: {
|
||||
includeCompactionDefaults: true,
|
||||
includeContextPruningDefaults: true,
|
||||
includeLoggingDefaults: false,
|
||||
normalizePaths: false,
|
||||
},
|
||||
snapshot: FULL_MATERIALIZATION_PROFILE,
|
||||
};
|
||||
// The mode parameter documents the call site; a per-mode defaults profile existed
|
||||
// until its last divergent ("missing") caller was removed and only invited drift.
|
||||
type ConfigMaterializationMode = "load" | "snapshot";
|
||||
|
||||
export function asResolvedSourceConfig(config: OpenClawConfig): ResolvedSourceConfig {
|
||||
return config as ResolvedSourceConfig;
|
||||
@@ -57,34 +33,25 @@ export function asRuntimeConfig(config: OpenClawConfig): RuntimeConfig {
|
||||
|
||||
export function materializeRuntimeConfig(
|
||||
config: OpenClawConfig,
|
||||
mode: ConfigMaterializationMode,
|
||||
_mode: ConfigMaterializationMode,
|
||||
options: {
|
||||
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
|
||||
loadManifestRegistry?: () => Pick<PluginManifestRegistry, "plugins"> | undefined;
|
||||
} = {},
|
||||
): RuntimeConfig {
|
||||
const profile = MATERIALIZATION_PROFILES[mode];
|
||||
let next = applyMessageDefaults(config);
|
||||
if (profile.includeLoggingDefaults) {
|
||||
next = applyLoggingDefaults(next);
|
||||
}
|
||||
next = applyLoggingDefaults(next);
|
||||
next = applySessionDefaults(next);
|
||||
next = applyAgentDefaults(next);
|
||||
next = applyCronDefaults(next);
|
||||
if (profile.includeContextPruningDefaults) {
|
||||
next = applyContextPruningDefaults(next, { manifestRegistry: options.manifestRegistry });
|
||||
}
|
||||
if (profile.includeCompactionDefaults) {
|
||||
next = applyCompactionDefaults(next);
|
||||
}
|
||||
next = applyContextPruningDefaults(next, { manifestRegistry: options.manifestRegistry });
|
||||
next = applyCompactionDefaults(next);
|
||||
next = applyModelDefaults(next, {
|
||||
manifestRegistry: options.manifestRegistry,
|
||||
loadManifestRegistry: options.loadManifestRegistry,
|
||||
});
|
||||
next = applyTalkConfigNormalization(next);
|
||||
if (profile.normalizePaths) {
|
||||
normalizeConfigPaths(next);
|
||||
}
|
||||
normalizeConfigPaths(next);
|
||||
normalizeExecSafeBinProfilesInConfig(next);
|
||||
return asRuntimeConfig(inheritLegacyDefaultAgentId(config, next));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user