diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index 09d5eef8e39..511141e366d 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -429,6 +429,7 @@ async function maybeMigrateLegacyConfig(): Promise { const legacyCandidates = [ path.join(home, ".clawdbot", "clawdbot.json"), path.join(home, ".moldbot", "moldbot.json"), + path.join(home, ".moltbot", "moltbot.json"), ]; let legacyPath: string | null = null; diff --git a/src/config/paths.test.ts b/src/config/paths.test.ts index cc764039a03..22b278d8171 100644 --- a/src/config/paths.test.ts +++ b/src/config/paths.test.ts @@ -78,12 +78,19 @@ describe("state + config path candidates", () => { path.join(resolvedHome, ".openclaw", "openclaw.json"), path.join(resolvedHome, ".openclaw", "clawdbot.json"), path.join(resolvedHome, ".openclaw", "moldbot.json"), + path.join(resolvedHome, ".openclaw", "moltbot.json"), path.join(resolvedHome, ".clawdbot", "openclaw.json"), path.join(resolvedHome, ".clawdbot", "clawdbot.json"), path.join(resolvedHome, ".clawdbot", "moldbot.json"), + path.join(resolvedHome, ".clawdbot", "moltbot.json"), path.join(resolvedHome, ".moldbot", "openclaw.json"), path.join(resolvedHome, ".moldbot", "clawdbot.json"), path.join(resolvedHome, ".moldbot", "moldbot.json"), + path.join(resolvedHome, ".moldbot", "moltbot.json"), + path.join(resolvedHome, ".moltbot", "openclaw.json"), + path.join(resolvedHome, ".moltbot", "clawdbot.json"), + path.join(resolvedHome, ".moltbot", "moldbot.json"), + path.join(resolvedHome, ".moltbot", "moltbot.json"), ]; expect(candidates).toEqual(expected); }); diff --git a/src/config/paths.ts b/src/config/paths.ts index 6e9a35597b1..18f8ab92663 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -17,10 +17,11 @@ export function resolveIsNixMode(env: NodeJS.ProcessEnv = process.env): boolean export const isNixMode = resolveIsNixMode(); -const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moldbot"] as const; +// Support historical (and occasionally misspelled) legacy state dirs. +const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moldbot", ".moltbot"] as const; const NEW_STATE_DIRNAME = ".openclaw"; const CONFIG_FILENAME = "openclaw.json"; -const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moldbot.json"] as const; +const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moldbot.json", "moltbot.json"] as const; function resolveDefaultHomeDir(): string { return resolveRequiredHomeDir(process.env, os.homedir); diff --git a/src/infra/state-migrations.state-dir.test.ts b/src/infra/state-migrations.state-dir.test.ts new file mode 100644 index 00000000000..8c46fe398e0 --- /dev/null +++ b/src/infra/state-migrations.state-dir.test.ts @@ -0,0 +1,52 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + autoMigrateLegacyStateDir, + resetAutoMigrateLegacyStateDirForTest, +} from "./state-migrations.js"; + +let tempRoot: string | null = null; + +async function makeTempRoot() { + const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-state-dir-")); + tempRoot = root; + return root; +} + +afterEach(async () => { + resetAutoMigrateLegacyStateDirForTest(); + if (!tempRoot) { + return; + } + await fs.promises.rm(tempRoot, { recursive: true, force: true }); + tempRoot = null; +}); + +describe("legacy state dir auto-migration", () => { + it("follows legacy symlink when it points at another legacy dir (clawdbot -> moltbot)", async () => { + const root = await makeTempRoot(); + const legacySymlink = path.join(root, ".clawdbot"); + const legacyDir = path.join(root, ".moltbot"); + + fs.mkdirSync(legacyDir, { recursive: true }); + fs.writeFileSync(path.join(legacyDir, "marker.txt"), "ok", "utf-8"); + + const dirLinkType = process.platform === "win32" ? "junction" : "dir"; + fs.symlinkSync(legacyDir, legacySymlink, dirLinkType); + + const result = await autoMigrateLegacyStateDir({ + env: {} as NodeJS.ProcessEnv, + homedir: () => root, + }); + + expect(result.migrated).toBe(true); + expect(result.warnings).toEqual([]); + + const targetMarker = path.join(root, ".openclaw", "marker.txt"); + expect(fs.readFileSync(targetMarker, "utf-8")).toBe("ok"); + expect(fs.readFileSync(path.join(root, ".moltbot", "marker.txt"), "utf-8")).toBe("ok"); + expect(fs.readFileSync(path.join(root, ".clawdbot", "marker.txt"), "utf-8")).toBe("ok"); + }); +});