chore: drop Clawdis legacy references

This commit is contained in:
Peter Steinberger
2026-01-15 06:18:34 +00:00
parent 60748b1370
commit d59aab7fd3
31 changed files with 61 additions and 592 deletions

View File

@@ -59,11 +59,11 @@ export async function maybeMigrateLegacyGatewayService(
note(
legacyServices.map((svc) => `- ${svc.label} (${svc.platform}, ${svc.detail})`).join("\n"),
"Legacy Clawdis services detected",
"Legacy gateway services detected",
);
const migrate = await prompter.confirmSkipInNonInteractive({
message: "Migrate legacy Clawdis services to Clawdbot now?",
message: "Migrate legacy gateway services to Clawdbot now?",
initialValue: true,
});
if (!migrate) return;

View File

@@ -1,59 +1,6 @@
import os from "node:os";
import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDBOT,
createConfigIO,
migrateLegacyConfig,
readConfigFileSnapshot,
writeConfigFile,
} from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js";
import { resolveUserPath } from "../utils.js";
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
function resolveLegacyConfigPath(env: NodeJS.ProcessEnv): string {
const override = env.CLAWDIS_CONFIG_PATH?.trim();
if (override) return override;
return path.join(os.homedir(), ".clawdis", "clawdis.json");
}
function normalizeDefaultWorkspacePath(value: string | undefined): string | undefined {
if (!value) return value;
const resolved = resolveUserPath(value);
const home = os.homedir();
const next = [
["clawdis", "clawd"],
["clawdbot", "clawd"],
].reduce((acc, [from, to]) => {
const fromPrefix = path.join(home, from);
if (acc === fromPrefix) return path.join(home, to);
const withSep = `${fromPrefix}${path.sep}`;
if (acc.startsWith(withSep)) {
return path.join(home, to).concat(acc.slice(fromPrefix.length));
}
return acc;
}, resolved);
return next === resolved ? value : next;
}
export function replaceLegacyName(value: string | undefined): string | undefined {
if (!value) return value;
const replacedClawdis = value.replace(/clawdis/g, "clawdbot");
return replacedClawdis.replace(/clawd(?!bot)/g, "clawdbot");
}
export function replaceModernName(value: string | undefined): string | undefined {
if (!value) return value;
if (!value.includes("clawdbot")) return value;
return value.replace(/clawdbot/g, "clawdis");
}
export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
config: ClawdbotConfig;
changes: string[];
@@ -61,164 +8,6 @@ export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
const changes: string[] = [];
let next: ClawdbotConfig = cfg;
const defaults = cfg.agents?.defaults;
if (defaults) {
let updatedDefaults = defaults;
let defaultsChanged = false;
const updatedWorkspace = normalizeDefaultWorkspacePath(defaults.workspace);
if (updatedWorkspace && updatedWorkspace !== defaults.workspace) {
updatedDefaults = { ...updatedDefaults, workspace: updatedWorkspace };
defaultsChanged = true;
changes.push(`Updated agents.defaults.workspace → ${updatedWorkspace}`);
}
const sandbox = defaults.sandbox;
if (sandbox) {
let updatedSandbox = sandbox;
let sandboxChanged = false;
const updatedWorkspaceRoot = normalizeDefaultWorkspacePath(sandbox.workspaceRoot);
if (updatedWorkspaceRoot && updatedWorkspaceRoot !== sandbox.workspaceRoot) {
updatedSandbox = {
...updatedSandbox,
workspaceRoot: updatedWorkspaceRoot,
};
sandboxChanged = true;
changes.push(`Updated agents.defaults.sandbox.workspaceRoot → ${updatedWorkspaceRoot}`);
}
const dockerImage = sandbox.docker?.image;
const updatedDockerImage = replaceLegacyName(dockerImage);
if (updatedDockerImage && updatedDockerImage !== dockerImage) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
image: updatedDockerImage,
},
};
sandboxChanged = true;
changes.push(`Updated agents.defaults.sandbox.docker.image → ${updatedDockerImage}`);
}
const containerPrefix = sandbox.docker?.containerPrefix;
const updatedContainerPrefix = replaceLegacyName(containerPrefix);
if (updatedContainerPrefix && updatedContainerPrefix !== containerPrefix) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
containerPrefix: updatedContainerPrefix,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.defaults.sandbox.docker.containerPrefix → ${updatedContainerPrefix}`,
);
}
if (sandboxChanged) {
updatedDefaults = { ...updatedDefaults, sandbox: updatedSandbox };
defaultsChanged = true;
}
}
if (defaultsChanged) {
next = {
...next,
agents: {
...next.agents,
defaults: updatedDefaults,
},
};
}
}
const list = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
if (list.length > 0) {
let listChanged = false;
const nextList = list.map((agent) => {
let updatedAgent = agent;
let agentChanged = false;
const updatedWorkspace = normalizeDefaultWorkspacePath(agent.workspace);
if (updatedWorkspace && updatedWorkspace !== agent.workspace) {
updatedAgent = { ...updatedAgent, workspace: updatedWorkspace };
agentChanged = true;
changes.push(`Updated agents.list (id "${agent.id}") workspace → ${updatedWorkspace}`);
}
const sandbox = agent.sandbox;
if (sandbox) {
let updatedSandbox = sandbox;
let sandboxChanged = false;
const updatedWorkspaceRoot = normalizeDefaultWorkspacePath(sandbox.workspaceRoot);
if (updatedWorkspaceRoot && updatedWorkspaceRoot !== sandbox.workspaceRoot) {
updatedSandbox = {
...updatedSandbox,
workspaceRoot: updatedWorkspaceRoot,
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.workspaceRoot → ${updatedWorkspaceRoot}`,
);
}
const dockerImage = sandbox.docker?.image;
const updatedDockerImage = replaceLegacyName(dockerImage);
if (updatedDockerImage && updatedDockerImage !== dockerImage) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
image: updatedDockerImage,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.docker.image → ${updatedDockerImage}`,
);
}
const containerPrefix = sandbox.docker?.containerPrefix;
const updatedContainerPrefix = replaceLegacyName(containerPrefix);
if (updatedContainerPrefix && updatedContainerPrefix !== containerPrefix) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
containerPrefix: updatedContainerPrefix,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.docker.containerPrefix → ${updatedContainerPrefix}`,
);
}
if (sandboxChanged) {
updatedAgent = { ...updatedAgent, sandbox: updatedSandbox };
agentChanged = true;
}
}
if (agentChanged) listChanged = true;
return agentChanged ? updatedAgent : agent;
});
if (listChanged) {
next = {
...next,
agents: {
...next.agents,
list: nextList,
},
};
}
}
const legacyAckReaction = cfg.messages?.ackReaction?.trim();
const hasWhatsAppConfig = cfg.channels?.whatsapp !== undefined;
const hasWhatsAppAuth = hasAnyWhatsAppAuth(cfg);
@@ -259,83 +48,3 @@ export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
return { config: next, changes };
}
export async function maybeMigrateLegacyConfigFile(runtime: RuntimeEnv) {
const legacyConfigPath = resolveLegacyConfigPath(process.env);
if (legacyConfigPath === CONFIG_PATH_CLAWDBOT) return;
const legacyIo = createConfigIO({ configPath: legacyConfigPath });
const legacySnapshot = await legacyIo.readConfigFileSnapshot();
if (!legacySnapshot.exists) return;
const currentSnapshot = await readConfigFileSnapshot();
if (currentSnapshot.exists) {
note(
`Legacy config still exists at ${legacyConfigPath}. Current config at ${CONFIG_PATH_CLAWDBOT}.`,
"Legacy config",
);
return;
}
const gatewayMode =
typeof (legacySnapshot.parsed as ClawdbotConfig)?.gateway?.mode === "string"
? (legacySnapshot.parsed as ClawdbotConfig).gateway?.mode
: undefined;
const gatewayBind =
typeof (legacySnapshot.parsed as ClawdbotConfig)?.gateway?.bind === "string"
? (legacySnapshot.parsed as ClawdbotConfig).gateway?.bind
: undefined;
const parsed = legacySnapshot.parsed as Record<string, unknown>;
const parsedAgents =
parsed.agents && typeof parsed.agents === "object"
? (parsed.agents as Record<string, unknown>)
: undefined;
const parsedDefaults =
parsedAgents?.defaults && typeof parsedAgents.defaults === "object"
? (parsedAgents.defaults as Record<string, unknown>)
: undefined;
const parsedLegacyAgent =
parsed.agent && typeof parsed.agent === "object"
? (parsed.agent as Record<string, unknown>)
: undefined;
const defaultWorkspace =
typeof parsedDefaults?.workspace === "string" ? parsedDefaults.workspace : undefined;
const legacyWorkspace =
typeof parsedLegacyAgent?.workspace === "string" ? parsedLegacyAgent.workspace : undefined;
const agentWorkspace = defaultWorkspace ?? legacyWorkspace;
const workspaceLabel = defaultWorkspace
? "agents.defaults.workspace"
: legacyWorkspace
? "agent.workspace"
: "agents.defaults.workspace";
note(
[
`- File exists at ${legacyConfigPath}`,
gatewayMode ? `- gateway.mode: ${gatewayMode}` : undefined,
gatewayBind ? `- gateway.bind: ${gatewayBind}` : undefined,
agentWorkspace ? `- ${workspaceLabel}: ${agentWorkspace}` : undefined,
]
.filter(Boolean)
.join("\n"),
"Legacy Clawdis config detected",
);
let nextConfig = legacySnapshot.valid ? legacySnapshot.config : null;
const { config: migratedConfig, changes } = migrateLegacyConfig(legacySnapshot.parsed);
if (migratedConfig) {
nextConfig = migratedConfig;
} else if (!nextConfig) {
note(`Legacy config at ${legacyConfigPath} is invalid; skipping migration.`, "Legacy config");
return;
}
const normalized = normalizeLegacyConfigValues(nextConfig);
const mergedChanges = [...changes, ...normalized.changes];
if (mergedChanges.length > 0) {
note(mergedChanges.join("\n"), "Doctor changes");
}
await writeConfigFile(normalized.config);
runtime.log(`Migrated legacy config to ${CONFIG_PATH_CLAWDBOT}`);
}

