Files
openclaw/src/config/mutate.ts
2026-03-30 00:17:23 +01:00

83 lines
2.5 KiB
TypeScript

import {
readConfigFileSnapshotForWrite,
resolveConfigSnapshotHash,
writeConfigFile,
type ConfigWriteOptions,
} from "./io.js";
import type { ConfigFileSnapshot, OpenClawConfig } from "./types.js";
export type ConfigMutationBase = "runtime" | "source";
export class ConfigMutationConflictError extends Error {
readonly currentHash: string | null;
constructor(message: string, params: { currentHash: string | null }) {
super(message);
this.name = "ConfigMutationConflictError";
this.currentHash = params.currentHash;
}
}
export type ConfigReplaceResult = {
path: string;
previousHash: string | null;
snapshot: ConfigFileSnapshot;
nextConfig: OpenClawConfig;
};
function assertBaseHashMatches(snapshot: ConfigFileSnapshot, expectedHash?: string): string | null {
const currentHash = resolveConfigSnapshotHash(snapshot) ?? null;
if (expectedHash !== undefined && expectedHash !== currentHash) {
throw new ConfigMutationConflictError("config changed since last load", {
currentHash,
});
}
return currentHash;
}
export async function replaceConfigFile(params: {
nextConfig: OpenClawConfig;
baseHash?: string;
writeOptions?: ConfigWriteOptions;
}): Promise<ConfigReplaceResult> {
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
const previousHash = assertBaseHashMatches(snapshot, params.baseHash);
await writeConfigFile(params.nextConfig, {
...writeOptions,
...params.writeOptions,
});
return {
path: snapshot.path,
previousHash,
snapshot,
nextConfig: params.nextConfig,
};
}
export async function mutateConfigFile<T = void>(params: {
base?: ConfigMutationBase;
baseHash?: string;
writeOptions?: ConfigWriteOptions;
mutate: (
draft: OpenClawConfig,
context: { snapshot: ConfigFileSnapshot; previousHash: string | null },
) => Promise<T | void> | T | void;
}): Promise<ConfigReplaceResult & { result: T | undefined }> {
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
const previousHash = assertBaseHashMatches(snapshot, params.baseHash);
const baseConfig = params.base === "runtime" ? snapshot.runtimeConfig : snapshot.sourceConfig;
const draft = structuredClone(baseConfig) as OpenClawConfig;
const result = (await params.mutate(draft, { snapshot, previousHash })) as T | undefined;
await writeConfigFile(draft, {
...writeOptions,
...params.writeOptions,
});
return {
path: snapshot.path,
previousHash,
snapshot,
nextConfig: draft,
result,
};
}