View File

@@ -11,7 +11,6 @@ import type { ClawdbotConfig } from "../config/config.js";
import { runCommandWithTimeout, runExec } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js";
import { replaceModernName } from "./doctor-legacy-config.js";
import type { DoctorPrompter } from "./doctor-prompter.js";
type SandboxScriptInfo = {
@@ -165,18 +164,6 @@ async function handleMissingSandboxImage(
if (built) return;
const legacyImage = replaceModernName(params.image);
if (!legacyImage || legacyImage === params.image) return;
const legacyExists = await dockerImageExists(legacyImage);
if (!legacyExists) return;
const fallback = await prompter.confirmSkipInNonInteractive({
message: `Switch config to legacy image ${legacyImage}?`,
initialValue: false,
});
if (!fallback) return;
params.updateConfig(legacyImage);
}
export async function maybeRepairSandboxImages(

View File

@@ -9,7 +9,7 @@ export function noteWorkspaceStatus(cfg: ClawdbotConfig) {
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const legacyWorkspace = detectLegacyWorkspaceDirs({ workspaceDir });
if (legacyWorkspace.legacyDirs.length > 0) {
note(formatLegacyWorkspaceWarning(legacyWorkspace), "Legacy workspace");
note(formatLegacyWorkspaceWarning(legacyWorkspace), "Extra workspace");
}
const skillsReport = buildWorkspaceSkillStatus(workspaceDir, { config: cfg });

View File

@@ -20,10 +20,10 @@ describe("detectLegacyWorkspaceDirs", () => {
expect(detection.legacyDirs).toEqual([]);
});
it("flags ~/clawdis when it contains workspace markers", () => {
it("flags ~/clawdbot when it contains workspace markers", () => {
const home = "/home/user";
const workspaceDir = "/home/user/clawd";
const candidate = path.join(home, "clawdis");
const candidate = path.join(home, "clawdbot");
const agentsPath = path.join(candidate, "AGENTS.md");
const detection = detectLegacyWorkspaceDirs({

View File

@@ -65,7 +65,7 @@ export function detectLegacyWorkspaceDirs(params: {
const exists = params.exists ?? fs.existsSync;
const home = homedir();
const activeWorkspace = path.resolve(params.workspaceDir);
const candidates = [path.join(home, "clawdis"), path.join(home, "clawdbot")];
const candidates = [path.join(home, "clawdbot")];
const legacyDirs = candidates
.filter((candidate) => {
if (!exists(candidate)) return false;
@@ -79,9 +79,9 @@ export function detectLegacyWorkspaceDirs(params: {
export function formatLegacyWorkspaceWarning(detection: LegacyWorkspaceDetection): string {
return [
"Legacy workspace directories detected (may contain old agent files):",
"Extra workspace directories detected (may contain old agent files):",
...detection.legacyDirs.map((dir) => `- ${dir}`),
`Active workspace: ${detection.activeWorkspace}`,
"If unused, archive or move to Trash (e.g. trash ~/clawdis).",
"If unused, archive or move to Trash (e.g. trash ~/clawdbot).",
].join("\n");
}

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0,
});
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -317,86 +317,6 @@ vi.mock("./doctor-state-migrations.js", () => ({
}));
describe("doctor command", () => {
it("falls back to legacy sandbox image when missing", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
exists: true,
raw: "{}",
parsed: {
agents: {
defaults: {
sandbox: {
mode: "non-main",
docker: {
image: "clawdbot-sandbox-common:bookworm-slim",
},
},
},
},
},
valid: true,
config: {
agents: {
defaults: {
sandbox: {
mode: "non-main",
docker: {
image: "clawdbot-sandbox-common:bookworm-slim",
},
},
},
},
},
issues: [],
legacyIssues: [],
});
runExec.mockImplementation((command: string, args: string[]) => {
if (command !== "docker") {
return Promise.resolve({ stdout: "", stderr: "" });
}
if (args[0] === "version") {
return Promise.resolve({ stdout: "1", stderr: "" });
}
if (args[0] === "image" && args[1] === "inspect") {
const image = args[2];
if (image === "clawdbot-sandbox-common:bookworm-slim") {
return Promise.reject(new Error("missing"));
}
if (image === "clawdis-sandbox-common:bookworm-slim") {
return Promise.resolve({ stdout: "ok", stderr: "" });
}
}
return Promise.resolve({ stdout: "", stderr: "" });
});
confirm
.mockResolvedValueOnce(false) // skip gateway token prompt
.mockResolvedValueOnce(false) // skip build
.mockResolvedValueOnce(true); // accept legacy fallback
const { doctorCommand } = await import("./doctor.js");
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
await doctorCommand(runtime);
const written = writeConfigFile.mock.calls.at(-1)?.[0] as Record<string, unknown>;
const agents = written.agents as Record<string, unknown>;
const defaults = agents.defaults as Record<string, unknown>;
const sandbox = defaults.sandbox as Record<string, unknown>;
const docker = sandbox.docker as Record<string, unknown>;
expect(docker.image).toBe("clawdis-sandbox-common:bookworm-slim");
const defaultsCalls = runCommandWithTimeout.mock.calls.filter(
([args]) => Array.isArray(args) && args[0] === "/usr/bin/defaults",
);
expect(defaultsCalls.length).toBe(runCommandWithTimeout.mock.calls.length);
}, 20_000);
it("runs legacy state migrations in non-interactive mode without prompting", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0,
});
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -361,7 +361,7 @@ describe("doctor command", () => {
expect(written.routing).toBeUndefined();
});
it("migrates legacy Clawdis services", async () => {
it("migrates legacy gateway services", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
exists: true,
@@ -376,7 +376,7 @@ describe("doctor command", () => {
findLegacyGatewayServices.mockResolvedValueOnce([
{
platform: "darwin",
label: "com.clawdis.gateway",
label: "com.steipete.clawdbot.gateway",
detail: "loaded",
},
]);

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0,
});
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},

View File

@@ -26,7 +26,6 @@ import {
maybeScanExtraGatewayServices,
} from "./doctor-gateway-services.js";
import { noteSourceInstallIssues } from "./doctor-install.js";
import { maybeMigrateLegacyConfigFile } from "./doctor-legacy-config.js";
import { noteMacLaunchAgentOverrides } from "./doctor-platform-notes.js";
import { createDoctorPrompter, type DoctorOptions } from "./doctor-prompter.js";
import { maybeRepairSandboxImages, noteSandboxScopeWarnings } from "./doctor-sandbox.js";
@@ -76,8 +75,6 @@ export async function doctorCommand(
await maybeRepairUiProtocolFreshness(runtime, prompter);
noteSourceInstallIssues(root);
await maybeMigrateLegacyConfigFile(runtime);
const configResult = await loadAndMaybeMigrateDoctorConfig({
options,
confirm: (p) => prompter.confirm(p),

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0,
});
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -374,7 +374,7 @@ describe("doctor command", () => {
).toBe(true);
}, 10_000);
it("warns when legacy workspace directories exist", async () => {
it("warns when extra workspace directories exist", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
exists: true,
@@ -391,10 +391,14 @@ describe("doctor command", () => {
note.mockClear();
const homedirSpy = vi.spyOn(os, "homedir").mockReturnValue("/Users/steipete");
const realExists = fs.existsSync;
const legacyPath = path.join("/Users/steipete", "clawdis");
const legacyPath = path.join("/Users/steipete", "clawdbot");
const legacyAgentsPath = path.join(legacyPath, "AGENTS.md");
const existsSpy = vi.spyOn(fs, "existsSync").mockImplementation((value) => {
if (value === "/Users/steipete/clawdis" || value === legacyPath || value === legacyAgentsPath)
if (
value === "/Users/steipete/clawdbot" ||
value === legacyPath ||
value === legacyAgentsPath
)
return true;
return realExists(value as never);
});
@@ -411,9 +415,9 @@ describe("doctor command", () => {
expect(
note.mock.calls.some(
([message, title]) =>
title === "Legacy workspace" &&
title === "Extra workspace" &&
typeof message === "string" &&
message.includes("clawdis"),
message.includes("clawdbot"),
),
).toBe(true);

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0,
});
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: false,
raw: null,
parsed: {},