refactor: rename to openclaw

This commit is contained in:
Peter Steinberger
2026-01-30 03:15:10 +01:00
parent 4583f88626
commit 9a7160786a
2357 changed files with 16688 additions and 16788 deletions
+4 -4
View File
@@ -3,7 +3,7 @@ import path from "node:path";
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
import { resolveUserPath } from "../utils.js";
import { resolveStateDir } from "./paths.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
export type DuplicateAgentDir = {
agentDir: string;
@@ -28,7 +28,7 @@ function canonicalizeAgentDir(agentDir: string): string {
return resolved;
}
function collectReferencedAgentIds(cfg: MoltbotConfig): string[] {
function collectReferencedAgentIds(cfg: OpenClawConfig): string[] {
const ids = new Set<string>();
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents?.list : [];
@@ -54,7 +54,7 @@ function collectReferencedAgentIds(cfg: MoltbotConfig): string[] {
}
function resolveEffectiveAgentDir(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
agentId: string,
deps?: { env?: NodeJS.ProcessEnv; homedir?: () => string },
): string {
@@ -69,7 +69,7 @@ function resolveEffectiveAgentDir(
}
export function findDuplicateAgentDirs(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
deps?: { env?: NodeJS.ProcessEnv; homedir?: () => string },
): DuplicateAgentDir[] {
const byDir = new Map<string, { agentDir: string; agentIds: string[] }>();
+3 -3
View File
@@ -1,9 +1,9 @@
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
export const DEFAULT_AGENT_MAX_CONCURRENT = 4;
export const DEFAULT_SUBAGENT_MAX_CONCURRENT = 8;
export function resolveAgentMaxConcurrent(cfg?: MoltbotConfig): number {
export function resolveAgentMaxConcurrent(cfg?: OpenClawConfig): number {
const raw = cfg?.agents?.defaults?.maxConcurrent;
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.max(1, Math.floor(raw));
@@ -11,7 +11,7 @@ export function resolveAgentMaxConcurrent(cfg?: MoltbotConfig): number {
return DEFAULT_AGENT_MAX_CONCURRENT;
}
export function resolveSubagentMaxConcurrent(cfg?: MoltbotConfig): number {
export function resolveSubagentMaxConcurrent(cfg?: OpenClawConfig): number {
const raw = cfg?.agents?.defaults?.subagents?.maxConcurrent;
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.max(1, Math.floor(raw));
+5 -5
View File
@@ -3,7 +3,7 @@ import type { ChannelPlugin } from "../channels/plugins/types.js";
import type { PluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { resolveChannelCapabilities } from "./channel-capabilities.js";
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
describe("resolveChannelCapabilities", () => {
beforeEach(() => {
@@ -32,7 +32,7 @@ describe("resolveChannelCapabilities", () => {
},
},
},
} satisfies Partial<MoltbotConfig>;
} satisfies Partial<OpenClawConfig>;
expect(
resolveChannelCapabilities({
@@ -53,7 +53,7 @@ describe("resolveChannelCapabilities", () => {
},
},
},
} satisfies Partial<MoltbotConfig>;
} satisfies Partial<OpenClawConfig>;
expect(
resolveChannelCapabilities({
@@ -73,7 +73,7 @@ describe("resolveChannelCapabilities", () => {
},
},
},
} satisfies Partial<MoltbotConfig>;
} satisfies Partial<OpenClawConfig>;
expect(
resolveChannelCapabilities({
@@ -96,7 +96,7 @@ describe("resolveChannelCapabilities", () => {
);
const cfg = {
channels: { msteams: { capabilities: [" polls ", ""] } },
} satisfies Partial<MoltbotConfig>;
} satisfies Partial<OpenClawConfig>;
expect(
resolveChannelCapabilities({
+2 -2
View File
@@ -1,6 +1,6 @@
import { normalizeChannelId } from "../channels/plugins/index.js";
import { normalizeAccountId } from "../routing/session-key.js";
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
import type { TelegramCapabilitiesConfig } from "./types.telegram.js";
type CapabilitiesConfig = TelegramCapabilitiesConfig;
@@ -45,7 +45,7 @@ function resolveAccountCapabilities(params: {
}
export function resolveChannelCapabilities(params: {
cfg?: Partial<MoltbotConfig>;
cfg?: Partial<OpenClawConfig>;
channel?: string | null;
accountId?: string | null;
}): string[] | undefined {
@@ -43,10 +43,10 @@ describe("agent concurrency defaults", () => {
it("injects defaults on load", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({}, null, 2),
"utf-8",
);
+3 -3
View File
@@ -3,17 +3,17 @@ import fs from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { withTempHome } from "./test-helpers.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
describe("config backup rotation", () => {
it("keeps a 5-deep backup ring for config writes", async () => {
await withTempHome(async () => {
const { resolveConfigPath, writeConfigFile } = await import("./config.js");
const configPath = resolveConfigPath();
const buildConfig = (version: number): MoltbotConfig =>
const buildConfig = (version: number): OpenClawConfig =>
({
agents: { list: [{ id: `v${version}` }] },
}) as MoltbotConfig;
}) as OpenClawConfig;
for (let version = 0; version <= 6; version += 1) {
await writeConfigFile(buildConfig(version));
@@ -6,10 +6,10 @@ import { withTempHome } from "./test-helpers.js";
describe("config compaction settings", () => {
it("preserves memory flush config values", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -48,10 +48,10 @@ describe("config compaction settings", () => {
it("defaults compaction mode to safeguard", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
+6 -6
View File
@@ -16,10 +16,10 @@ describe("config discord", () => {
it("loads discord guild map + dm group settings", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
channels: {
@@ -29,7 +29,7 @@ describe("config discord", () => {
enabled: true,
allowFrom: ["steipete"],
groupEnabled: true,
groupChannels: ["clawd-dm"],
groupChannels: ["openclaw-dm"],
},
actions: {
emojiUploads: true,
@@ -38,7 +38,7 @@ describe("config discord", () => {
},
guilds: {
"123": {
slug: "friends-of-clawd",
slug: "friends-of-openclaw",
requireMention: false,
users: ["steipete"],
channels: {
@@ -61,11 +61,11 @@ describe("config discord", () => {
expect(cfg.channels?.discord?.enabled).toBe(true);
expect(cfg.channels?.discord?.dm?.groupEnabled).toBe(true);
expect(cfg.channels?.discord?.dm?.groupChannels).toEqual(["clawd-dm"]);
expect(cfg.channels?.discord?.dm?.groupChannels).toEqual(["openclaw-dm"]);
expect(cfg.channels?.discord?.actions?.emojiUploads).toBe(true);
expect(cfg.channels?.discord?.actions?.stickerUploads).toBe(false);
expect(cfg.channels?.discord?.actions?.channels).toBe(true);
expect(cfg.channels?.discord?.guilds?.["123"]?.slug).toBe("friends-of-clawd");
expect(cfg.channels?.discord?.guilds?.["123"]?.slug).toBe("friends-of-openclaw");
expect(cfg.channels?.discord?.guilds?.["123"]?.channels?.general?.allow).toBe(true);
});
});
+6 -6
View File
@@ -6,10 +6,10 @@ import { withEnvOverride, withTempHome } from "./test-helpers.js";
describe("config env vars", () => {
it("applies env vars from env block when missing", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
env: { vars: { OPENROUTER_API_KEY: "config-key" } },
@@ -30,10 +30,10 @@ describe("config env vars", () => {
it("does not override existing env vars", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
env: { vars: { OPENROUTER_API_KEY: "config-key" } },
@@ -54,10 +54,10 @@ describe("config env vars", () => {
it("applies env vars from env.vars when missing", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
env: { vars: { GROQ_API_KEY: "gsk-config" } },
+4 -4
View File
@@ -8,10 +8,10 @@ import { withTempHome } from "./test-helpers.js";
describe("identity avatar validation", () => {
it("accepts workspace-relative avatar paths", async () => {
await withTempHome(async (home) => {
const workspace = path.join(home, "clawd");
const workspace = path.join(home, "openclaw");
const res = validateConfigObject({
agents: {
list: [{ id: "main", workspace, identity: { avatar: "avatars/clawd.png" } }],
list: [{ id: "main", workspace, identity: { avatar: "avatars/openclaw.png" } }],
},
});
expect(res.ok).toBe(true);
@@ -20,7 +20,7 @@ describe("identity avatar validation", () => {
it("accepts http(s) and data avatars", async () => {
await withTempHome(async (home) => {
const workspace = path.join(home, "clawd");
const workspace = path.join(home, "openclaw");
const httpRes = validateConfigObject({
agents: {
list: [{ id: "main", workspace, identity: { avatar: "https://example.com/avatar.png" } }],
@@ -39,7 +39,7 @@ describe("identity avatar validation", () => {
it("rejects avatar paths outside workspace", async () => {
await withTempHome(async (home) => {
const workspace = path.join(home, "clawd");
const workspace = path.join(home, "openclaw");
const res = validateConfigObject({
agents: {
list: [{ id: "main", workspace, identity: { avatar: "../oops.png" } }],
+22 -22
View File
@@ -17,10 +17,10 @@ describe("config identity defaults", () => {
it("does not derive mentionPatterns when identity is set", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -54,10 +54,10 @@ describe("config identity defaults", () => {
it("defaults ackReactionScope without setting ackReaction", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -91,10 +91,10 @@ describe("config identity defaults", () => {
it("keeps ackReaction unset when identity is missing", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
messages: {},
@@ -116,10 +116,10 @@ describe("config identity defaults", () => {
it("does not override explicit values", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -131,7 +131,7 @@ describe("config identity defaults", () => {
theme: "space lobster",
emoji: "🦞",
},
groupChat: { mentionPatterns: ["@clawd"] },
groupChat: { mentionPatterns: ["@openclaw"] },
},
],
},
@@ -150,20 +150,20 @@ describe("config identity defaults", () => {
const cfg = loadConfig();
expect(cfg.messages?.responsePrefix).toBe("✅");
expect(cfg.agents?.list?.[0]?.groupChat?.mentionPatterns).toEqual(["@clawd"]);
expect(cfg.agents?.list?.[0]?.groupChat?.mentionPatterns).toEqual(["@openclaw"]);
});
});
it("supports provider textChunkLimit config", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
messages: {
messagePrefix: "[moltbot]",
messagePrefix: "[openclaw]",
responsePrefix: "🦞",
},
channels: {
@@ -202,10 +202,10 @@ describe("config identity defaults", () => {
it("accepts blank model provider apiKey values", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
models: {
@@ -251,10 +251,10 @@ describe("config identity defaults", () => {
it("respects empty responsePrefix to disable identity defaults", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -287,10 +287,10 @@ describe("config identity defaults", () => {
it("does not synthesize agent list/session when absent", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
messages: {},
@@ -316,10 +316,10 @@ describe("config identity defaults", () => {
it("does not derive responsePrefix from identity emoji", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
@@ -327,7 +327,7 @@ describe("config identity defaults", () => {
{
id: "main",
identity: {
name: "Clawd",
name: "OpenClaw",
theme: "space lobster",
emoji: "🦞",
},
@@ -176,7 +176,7 @@ describe("legacy config detection", () => {
});
it("flags legacy config in snapshot", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -202,7 +202,7 @@ describe("legacy config detection", () => {
});
it("does not auto-migrate claude-cli auth profile mode on load", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -234,7 +234,7 @@ describe("legacy config detection", () => {
});
it("flags legacy provider sections in snapshot", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -260,7 +260,7 @@ describe("legacy config detection", () => {
});
it("flags routing.allowFrom in snapshot", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -286,7 +286,7 @@ describe("legacy config detection", () => {
});
it("rejects bindings[].match.provider on load", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -316,7 +316,7 @@ describe("legacy config detection", () => {
});
it("rejects bindings[].match.accountID on load", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -346,7 +346,7 @@ describe("legacy config detection", () => {
});
it("rejects session.sendPolicy.rules[].match.provider on load", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -380,7 +380,7 @@ describe("legacy config detection", () => {
});
it("rejects messages.queue.byProvider on load", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdbot", "moltbot.json");
const configPath = path.join(home, ".openclaw", "openclaw.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,
@@ -89,12 +89,12 @@ describe("legacy config detection", () => {
vi.resetModules();
const { migrateLegacyConfig } = await import("./config.js");
const res = migrateLegacyConfig({
routing: { groupChat: { mentionPatterns: ["@clawd"] } },
routing: { groupChat: { mentionPatterns: ["@openclaw"] } },
});
expect(res.changes).toContain(
"Moved routing.groupChat.mentionPatterns → messages.groupChat.mentionPatterns.",
);
expect(res.config?.messages?.groupChat?.mentionPatterns).toEqual(["@clawd"]);
expect(res.config?.messages?.groupChat?.mentionPatterns).toEqual(["@openclaw"]);
expect(res.config?.routing?.groupChat?.mentionPatterns).toBeUndefined();
});
it("migrates routing agentToAgent/queue/transcribeAudio to tools/messages/media", async () => {
@@ -198,7 +198,7 @@ describe("legacy config detection", () => {
list: [
{
id: "work",
workspace: "~/clawd-work",
workspace: "~/openclaw-work",
tools: {
elevated: {
enabled: false,
@@ -8,7 +8,7 @@ describe("multi-agent agentDir validation", () => {
it("rejects shared agents.list agentDir", async () => {
vi.resetModules();
const { validateConfigObject } = await import("./config.js");
const shared = path.join(tmpdir(), "moltbot-shared-agentdir");
const shared = path.join(tmpdir(), "openclaw-shared-agentdir");
const res = validateConfigObject({
agents: {
list: [
@@ -26,16 +26,16 @@ describe("multi-agent agentDir validation", () => {
it("throws on shared agentDir during loadConfig()", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
agents: {
list: [
{ id: "a", agentDir: "~/.clawdbot/agents/shared/agent" },
{ id: "b", agentDir: "~/.clawdbot/agents/shared/agent" },
{ id: "a", agentDir: "~/.openclaw/agents/shared/agent" },
{ id: "b", agentDir: "~/.openclaw/agents/shared/agent" },
],
},
bindings: [{ agentId: "a", match: { channel: "telegram" } }],
@@ -5,29 +5,29 @@ import { withEnvOverride, withTempHome } from "./test-helpers.js";
describe("Nix integration (U3, U5, U9)", () => {
describe("U3: isNixMode env var detection", () => {
it("isNixMode is false when CLAWDBOT_NIX_MODE is not set", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: undefined }, async () => {
it("isNixMode is false when OPENCLAW_NIX_MODE is not set", async () => {
await withEnvOverride({ OPENCLAW_NIX_MODE: undefined }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is false when CLAWDBOT_NIX_MODE is empty", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "" }, async () => {
it("isNixMode is false when OPENCLAW_NIX_MODE is empty", async () => {
await withEnvOverride({ OPENCLAW_NIX_MODE: "" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is false when CLAWDBOT_NIX_MODE is not '1'", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "true" }, async () => {
it("isNixMode is false when OPENCLAW_NIX_MODE is not '1'", async () => {
await withEnvOverride({ OPENCLAW_NIX_MODE: "true" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is true when CLAWDBOT_NIX_MODE=1", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "1" }, async () => {
it("isNixMode is true when OPENCLAW_NIX_MODE=1", async () => {
await withEnvOverride({ OPENCLAW_NIX_MODE: "1" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(true);
});
@@ -35,97 +35,55 @@ describe("Nix integration (U3, U5, U9)", () => {
});
describe("U5: CONFIG_PATH and STATE_DIR env var overrides", () => {
it("STATE_DIR defaults to ~/.clawdbot when env not set", async () => {
await withEnvOverride(
{ MOLTBOT_STATE_DIR: undefined, CLAWDBOT_STATE_DIR: undefined },
async () => {
const { STATE_DIR } = await import("./config.js");
expect(STATE_DIR).toMatch(/\.clawdbot$/);
},
);
it("STATE_DIR defaults to ~/.openclaw when env not set", async () => {
await withEnvOverride({ OPENCLAW_STATE_DIR: undefined }, async () => {
const { STATE_DIR } = await import("./config.js");
expect(STATE_DIR).toMatch(/\.openclaw$/);
});
});
it("STATE_DIR respects CLAWDBOT_STATE_DIR override", async () => {
await withEnvOverride(
{ MOLTBOT_STATE_DIR: undefined, CLAWDBOT_STATE_DIR: "/custom/state/dir" },
async () => {
const { STATE_DIR } = await import("./config.js");
expect(STATE_DIR).toBe(path.resolve("/custom/state/dir"));
},
);
it("STATE_DIR respects OPENCLAW_STATE_DIR override", async () => {
await withEnvOverride({ OPENCLAW_STATE_DIR: "/custom/state/dir" }, async () => {
const { STATE_DIR } = await import("./config.js");
expect(STATE_DIR).toBe(path.resolve("/custom/state/dir"));
});
});
it("STATE_DIR prefers MOLTBOT_STATE_DIR over legacy override", async () => {
it("CONFIG_PATH defaults to ~/.openclaw/openclaw.json when env not set", async () => {
await withEnvOverride(
{ MOLTBOT_STATE_DIR: "/custom/new", CLAWDBOT_STATE_DIR: "/custom/legacy" },
async () => {
const { STATE_DIR } = await import("./config.js");
expect(STATE_DIR).toBe(path.resolve("/custom/new"));
},
);
});
it("CONFIG_PATH defaults to ~/.clawdbot/moltbot.json when env not set", async () => {
await withEnvOverride(
{
MOLTBOT_CONFIG_PATH: undefined,
MOLTBOT_STATE_DIR: undefined,
CLAWDBOT_CONFIG_PATH: undefined,
CLAWDBOT_STATE_DIR: undefined,
},
{ OPENCLAW_CONFIG_PATH: undefined, OPENCLAW_STATE_DIR: undefined },
async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toMatch(/\.clawdbot[\\/]moltbot\.json$/);
expect(CONFIG_PATH).toMatch(/\.openclaw[\\/]openclaw\.json$/);
},
);
});
it("CONFIG_PATH respects CLAWDBOT_CONFIG_PATH override", async () => {
await withEnvOverride(
{ MOLTBOT_CONFIG_PATH: undefined, CLAWDBOT_CONFIG_PATH: "/nix/store/abc/moltbot.json" },
async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.resolve("/nix/store/abc/moltbot.json"));
},
);
it("CONFIG_PATH respects OPENCLAW_CONFIG_PATH override", async () => {
await withEnvOverride({ OPENCLAW_CONFIG_PATH: "/nix/store/abc/openclaw.json" }, async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.resolve("/nix/store/abc/openclaw.json"));
});
});
it("CONFIG_PATH prefers MOLTBOT_CONFIG_PATH over legacy override", async () => {
await withEnvOverride(
{
MOLTBOT_CONFIG_PATH: "/nix/store/new/moltbot.json",
CLAWDBOT_CONFIG_PATH: "/nix/store/legacy/moltbot.json",
},
async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.resolve("/nix/store/new/moltbot.json"));
},
);
});
it("CONFIG_PATH expands ~ in CLAWDBOT_CONFIG_PATH override", async () => {
it("CONFIG_PATH expands ~ in OPENCLAW_CONFIG_PATH override", async () => {
await withTempHome(async (home) => {
await withEnvOverride(
{ MOLTBOT_CONFIG_PATH: undefined, CLAWDBOT_CONFIG_PATH: "~/.clawdbot/custom.json" },
async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.join(home, ".clawdbot", "custom.json"));
},
);
await withEnvOverride({ OPENCLAW_CONFIG_PATH: "~/.openclaw/custom.json" }, async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.join(home, ".openclaw", "custom.json"));
});
});
});
it("CONFIG_PATH uses STATE_DIR when only state dir is overridden", async () => {
await withEnvOverride(
{
MOLTBOT_CONFIG_PATH: undefined,
MOLTBOT_STATE_DIR: undefined,
CLAWDBOT_CONFIG_PATH: undefined,
CLAWDBOT_STATE_DIR: "/custom/state",
OPENCLAW_CONFIG_PATH: undefined,
OPENCLAW_STATE_DIR: "/custom/state",
},
async () => {
const { CONFIG_PATH } = await import("./config.js");
expect(CONFIG_PATH).toBe(path.join(path.resolve("/custom/state"), "moltbot.json"));
expect(CONFIG_PATH).toBe(path.join(path.resolve("/custom/state"), "openclaw.json"));
},
);
});
@@ -134,7 +92,7 @@ describe("Nix integration (U3, U5, U9)", () => {
describe("U5b: tilde expansion for config paths", () => {
it("expands ~ in common path-ish config fields", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
const pluginDir = path.join(home, "plugins", "demo-plugin");
await fs.mkdir(pluginDir, { recursive: true });
@@ -144,7 +102,7 @@ describe("Nix integration (U3, U5, U9)", () => {
"utf-8",
);
await fs.writeFile(
path.join(pluginDir, "moltbot.plugin.json"),
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "demo-plugin",
@@ -156,7 +114,7 @@ describe("Nix integration (U3, U5, U9)", () => {
"utf-8",
);
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
plugins: {
@@ -170,7 +128,7 @@ describe("Nix integration (U3, U5, U9)", () => {
{
id: "main",
workspace: "~/ws-agent",
agentDir: "~/.clawdbot/agents/main",
agentDir: "~/.openclaw/agents/main",
sandbox: { workspaceRoot: "~/sandbox-root" },
},
],
@@ -179,7 +137,7 @@ describe("Nix integration (U3, U5, U9)", () => {
whatsapp: {
accounts: {
personal: {
authDir: "~/.clawdbot/credentials/wa-personal",
authDir: "~/.openclaw/credentials/wa-personal",
},
},
},
@@ -199,11 +157,11 @@ describe("Nix integration (U3, U5, U9)", () => {
expect(cfg.agents?.defaults?.workspace).toBe(path.join(home, "ws-default"));
expect(cfg.agents?.list?.[0]?.workspace).toBe(path.join(home, "ws-agent"));
expect(cfg.agents?.list?.[0]?.agentDir).toBe(
path.join(home, ".clawdbot", "agents", "main"),
path.join(home, ".openclaw", "agents", "main"),
);
expect(cfg.agents?.list?.[0]?.sandbox?.workspaceRoot).toBe(path.join(home, "sandbox-root"));
expect(cfg.channels?.whatsapp?.accounts?.personal?.authDir).toBe(
path.join(home, ".clawdbot", "credentials", "wa-personal"),
path.join(home, ".openclaw", "credentials", "wa-personal"),
);
});
});
@@ -211,21 +169,21 @@ describe("Nix integration (U3, U5, U9)", () => {
describe("U6: gateway port resolution", () => {
it("uses default when env and config are unset", async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: undefined }, async () => {
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: undefined }, async () => {
const { DEFAULT_GATEWAY_PORT, resolveGatewayPort } = await import("./config.js");
expect(resolveGatewayPort({})).toBe(DEFAULT_GATEWAY_PORT);
});
});
it("prefers CLAWDBOT_GATEWAY_PORT over config", async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: "19001" }, async () => {
it("prefers OPENCLAW_GATEWAY_PORT over config", async () => {
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: "19001" }, async () => {
const { resolveGatewayPort } = await import("./config.js");
expect(resolveGatewayPort({ gateway: { port: 19002 } })).toBe(19001);
});
});
it("falls back to config when env is invalid", async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: "nope" }, async () => {
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: "nope" }, async () => {
const { resolveGatewayPort } = await import("./config.js");
expect(resolveGatewayPort({ gateway: { port: 19003 } })).toBe(19003);
});
@@ -235,10 +193,10 @@ describe("Nix integration (U3, U5, U9)", () => {
describe("U9: telegram.tokenFile schema validation", () => {
it("accepts config with only botToken", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({
channels: { telegram: { botToken: "123:ABC" } },
}),
@@ -255,10 +213,10 @@ describe("Nix integration (U3, U5, U9)", () => {
it("accepts config with only tokenFile", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({
channels: { telegram: { tokenFile: "/run/agenix/telegram-token" } },
}),
@@ -275,10 +233,10 @@ describe("Nix integration (U3, U5, U9)", () => {
it("accepts config with both botToken and tokenFile", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({
channels: {
telegram: {
+8 -8
View File
@@ -25,7 +25,7 @@ async function writePluginFixture(params: {
manifest.channels = params.channels;
}
await fs.writeFile(
path.join(params.dir, "moltbot.plugin.json"),
path.join(params.dir, "openclaw.plugin.json"),
JSON.stringify(manifest, null, 2),
"utf-8",
);
@@ -34,7 +34,7 @@ async function writePluginFixture(params: {
describe("config plugin validation", () => {
it("rejects missing plugin load paths", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
vi.resetModules();
const { validateConfigObjectWithPlugins } = await import("./config.js");
const missingPath = path.join(home, "missing-plugin");
@@ -55,7 +55,7 @@ describe("config plugin validation", () => {
it("rejects missing plugin ids in entries", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
vi.resetModules();
const { validateConfigObjectWithPlugins } = await import("./config.js");
const res = validateConfigObjectWithPlugins({
@@ -74,7 +74,7 @@ describe("config plugin validation", () => {
it("rejects missing plugin ids in allow/deny/slots", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
vi.resetModules();
const { validateConfigObjectWithPlugins } = await import("./config.js");
const res = validateConfigObjectWithPlugins({
@@ -101,7 +101,7 @@ describe("config plugin validation", () => {
it("surfaces plugin config diagnostics", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
const pluginDir = path.join(home, "bad-plugin");
await writePluginFixture({
dir: pluginDir,
@@ -140,7 +140,7 @@ describe("config plugin validation", () => {
it("accepts known plugin ids", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
vi.resetModules();
const { validateConfigObjectWithPlugins } = await import("./config.js");
const res = validateConfigObjectWithPlugins({
@@ -153,7 +153,7 @@ describe("config plugin validation", () => {
it("accepts plugin heartbeat targets", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
const pluginDir = path.join(home, "bluebubbles-plugin");
await writePluginFixture({
dir: pluginDir,
@@ -174,7 +174,7 @@ describe("config plugin validation", () => {
it("rejects unknown heartbeat targets", async () => {
await withTempHome(async (home) => {
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
process.env.OPENCLAW_STATE_DIR = path.join(home, ".openclaw");
vi.resetModules();
const { validateConfigObjectWithPlugins } = await import("./config.js");
const res = validateConfigObjectWithPlugins({
@@ -16,10 +16,10 @@ describe("config strict validation", () => {
it("flags legacy config entries without auto-migrating", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({
agents: { list: [{ id: "pi" }] },
routing: { allowFrom: ["+15555550123"] },
+8 -8
View File
@@ -10,10 +10,10 @@ describe("config pruning defaults", () => {
process.env.ANTHROPIC_API_KEY = "";
process.env.ANTHROPIC_OAUTH_TOKEN = "";
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({ agents: { defaults: {} } }, null, 2),
"utf-8",
);
@@ -38,10 +38,10 @@ describe("config pruning defaults", () => {
it("enables cache-ttl pruning + 1h heartbeat for Anthropic OAuth", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
auth: {
@@ -69,10 +69,10 @@ describe("config pruning defaults", () => {
it("enables cache-ttl pruning + 1h cache TTL for Anthropic API keys", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify(
{
auth: {
@@ -107,10 +107,10 @@ describe("config pruning defaults", () => {
it("does not override explicit contextPruning mode", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdbot");
const configDir = path.join(home, ".openclaw");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "moltbot.json"),
path.join(configDir, "openclaw.json"),
JSON.stringify({ agents: { defaults: { contextPruning: { mode: "off" } } } }, null, 2),
"utf-8",
);
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { MoltbotSchema } from "./zod-schema.js";
import { OpenClawSchema } from "./zod-schema.js";
describe("skills entries config schema", () => {
it("accepts custom fields under config", () => {
const res = MoltbotSchema.safeParse({
const res = OpenClawSchema.safeParse({
skills: {
entries: {
"custom-skill": {
@@ -22,7 +22,7 @@ describe("skills entries config schema", () => {
});
it("rejects unknown top-level fields", () => {
const res = MoltbotSchema.safeParse({
const res = OpenClawSchema.safeParse({
skills: {
entries: {
"custom-skill": {
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { MoltbotSchema } from "./zod-schema.js";
import { OpenClawSchema } from "./zod-schema.js";
describe("telegram custom commands schema", () => {
it("normalizes custom commands", () => {
const res = MoltbotSchema.safeParse({
const res = OpenClawSchema.safeParse({
channels: {
telegram: {
customCommands: [{ command: "/Backup", description: " Git backup " }],
@@ -21,7 +21,7 @@ describe("telegram custom commands schema", () => {
});
it("rejects custom commands with invalid names", () => {
const res = MoltbotSchema.safeParse({
const res = OpenClawSchema.safeParse({
channels: {
telegram: {
customCommands: [{ command: "Bad-Name", description: "Override status" }],
+1 -1
View File
@@ -11,4 +11,4 @@ export * from "./paths.js";
export * from "./runtime-overrides.js";
export * from "./types.js";
export { validateConfigObject, validateConfigObjectWithPlugins } from "./validation.js";
export { MoltbotSchema } from "./zod-schema.js";
export { OpenClawSchema } from "./zod-schema.js";
+12 -12
View File
@@ -1,7 +1,7 @@
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { parseModelRef } from "../agents/model-selection.js";
import { resolveTalkApiKey } from "./talk.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import type { ModelDefinitionConfig } from "./types.models.js";
@@ -53,7 +53,7 @@ function resolveModelCost(
};
}
function resolveAnthropicDefaultAuthMode(cfg: MoltbotConfig): AnthropicAuthDefaultsMode | null {
function resolveAnthropicDefaultAuthMode(cfg: OpenClawConfig): AnthropicAuthDefaultsMode | null {
const profiles = cfg.auth?.profiles ?? {};
const anthropicProfiles = Object.entries(profiles).filter(
([, profile]) => profile?.provider === "anthropic",
@@ -92,7 +92,7 @@ export type SessionDefaultsOptions = {
warnState?: WarnState;
};
export function applyMessageDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyMessageDefaults(cfg: OpenClawConfig): OpenClawConfig {
const messages = cfg.messages;
const hasAckScope = messages?.ackReactionScope !== undefined;
if (hasAckScope) return cfg;
@@ -106,9 +106,9 @@ export function applyMessageDefaults(cfg: MoltbotConfig): MoltbotConfig {
}
export function applySessionDefaults(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
options: SessionDefaultsOptions = {},
): MoltbotConfig {
): OpenClawConfig {
const session = cfg.session;
if (!session || session.mainKey === undefined) return cfg;
@@ -116,7 +116,7 @@ export function applySessionDefaults(
const warn = options.warn ?? console.warn;
const warnState = options.warnState ?? defaultWarnState;
const next: MoltbotConfig = {
const next: OpenClawConfig = {
...cfg,
session: { ...session, mainKey: "main" },
};
@@ -129,7 +129,7 @@ export function applySessionDefaults(
return next;
}
export function applyTalkApiKey(config: MoltbotConfig): MoltbotConfig {
export function applyTalkApiKey(config: OpenClawConfig): OpenClawConfig {
const resolved = resolveTalkApiKey();
if (!resolved) return config;
const existing = config.talk?.apiKey?.trim();
@@ -143,7 +143,7 @@ export function applyTalkApiKey(config: MoltbotConfig): MoltbotConfig {
};
}
export function applyModelDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
let mutated = false;
let nextCfg = cfg;
@@ -238,7 +238,7 @@ export function applyModelDefaults(cfg: MoltbotConfig): MoltbotConfig {
};
}
export function applyAgentDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig {
const agents = cfg.agents;
const defaults = agents?.defaults;
const hasMax =
@@ -275,7 +275,7 @@ export function applyAgentDefaults(cfg: MoltbotConfig): MoltbotConfig {
};
}
export function applyLoggingDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyLoggingDefaults(cfg: OpenClawConfig): OpenClawConfig {
const logging = cfg.logging;
if (!logging) return cfg;
if (logging.redactSensitive) return cfg;
@@ -288,7 +288,7 @@ export function applyLoggingDefaults(cfg: MoltbotConfig): MoltbotConfig {
};
}
export function applyContextPruningDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig {
const defaults = cfg.agents?.defaults;
if (!defaults) return cfg;
@@ -369,7 +369,7 @@ export function applyContextPruningDefaults(cfg: MoltbotConfig): MoltbotConfig {
};
}
export function applyCompactionDefaults(cfg: MoltbotConfig): MoltbotConfig {
export function applyCompactionDefaults(cfg: OpenClawConfig): OpenClawConfig {
const defaults = cfg.agents?.defaults;
if (!defaults) return cfg;
const compaction = defaults?.compaction;
+2 -2
View File
@@ -247,12 +247,12 @@ describe("resolveConfigEnvVars", () => {
const config = {
gateway: {
auth: {
token: "${CLAWDBOT_GATEWAY_TOKEN}",
token: "${OPENCLAW_GATEWAY_TOKEN}",
},
},
};
const result = resolveConfigEnvVars(config, {
CLAWDBOT_GATEWAY_TOKEN: "secret-token",
OPENCLAW_GATEWAY_TOKEN: "secret-token",
});
expect(result).toEqual({
gateway: {
+2 -2
View File
@@ -1,6 +1,6 @@
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
export function collectConfigEnvVars(cfg?: MoltbotConfig): Record<string, string> {
export function collectConfigEnvVars(cfg?: OpenClawConfig): Record<string, string> {
const envConfig = cfg?.env;
if (!envConfig) return {};
+5 -5
View File
@@ -1,6 +1,6 @@
import type { ChannelId } from "../channels/plugins/types.js";
import { normalizeAccountId } from "../routing/session-key.js";
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
export type GroupPolicyChannel = ChannelId;
@@ -80,7 +80,7 @@ export function resolveToolsBySender(
}
function resolveChannelGroups(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
channel: GroupPolicyChannel,
accountId?: string | null,
): ChannelGroups | undefined {
@@ -103,7 +103,7 @@ function resolveChannelGroups(
}
export function resolveChannelGroupPolicy(params: {
cfg: MoltbotConfig;
cfg: OpenClawConfig;
channel: GroupPolicyChannel;
groupId?: string | null;
accountId?: string | null;
@@ -128,7 +128,7 @@ export function resolveChannelGroupPolicy(params: {
}
export function resolveChannelGroupRequireMention(params: {
cfg: MoltbotConfig;
cfg: OpenClawConfig;
channel: GroupPolicyChannel;
groupId?: string | null;
accountId?: string | null;
@@ -156,7 +156,7 @@ export function resolveChannelGroupRequireMention(params: {
export function resolveChannelGroupToolsPolicy(
params: {
cfg: MoltbotConfig;
cfg: OpenClawConfig;
channel: GroupPolicyChannel;
groupId?: string | null;
accountId?: string | null;
+6 -6
View File
@@ -11,17 +11,17 @@ import {
const ROOT_DIR = path.parse(process.cwd()).root;
const CONFIG_DIR = path.join(ROOT_DIR, "config");
const ETC_CLAWDBOT_DIR = path.join(ROOT_DIR, "etc", "moltbot");
const ETC_OPENCLAW_DIR = path.join(ROOT_DIR, "etc", "openclaw");
const SHARED_DIR = path.join(ROOT_DIR, "shared");
const DEFAULT_BASE_PATH = path.join(CONFIG_DIR, "moltbot.json");
const DEFAULT_BASE_PATH = path.join(CONFIG_DIR, "openclaw.json");
function configPath(...parts: string[]) {
return path.join(CONFIG_DIR, ...parts);
}
function etcMoltbotPath(...parts: string[]) {
return path.join(ETC_CLAWDBOT_DIR, ...parts);
function etcOpenClawPath(...parts: string[]) {
return path.join(ETC_OPENCLAW_DIR, ...parts);
}
function sharedPath(...parts: string[]) {
@@ -70,7 +70,7 @@ describe("resolveConfigIncludes", () => {
});
it("resolves absolute path $include", () => {
const absolute = etcMoltbotPath("agents.json");
const absolute = etcOpenClawPath("agents.json");
const files = { [absolute]: { list: [{ id: "main" }] } };
const obj = { agents: { $include: absolute } };
expect(resolve(obj, files)).toEqual({
@@ -283,7 +283,7 @@ describe("resolveConfigIncludes", () => {
it("resolves parent directory references", () => {
const files = { [sharedPath("common.json")]: { shared: true } };
const obj = { $include: "../../shared/common.json" };
expect(resolve(obj, files, configPath("sub", "moltbot.json"))).toEqual({
expect(resolve(obj, files, configPath("sub", "openclaw.json"))).toEqual({
shared: true,
});
});
+13 -52
View File
@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
import { createConfigIO } from "./io.js";
async function withTempHome(run: (home: string) => Promise<void>): Promise<void> {
const home = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-config-"));
const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-"));
try {
await run(home);
} finally {
@@ -16,9 +16,9 @@ async function withTempHome(run: (home: string) => Promise<void>): Promise<void>
async function writeConfig(
home: string,
dirname: ".moltbot" | ".clawdbot",
dirname: ".openclaw",
port: number,
filename: "moltbot.json" | "clawdbot.json" = "moltbot.json",
filename: string = "openclaw.json",
) {
const dir = path.join(home, dirname);
await fs.mkdir(dir, { recursive: true });
@@ -27,76 +27,37 @@ async function writeConfig(
return configPath;
}
describe("config io compat (new + legacy folders)", () => {
it("prefers ~/.moltbot/moltbot.json when both configs exist", async () => {
describe("config io paths", () => {
it("uses ~/.openclaw/openclaw.json when config exists", async () => {
await withTempHome(async (home) => {
const newConfigPath = await writeConfig(home, ".moltbot", 19001);
await writeConfig(home, ".clawdbot", 18789);
const configPath = await writeConfig(home, ".openclaw", 19001);
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
homedir: () => home,
});
expect(io.configPath).toBe(newConfigPath);
expect(io.configPath).toBe(configPath);
expect(io.loadConfig().gateway?.port).toBe(19001);
});
});
it("falls back to ~/.clawdbot/moltbot.json when only legacy exists", async () => {
it("defaults to ~/.openclaw/openclaw.json when config is missing", async () => {
await withTempHome(async (home) => {
const legacyConfigPath = await writeConfig(home, ".clawdbot", 20001);
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
homedir: () => home,
});
expect(io.configPath).toBe(legacyConfigPath);
expect(io.loadConfig().gateway?.port).toBe(20001);
expect(io.configPath).toBe(path.join(home, ".openclaw", "openclaw.json"));
});
});
it("falls back to ~/.clawdbot/clawdbot.json when only legacy filename exists", async () => {
it("honors explicit OPENCLAW_CONFIG_PATH override", async () => {
await withTempHome(async (home) => {
const legacyConfigPath = await writeConfig(home, ".clawdbot", 20002, "clawdbot.json");
const customPath = await writeConfig(home, ".openclaw", 20002, "custom.json");
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
env: { OPENCLAW_CONFIG_PATH: customPath } as NodeJS.ProcessEnv,
homedir: () => home,
});
expect(io.configPath).toBe(legacyConfigPath);
expect(io.loadConfig().gateway?.port).toBe(20002);
});
});
it("prefers moltbot.json over legacy filename in the same dir", async () => {
await withTempHome(async (home) => {
const preferred = await writeConfig(home, ".clawdbot", 20003, "moltbot.json");
await writeConfig(home, ".clawdbot", 20004, "clawdbot.json");
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
homedir: () => home,
});
expect(io.configPath).toBe(preferred);
expect(io.loadConfig().gateway?.port).toBe(20003);
});
});
it("honors explicit legacy config path env override", async () => {
await withTempHome(async (home) => {
const newConfigPath = await writeConfig(home, ".moltbot", 19002);
const legacyConfigPath = await writeConfig(home, ".clawdbot", 20002);
const io = createConfigIO({
env: { CLAWDBOT_CONFIG_PATH: legacyConfigPath } as NodeJS.ProcessEnv,
homedir: () => home,
});
expect(io.configPath).not.toBe(newConfigPath);
expect(io.configPath).toBe(legacyConfigPath);
expect(io.configPath).toBe(customPath);
expect(io.loadConfig().gateway?.port).toBe(20002);
});
});
+22 -22
View File
@@ -30,9 +30,9 @@ import { findLegacyConfigIssues } from "./legacy.js";
import { normalizeConfigPaths } from "./normalize-paths.js";
import { resolveConfigPath, resolveDefaultConfigCandidates, resolveStateDir } from "./paths.js";
import { applyConfigOverrides } from "./runtime-overrides.js";
import type { MoltbotConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
import { validateConfigObjectWithPlugins } from "./validation.js";
import { compareMoltbotVersions } from "./version.js";
import { compareOpenClawVersions } from "./version.js";
// Re-export for backwards compatibility
export { CircularIncludeError, ConfigIncludeError } from "./includes.js";
@@ -53,8 +53,8 @@ const SHELL_ENV_EXPECTED_KEYS = [
"DISCORD_BOT_TOKEN",
"SLACK_BOT_TOKEN",
"SLACK_APP_TOKEN",
"CLAWDBOT_GATEWAY_TOKEN",
"CLAWDBOT_GATEWAY_PASSWORD",
"OPENCLAW_GATEWAY_TOKEN",
"OPENCLAW_GATEWAY_PASSWORD",
];
const CONFIG_BACKUP_COUNT = 5;
@@ -81,11 +81,11 @@ export function resolveConfigSnapshotHash(snapshot: {
return hashConfigRaw(snapshot.raw);
}
function coerceConfig(value: unknown): MoltbotConfig {
function coerceConfig(value: unknown): OpenClawConfig {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return {};
}
return value as MoltbotConfig;
return value as OpenClawConfig;
}
async function rotateConfigBackups(configPath: string, ioFs: typeof fs.promises): Promise<void> {
@@ -125,7 +125,7 @@ function warnOnConfigMiskeys(raw: unknown, logger: Pick<typeof console, "warn">)
}
}
function stampConfigVersion(cfg: MoltbotConfig): MoltbotConfig {
function stampConfigVersion(cfg: OpenClawConfig): OpenClawConfig {
const now = new Date().toISOString();
return {
...cfg,
@@ -137,19 +137,19 @@ function stampConfigVersion(cfg: MoltbotConfig): MoltbotConfig {
};
}
function warnIfConfigFromFuture(cfg: MoltbotConfig, logger: Pick<typeof console, "warn">): void {
function warnIfConfigFromFuture(cfg: OpenClawConfig, logger: Pick<typeof console, "warn">): void {
const touched = cfg.meta?.lastTouchedVersion;
if (!touched) return;
const cmp = compareMoltbotVersions(VERSION, touched);
const cmp = compareOpenClawVersions(VERSION, touched);
if (cmp === null) return;
if (cmp < 0) {
logger.warn(
`Config was last written by a newer Moltbot (${touched}); current version is ${VERSION}.`,
`Config was last written by a newer OpenClaw (${touched}); current version is ${VERSION}.`,
);
}
}
function applyConfigEnv(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): void {
function applyConfigEnv(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): void {
const entries = collectConfigEnvVars(cfg);
for (const [key, value] of Object.entries(entries)) {
if (env[key]?.trim()) continue;
@@ -193,7 +193,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
const configPath =
candidatePaths.find((candidate) => deps.fs.existsSync(candidate)) ?? requestedConfigPath;
function loadConfig(): MoltbotConfig {
function loadConfig(): OpenClawConfig {
try {
if (!deps.fs.existsSync(configPath)) {
if (shouldEnableShellEnvFallback(deps.env) && !shouldDeferShellEnvFallback(deps.env)) {
@@ -218,7 +218,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
// Apply config.env to process.env BEFORE substitution so ${VAR} can reference config-defined vars
if (resolved && typeof resolved === "object" && "env" in resolved) {
applyConfigEnv(resolved as MoltbotConfig, deps.env);
applyConfigEnv(resolved as OpenClawConfig, deps.env);
}
// Substitute ${VAR} env var references
@@ -227,7 +227,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
const resolvedConfig = substituted;
warnOnConfigMiskeys(resolvedConfig, deps.logger);
if (typeof resolvedConfig !== "object" || resolvedConfig === null) return {};
const preValidationDuplicates = findDuplicateAgentDirs(resolvedConfig as MoltbotConfig, {
const preValidationDuplicates = findDuplicateAgentDirs(resolvedConfig as OpenClawConfig, {
env: deps.env,
homedir: deps.homedir,
});
@@ -377,7 +377,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
// Apply config.env to process.env BEFORE substitution so ${VAR} can reference config-defined vars
if (resolved && typeof resolved === "object" && "env" in resolved) {
applyConfigEnv(resolved as MoltbotConfig, deps.env);
applyConfigEnv(resolved as OpenClawConfig, deps.env);
}
// Substitute ${VAR} env var references
@@ -459,7 +459,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
}
}
async function writeConfigFile(cfg: MoltbotConfig) {
async function writeConfigFile(cfg: OpenClawConfig) {
clearConfigCache();
const validated = validateConfigObjectWithPlugins(cfg);
if (!validated.ok) {
@@ -527,17 +527,17 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
}
// NOTE: These wrappers intentionally do *not* cache the resolved config path at
// module scope. `CLAWDBOT_CONFIG_PATH` (and friends) are expected to work even
// module scope. `OPENCLAW_CONFIG_PATH` (and friends) are expected to work even
// when set after the module has been imported (tests, one-off scripts, etc.).
const DEFAULT_CONFIG_CACHE_MS = 200;
let configCache: {
configPath: string;
expiresAt: number;
config: MoltbotConfig;
config: OpenClawConfig;
} | null = null;
function resolveConfigCacheMs(env: NodeJS.ProcessEnv): number {
const raw = env.CLAWDBOT_CONFIG_CACHE_MS?.trim();
const raw = env.OPENCLAW_CONFIG_CACHE_MS?.trim();
if (raw === "" || raw === "0") return 0;
if (!raw) return DEFAULT_CONFIG_CACHE_MS;
const parsed = Number.parseInt(raw, 10);
@@ -546,7 +546,7 @@ function resolveConfigCacheMs(env: NodeJS.ProcessEnv): number {
}
function shouldUseConfigCache(env: NodeJS.ProcessEnv): boolean {
if (env.CLAWDBOT_DISABLE_CONFIG_CACHE?.trim()) return false;
if (env.OPENCLAW_DISABLE_CONFIG_CACHE?.trim()) return false;
return resolveConfigCacheMs(env) > 0;
}
@@ -554,7 +554,7 @@ function clearConfigCache(): void {
configCache = null;
}
export function loadConfig(): MoltbotConfig {
export function loadConfig(): OpenClawConfig {
const io = createConfigIO();
const configPath = io.configPath;
const now = Date.now();
@@ -582,7 +582,7 @@ export async function readConfigFileSnapshot(): Promise<ConfigFileSnapshot> {
return await createConfigIO().readConfigFileSnapshot();
}
export async function writeConfigFile(cfg: MoltbotConfig): Promise<void> {
export async function writeConfigFile(cfg: OpenClawConfig): Promise<void> {
clearConfigCache();
await createConfigIO().writeConfigFile(cfg);
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { applyLegacyMigrations } from "./legacy.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
import { validateConfigObjectWithPlugins } from "./validation.js";
export function migrateLegacyConfig(raw: unknown): {
config: MoltbotConfig | null;
config: OpenClawConfig | null;
changes: string[];
} {
const { next, changes } = applyLegacyMigrations(raw);
+2 -2
View File
@@ -1,6 +1,6 @@
import { normalizeChannelId } from "../channels/plugins/index.js";
import { normalizeAccountId } from "../routing/session-key.js";
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
import type { MarkdownTableMode } from "./types.base.js";
type MarkdownConfigEntry = {
@@ -44,7 +44,7 @@ function resolveMarkdownModeFromSection(
}
export function resolveMarkdownTableMode(params: {
cfg?: Partial<MoltbotConfig>;
cfg?: Partial<OpenClawConfig>;
channel?: string | null;
accountId?: string | null;
}): MarkdownTableMode {
+3 -3
View File
@@ -1,4 +1,4 @@
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
import type { WhatsAppConfig } from "./types.js";
export type MergeSectionOptions<T> = {
@@ -24,10 +24,10 @@ export function mergeConfigSection<T extends Record<string, unknown>>(
}
export function mergeWhatsAppConfig(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
patch: Partial<WhatsAppConfig>,
options?: MergeSectionOptions<WhatsAppConfig>,
): MoltbotConfig {
): OpenClawConfig {
return {
...cfg,
channels: {
+5 -5
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { applyModelDefaults } from "./defaults.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
describe("applyModelDefaults", () => {
it("adds default aliases when models are present", () => {
@@ -14,7 +14,7 @@ describe("applyModelDefaults", () => {
},
},
},
} satisfies MoltbotConfig;
} satisfies OpenClawConfig;
const next = applyModelDefaults(cfg);
expect(next.agents?.defaults?.models?.["anthropic/claude-opus-4-5"]?.alias).toBe("opus");
@@ -30,7 +30,7 @@ describe("applyModelDefaults", () => {
},
},
},
} satisfies MoltbotConfig;
} satisfies OpenClawConfig;
const next = applyModelDefaults(cfg);
@@ -47,7 +47,7 @@ describe("applyModelDefaults", () => {
},
},
},
} satisfies MoltbotConfig;
} satisfies OpenClawConfig;
const next = applyModelDefaults(cfg);
@@ -69,7 +69,7 @@ describe("applyModelDefaults", () => {
},
},
},
} satisfies MoltbotConfig;
} satisfies OpenClawConfig;
const next = applyModelDefaults(cfg);
const model = next.models?.providers?.myproxy?.models?.[0];
+8 -8
View File
@@ -13,16 +13,16 @@ describe("normalizeConfigPaths", () => {
const cfg = normalizeConfigPaths({
tools: { exec: { pathPrepend: ["~/bin"] } },
plugins: { load: { paths: ["~/plugins/a"] } },
logging: { file: "~/.clawdbot/logs/moltbot.log" },
logging: { file: "~/.openclaw/logs/openclaw.log" },
hooks: {
path: "~/.clawdbot/hooks.json5",
path: "~/.openclaw/hooks.json5",
transformsDir: "~/hooks-xform",
},
channels: {
telegram: {
accounts: {
personal: {
tokenFile: "~/.clawdbot/telegram.token",
tokenFile: "~/.openclaw/telegram.token",
},
},
},
@@ -36,7 +36,7 @@ describe("normalizeConfigPaths", () => {
{
id: "main",
workspace: "~/ws-agent",
agentDir: "~/.clawdbot/agents/main",
agentDir: "~/.openclaw/agents/main",
identity: {
name: "~not-a-path",
},
@@ -47,19 +47,19 @@ describe("normalizeConfigPaths", () => {
});
expect(cfg.plugins?.load?.paths?.[0]).toBe(path.join(home, "plugins", "a"));
expect(cfg.logging?.file).toBe(path.join(home, ".clawdbot", "logs", "moltbot.log"));
expect(cfg.hooks?.path).toBe(path.join(home, ".clawdbot", "hooks.json5"));
expect(cfg.logging?.file).toBe(path.join(home, ".openclaw", "logs", "openclaw.log"));
expect(cfg.hooks?.path).toBe(path.join(home, ".openclaw", "hooks.json5"));
expect(cfg.hooks?.transformsDir).toBe(path.join(home, "hooks-xform"));
expect(cfg.tools?.exec?.pathPrepend?.[0]).toBe(path.join(home, "bin"));
expect(cfg.channels?.telegram?.accounts?.personal?.tokenFile).toBe(
path.join(home, ".clawdbot", "telegram.token"),
path.join(home, ".openclaw", "telegram.token"),
);
expect(cfg.channels?.imessage?.accounts?.personal?.dbPath).toBe(
path.join(home, "Library", "Messages", "chat.db"),
);
expect(cfg.agents?.defaults?.workspace).toBe(path.join(home, "ws-default"));
expect(cfg.agents?.list?.[0]?.workspace).toBe(path.join(home, "ws-agent"));
expect(cfg.agents?.list?.[0]?.agentDir).toBe(path.join(home, ".clawdbot", "agents", "main"));
expect(cfg.agents?.list?.[0]?.agentDir).toBe(path.join(home, ".openclaw", "agents", "main"));
expect(cfg.agents?.list?.[0]?.sandbox?.workspaceRoot).toBe(path.join(home, "sandbox-root"));
// Non-path key => do not treat "~" as home expansion.
+2 -2
View File
@@ -1,5 +1,5 @@
import { resolveUserPath } from "../utils.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
@@ -50,7 +50,7 @@ function normalizeAny(key: string | undefined, value: unknown): unknown {
* Goal: accept `~/...` consistently across config file + env overrides, while
* keeping the surface area small and predictable.
*/
export function normalizeConfigPaths(cfg: MoltbotConfig): MoltbotConfig {
export function normalizeConfigPaths(cfg: OpenClawConfig): OpenClawConfig {
if (!cfg || typeof cfg !== "object") return cfg;
normalizeAny(undefined, cfg);
return cfg;
+34 -41
View File
@@ -12,10 +12,10 @@ import {
} from "./paths.js";
describe("oauth paths", () => {
it("prefers CLAWDBOT_OAUTH_DIR over CLAWDBOT_STATE_DIR", () => {
it("prefers OPENCLAW_OAUTH_DIR over OPENCLAW_STATE_DIR", () => {
const env = {
CLAWDBOT_OAUTH_DIR: "/custom/oauth",
CLAWDBOT_STATE_DIR: "/custom/state",
OPENCLAW_OAUTH_DIR: "/custom/oauth",
OPENCLAW_STATE_DIR: "/custom/state",
} as NodeJS.ProcessEnv;
expect(resolveOAuthDir(env, "/custom/state")).toBe(path.resolve("/custom/oauth"));
@@ -24,9 +24,9 @@ describe("oauth paths", () => {
);
});
it("derives oauth path from CLAWDBOT_STATE_DIR when unset", () => {
it("derives oauth path from OPENCLAW_STATE_DIR when unset", () => {
const env = {
CLAWDBOT_STATE_DIR: "/custom/state",
OPENCLAW_STATE_DIR: "/custom/state",
} as NodeJS.ProcessEnv;
expect(resolveOAuthDir(env, "/custom/state")).toBe(path.join("/custom/state", "credentials"));
@@ -37,28 +37,25 @@ describe("oauth paths", () => {
});
describe("state + config path candidates", () => {
it("prefers MOLTBOT_STATE_DIR over legacy state dir env", () => {
it("uses OPENCLAW_STATE_DIR when set", () => {
const env = {
MOLTBOT_STATE_DIR: "/new/state",
CLAWDBOT_STATE_DIR: "/legacy/state",
OPENCLAW_STATE_DIR: "/new/state",
} as NodeJS.ProcessEnv;
expect(resolveStateDir(env, () => "/home/test")).toBe(path.resolve("/new/state"));
});
it("orders default config candidates as new then legacy", () => {
it("orders default config candidates in a stable order", () => {
const home = "/home/test";
const candidates = resolveDefaultConfigCandidates({} as NodeJS.ProcessEnv, () => home);
expect(candidates[0]).toBe(path.join(home, ".moltbot", "moltbot.json"));
expect(candidates[1]).toBe(path.join(home, ".moltbot", "clawdbot.json"));
expect(candidates[2]).toBe(path.join(home, ".clawdbot", "moltbot.json"));
expect(candidates[3]).toBe(path.join(home, ".clawdbot", "clawdbot.json"));
expect(candidates[0]).toBe(path.join(home, ".openclaw", "openclaw.json"));
expect(candidates).toHaveLength(1);
});
it("prefers ~/.moltbot when it exists and legacy dir is missing", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-state-"));
it("prefers ~/.openclaw when it exists and legacy dir is missing", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-state-"));
try {
const newDir = path.join(root, ".moltbot");
const newDir = path.join(root, ".openclaw");
await fs.mkdir(newDir, { recursive: true });
const resolved = resolveStateDir({} as NodeJS.ProcessEnv, () => root);
expect(resolved).toBe(newDir);
@@ -67,20 +64,18 @@ describe("state + config path candidates", () => {
}
});
it("CONFIG_PATH prefers existing legacy filename when present", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-config-"));
it("CONFIG_PATH prefers existing config when present", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-"));
const previousHome = process.env.HOME;
const previousUserProfile = process.env.USERPROFILE;
const previousHomeDrive = process.env.HOMEDRIVE;
const previousHomePath = process.env.HOMEPATH;
const previousMoltbotConfig = process.env.MOLTBOT_CONFIG_PATH;
const previousClawdbotConfig = process.env.CLAWDBOT_CONFIG_PATH;
const previousMoltbotState = process.env.MOLTBOT_STATE_DIR;
const previousClawdbotState = process.env.CLAWDBOT_STATE_DIR;
const previousOpenClawConfig = process.env.OPENCLAW_CONFIG_PATH;
const previousOpenClawState = process.env.OPENCLAW_STATE_DIR;
try {
const legacyDir = path.join(root, ".clawdbot");
const legacyDir = path.join(root, ".openclaw");
await fs.mkdir(legacyDir, { recursive: true });
const legacyPath = path.join(legacyDir, "clawdbot.json");
const legacyPath = path.join(legacyDir, "openclaw.json");
await fs.writeFile(legacyPath, "{}", "utf-8");
process.env.HOME = root;
@@ -90,10 +85,8 @@ describe("state + config path candidates", () => {
process.env.HOMEDRIVE = parsed.root.replace(/\\$/, "");
process.env.HOMEPATH = root.slice(parsed.root.length - 1);
}
delete process.env.MOLTBOT_CONFIG_PATH;
delete process.env.CLAWDBOT_CONFIG_PATH;
delete process.env.MOLTBOT_STATE_DIR;
delete process.env.CLAWDBOT_STATE_DIR;
delete process.env.OPENCLAW_CONFIG_PATH;
delete process.env.OPENCLAW_STATE_DIR;
vi.resetModules();
const { CONFIG_PATH } = await import("./paths.js");
@@ -110,31 +103,31 @@ describe("state + config path candidates", () => {
else process.env.HOMEDRIVE = previousHomeDrive;
if (previousHomePath === undefined) delete process.env.HOMEPATH;
else process.env.HOMEPATH = previousHomePath;
if (previousMoltbotConfig === undefined) delete process.env.MOLTBOT_CONFIG_PATH;
else process.env.MOLTBOT_CONFIG_PATH = previousMoltbotConfig;
if (previousClawdbotConfig === undefined) delete process.env.CLAWDBOT_CONFIG_PATH;
else process.env.CLAWDBOT_CONFIG_PATH = previousClawdbotConfig;
if (previousMoltbotState === undefined) delete process.env.MOLTBOT_STATE_DIR;
else process.env.MOLTBOT_STATE_DIR = previousMoltbotState;
if (previousClawdbotState === undefined) delete process.env.CLAWDBOT_STATE_DIR;
else process.env.CLAWDBOT_STATE_DIR = previousClawdbotState;
if (previousOpenClawConfig === undefined) delete process.env.OPENCLAW_CONFIG_PATH;
else process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig;
if (previousOpenClawConfig === undefined) delete process.env.OPENCLAW_CONFIG_PATH;
else process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig;
if (previousOpenClawState === undefined) delete process.env.OPENCLAW_STATE_DIR;
else process.env.OPENCLAW_STATE_DIR = previousOpenClawState;
if (previousOpenClawState === undefined) delete process.env.OPENCLAW_STATE_DIR;
else process.env.OPENCLAW_STATE_DIR = previousOpenClawState;
await fs.rm(root, { recursive: true, force: true });
vi.resetModules();
}
});
it("respects state dir overrides when config is missing", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-config-override-"));
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-override-"));
try {
const legacyDir = path.join(root, ".clawdbot");
const legacyDir = path.join(root, ".openclaw");
await fs.mkdir(legacyDir, { recursive: true });
const legacyConfig = path.join(legacyDir, "moltbot.json");
const legacyConfig = path.join(legacyDir, "openclaw.json");
await fs.writeFile(legacyConfig, "{}", "utf-8");
const overrideDir = path.join(root, "override");
const env = { MOLTBOT_STATE_DIR: overrideDir } as NodeJS.ProcessEnv;
const env = { OPENCLAW_STATE_DIR: overrideDir } as NodeJS.ProcessEnv;
const resolved = resolveConfigPath(env, overrideDir, () => root);
expect(resolved).toBe(path.join(overrideDir, "moltbot.json"));
expect(resolved).toBe(path.join(overrideDir, "openclaw.json"));
} finally {
await fs.rm(root, { recursive: true, force: true });
}
+55 -49
View File
@@ -1,28 +1,28 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
/**
* Nix mode detection: When CLAWDBOT_NIX_MODE=1, the gateway is running under Nix.
* Nix mode detection: When OPENCLAW_NIX_MODE=1, the gateway is running under Nix.
* In this mode:
* - No auto-install flows should be attempted
* - Missing dependencies should produce actionable Nix-specific error messages
* - Config is managed externally (read-only from Nix perspective)
*/
export function resolveIsNixMode(env: NodeJS.ProcessEnv = process.env): boolean {
return env.CLAWDBOT_NIX_MODE === "1";
return env.OPENCLAW_NIX_MODE === "1";
}
export const isNixMode = resolveIsNixMode();
const LEGACY_STATE_DIRNAME = ".clawdbot";
const NEW_STATE_DIRNAME = ".moltbot";
const CONFIG_FILENAME = "moltbot.json";
const LEGACY_CONFIG_FILENAME = "clawdbot.json";
const LEGACY_STATE_DIRNAMES = [] as const;
const NEW_STATE_DIRNAME = ".openclaw";
const CONFIG_FILENAME = "openclaw.json";
const LEGACY_CONFIG_FILENAMES = [] as const;
function legacyStateDir(homedir: () => string = os.homedir): string {
return path.join(homedir(), LEGACY_STATE_DIRNAME);
function legacyStateDirs(homedir: () => string = os.homedir): string[] {
return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir));
}
function newStateDir(homedir: () => string = os.homedir): string {
@@ -30,7 +30,11 @@ function newStateDir(homedir: () => string = os.homedir): string {
}
export function resolveLegacyStateDir(homedir: () => string = os.homedir): string {
return legacyStateDir(homedir);
return legacyStateDirs(homedir)[0] ?? newStateDir(homedir);
}
export function resolveLegacyStateDirs(homedir: () => string = os.homedir): string[] {
return legacyStateDirs(homedir);
}
export function resolveNewStateDir(homedir: () => string = os.homedir): string {
@@ -39,22 +43,28 @@ export function resolveNewStateDir(homedir: () => string = os.homedir): string {
/**
* State directory for mutable data (sessions, logs, caches).
* Can be overridden via MOLTBOT_STATE_DIR (preferred) or CLAWDBOT_STATE_DIR (legacy).
* Default: ~/.clawdbot (legacy default for compatibility)
* If ~/.moltbot exists and ~/.clawdbot does not, prefer ~/.moltbot.
* Can be overridden via OPENCLAW_STATE_DIR.
* Default: ~/.openclaw
*/
export function resolveStateDir(
env: NodeJS.ProcessEnv = process.env,
homedir: () => string = os.homedir,
): string {
const override = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
if (override) return resolveUserPath(override);
const legacyDir = legacyStateDir(homedir);
const newDir = newStateDir(homedir);
const hasLegacy = fs.existsSync(legacyDir);
const legacyDirs = legacyStateDirs(homedir);
const hasNew = fs.existsSync(newDir);
if (!hasLegacy && hasNew) return newDir;
return legacyDir;
if (hasNew) return newDir;
const existingLegacy = legacyDirs.find((dir) => {
try {
return fs.existsSync(dir);
} catch {
return false;
}
});
if (existingLegacy) return existingLegacy;
return newDir;
}
function resolveUserPath(input: string): string {
@@ -71,21 +81,21 @@ export const STATE_DIR = resolveStateDir();
/**
* Config file path (JSON5).
* Can be overridden via MOLTBOT_CONFIG_PATH (preferred) or CLAWDBOT_CONFIG_PATH (legacy).
* Default: ~/.clawdbot/moltbot.json (or $*_STATE_DIR/moltbot.json)
* Can be overridden via OPENCLAW_CONFIG_PATH.
* Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json)
*/
export function resolveCanonicalConfigPath(
env: NodeJS.ProcessEnv = process.env,
stateDir: string = resolveStateDir(env, os.homedir),
): string {
const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
const override = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override);
return path.join(stateDir, CONFIG_FILENAME);
}
/**
* Resolve the active config path by preferring existing config candidates
* (new/legacy filenames) before falling back to the canonical path.
* before falling back to the canonical path.
*/
export function resolveConfigPathCandidate(
env: NodeJS.ProcessEnv = process.env,
@@ -104,19 +114,19 @@ export function resolveConfigPathCandidate(
}
/**
* Active config path (prefers existing legacy/new config files).
* Active config path (prefers existing config files).
*/
export function resolveConfigPath(
env: NodeJS.ProcessEnv = process.env,
stateDir: string = resolveStateDir(env, os.homedir),
homedir: () => string = os.homedir,
): string {
const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
const override = env.OPENCLAW_CONFIG_PATH?.trim();
if (override) return resolveUserPath(override);
const stateOverride = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
const stateOverride = env.OPENCLAW_STATE_DIR?.trim();
const candidates = [
path.join(stateDir, CONFIG_FILENAME),
path.join(stateDir, LEGACY_CONFIG_FILENAME),
...LEGACY_CONFIG_FILENAMES.map((name) => path.join(stateDir, name)),
];
const existing = candidates.find((candidate) => {
try {
@@ -137,32 +147,29 @@ export function resolveConfigPath(
export const CONFIG_PATH = resolveConfigPathCandidate();
/**
* Resolve default config path candidates across new + legacy locations.
* Order: explicit config path state-dir-derived paths new default legacy default.
* Resolve default config path candidates across default locations.
* Order: explicit config path state-dir-derived paths new default.
*/
export function resolveDefaultConfigCandidates(
env: NodeJS.ProcessEnv = process.env,
homedir: () => string = os.homedir,
): string[] {
const explicit = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
const explicit = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
if (explicit) return [resolveUserPath(explicit)];
const candidates: string[] = [];
const moltbotStateDir = env.MOLTBOT_STATE_DIR?.trim();
if (moltbotStateDir) {
candidates.push(path.join(resolveUserPath(moltbotStateDir), CONFIG_FILENAME));
candidates.push(path.join(resolveUserPath(moltbotStateDir), LEGACY_CONFIG_FILENAME));
}
const legacyStateDirOverride = env.CLAWDBOT_STATE_DIR?.trim();
if (legacyStateDirOverride) {
candidates.push(path.join(resolveUserPath(legacyStateDirOverride), CONFIG_FILENAME));
candidates.push(path.join(resolveUserPath(legacyStateDirOverride), LEGACY_CONFIG_FILENAME));
const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
if (openclawStateDir) {
const resolved = resolveUserPath(openclawStateDir);
candidates.push(path.join(resolved, CONFIG_FILENAME));
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(resolved, name)));
}
candidates.push(path.join(newStateDir(homedir), CONFIG_FILENAME));
candidates.push(path.join(newStateDir(homedir), LEGACY_CONFIG_FILENAME));
candidates.push(path.join(legacyStateDir(homedir), CONFIG_FILENAME));
candidates.push(path.join(legacyStateDir(homedir), LEGACY_CONFIG_FILENAME));
const defaultDirs = [newStateDir(homedir), ...legacyStateDirs(homedir)];
for (const dir of defaultDirs) {
candidates.push(path.join(dir, CONFIG_FILENAME));
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(dir, name)));
}
return candidates;
}
@@ -170,12 +177,12 @@ export const DEFAULT_GATEWAY_PORT = 18789;
/**
* Gateway lock directory (ephemeral).
* Default: os.tmpdir()/moltbot-<uid> (uid suffix when available).
* Default: os.tmpdir()/openclaw-<uid> (uid suffix when available).
*/
export function resolveGatewayLockDir(tmpdir: () => string = os.tmpdir): string {
const base = tmpdir();
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
const suffix = uid != null ? `moltbot-${uid}` : "moltbot";
const suffix = uid != null ? `openclaw-${uid}` : "openclaw";
return path.join(base, suffix);
}
@@ -185,15 +192,14 @@ const OAUTH_FILENAME = "oauth.json";
* OAuth credentials storage directory.
*
* Precedence:
* - `CLAWDBOT_OAUTH_DIR` (explicit override)
* - `OPENCLAW_OAUTH_DIR` (explicit override)
* - `$*_STATE_DIR/credentials` (canonical server/default)
* - `~/.clawdbot/credentials` (legacy default)
*/
export function resolveOAuthDir(
env: NodeJS.ProcessEnv = process.env,
stateDir: string = resolveStateDir(env, os.homedir),
): string {
const override = env.CLAWDBOT_OAUTH_DIR?.trim();
const override = env.OPENCLAW_OAUTH_DIR?.trim();
if (override) return resolveUserPath(override);
return path.join(stateDir, "credentials");
}
@@ -206,10 +212,10 @@ export function resolveOAuthPath(
}
export function resolveGatewayPort(
cfg?: MoltbotConfig,
cfg?: OpenClawConfig,
env: NodeJS.ProcessEnv = process.env,
): number {
const envRaw = env.CLAWDBOT_GATEWAY_PORT?.trim();
const envRaw = env.OPENCLAW_GATEWAY_PORT?.trim() || env.CLAWDBOT_GATEWAY_PORT?.trim();
if (envRaw) {
const parsed = Number.parseInt(envRaw, 10);
if (Number.isFinite(parsed) && parsed > 0) return parsed;
+20 -20
View File
@@ -1,4 +1,4 @@
import type { MoltbotConfig } from "./config.js";
import type { OpenClawConfig } from "./config.js";
import {
getChatChannelMeta,
listChatChannels,
@@ -17,7 +17,7 @@ type PluginEnableChange = {
};
export type PluginAutoEnableResult = {
config: MoltbotConfig;
config: OpenClawConfig;
changes: string[];
};
@@ -59,7 +59,7 @@ function accountsHaveKeys(value: unknown, keys: string[]): boolean {
}
function resolveChannelConfig(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
channelId: string,
): Record<string, unknown> | null {
const channels = cfg.channels as Record<string, unknown> | undefined;
@@ -67,7 +67,7 @@ function resolveChannelConfig(
return isRecord(entry) ? entry : null;
}
function isTelegramConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boolean {
function isTelegramConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
if (hasNonEmptyString(env.TELEGRAM_BOT_TOKEN)) return true;
const entry = resolveChannelConfig(cfg, "telegram");
if (!entry) return false;
@@ -76,7 +76,7 @@ function isTelegramConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boole
return recordHasKeys(entry);
}
function isDiscordConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boolean {
function isDiscordConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
if (hasNonEmptyString(env.DISCORD_BOT_TOKEN)) return true;
const entry = resolveChannelConfig(cfg, "discord");
if (!entry) return false;
@@ -85,7 +85,7 @@ function isDiscordConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boolea
return recordHasKeys(entry);
}
function isSlackConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boolean {
function isSlackConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
if (
hasNonEmptyString(env.SLACK_BOT_TOKEN) ||
hasNonEmptyString(env.SLACK_APP_TOKEN) ||
@@ -106,7 +106,7 @@ function isSlackConfigured(cfg: MoltbotConfig, env: NodeJS.ProcessEnv): boolean
return recordHasKeys(entry);
}
function isSignalConfigured(cfg: MoltbotConfig): boolean {
function isSignalConfigured(cfg: OpenClawConfig): boolean {
const entry = resolveChannelConfig(cfg, "signal");
if (!entry) return false;
if (
@@ -122,27 +122,27 @@ function isSignalConfigured(cfg: MoltbotConfig): boolean {
return recordHasKeys(entry);
}
function isIMessageConfigured(cfg: MoltbotConfig): boolean {
function isIMessageConfigured(cfg: OpenClawConfig): boolean {
const entry = resolveChannelConfig(cfg, "imessage");
if (!entry) return false;
if (hasNonEmptyString(entry.cliPath)) return true;
return recordHasKeys(entry);
}
function isWhatsAppConfigured(cfg: MoltbotConfig): boolean {
function isWhatsAppConfigured(cfg: OpenClawConfig): boolean {
if (hasAnyWhatsAppAuth(cfg)) return true;
const entry = resolveChannelConfig(cfg, "whatsapp");
if (!entry) return false;
return recordHasKeys(entry);
}
function isGenericChannelConfigured(cfg: MoltbotConfig, channelId: string): boolean {
function isGenericChannelConfigured(cfg: OpenClawConfig, channelId: string): boolean {
const entry = resolveChannelConfig(cfg, channelId);
return recordHasKeys(entry);
}
export function isChannelConfigured(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
channelId: string,
env: NodeJS.ProcessEnv = process.env,
): boolean {
@@ -164,7 +164,7 @@ export function isChannelConfigured(
}
}
function collectModelRefs(cfg: MoltbotConfig): string[] {
function collectModelRefs(cfg: OpenClawConfig): string[] {
const refs: string[] = [];
const pushModelRef = (value: unknown) => {
if (typeof value === "string" && value.trim()) refs.push(value.trim());
@@ -208,7 +208,7 @@ function extractProviderFromModelRef(value: string): string | null {
return normalizeProviderId(trimmed.slice(0, slash));
}
function isProviderConfigured(cfg: MoltbotConfig, providerId: string): boolean {
function isProviderConfigured(cfg: OpenClawConfig, providerId: string): boolean {
const normalized = normalizeProviderId(providerId);
const profiles = cfg.auth?.profiles;
@@ -237,7 +237,7 @@ function isProviderConfigured(cfg: MoltbotConfig, providerId: string): boolean {
}
function resolveConfiguredPlugins(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
env: NodeJS.ProcessEnv,
): PluginEnableChange[] {
const changes: PluginEnableChange[] = [];
@@ -269,12 +269,12 @@ function resolveConfiguredPlugins(
return changes;
}
function isPluginExplicitlyDisabled(cfg: MoltbotConfig, pluginId: string): boolean {
function isPluginExplicitlyDisabled(cfg: OpenClawConfig, pluginId: string): boolean {
const entry = cfg.plugins?.entries?.[pluginId];
return entry?.enabled === false;
}
function isPluginDenied(cfg: MoltbotConfig, pluginId: string): boolean {
function isPluginDenied(cfg: OpenClawConfig, pluginId: string): boolean {
const deny = cfg.plugins?.deny;
return Array.isArray(deny) && deny.includes(pluginId);
}
@@ -289,7 +289,7 @@ function resolvePreferredOverIds(pluginId: string): string[] {
}
function shouldSkipPreferredPluginAutoEnable(
cfg: MoltbotConfig,
cfg: OpenClawConfig,
entry: PluginEnableChange,
configured: PluginEnableChange[],
): boolean {
@@ -305,7 +305,7 @@ function shouldSkipPreferredPluginAutoEnable(
return false;
}
function ensureAllowlisted(cfg: MoltbotConfig, pluginId: string): MoltbotConfig {
function ensureAllowlisted(cfg: OpenClawConfig, pluginId: string): OpenClawConfig {
const allow = cfg.plugins?.allow;
if (!Array.isArray(allow) || allow.includes(pluginId)) return cfg;
return {
@@ -317,7 +317,7 @@ function ensureAllowlisted(cfg: MoltbotConfig, pluginId: string): MoltbotConfig
};
}
function enablePluginEntry(cfg: MoltbotConfig, pluginId: string): MoltbotConfig {
function enablePluginEntry(cfg: OpenClawConfig, pluginId: string): OpenClawConfig {
const entries = {
...cfg.plugins?.entries,
[pluginId]: {
@@ -346,7 +346,7 @@ function formatAutoEnableChange(entry: PluginEnableChange): string {
}
export function applyPluginAutoEnable(params: {
config: MoltbotConfig;
config: OpenClawConfig;
env?: NodeJS.ProcessEnv;
}): PluginAutoEnableResult {
const env = params.env ?? process.env;
+4 -4
View File
@@ -6,7 +6,7 @@ import {
setConfigOverride,
unsetConfigOverride,
} from "./runtime-overrides.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
describe("runtime overrides", () => {
beforeEach(() => {
@@ -15,8 +15,8 @@ describe("runtime overrides", () => {
it("sets and applies nested overrides", () => {
const cfg = {
messages: { responsePrefix: "[moltbot]" },
} as MoltbotConfig;
messages: { responsePrefix: "[openclaw]" },
} as OpenClawConfig;
setConfigOverride("messages.responsePrefix", "[debug]");
const next = applyConfigOverrides(cfg);
expect(next.messages?.responsePrefix).toBe("[debug]");
@@ -25,7 +25,7 @@ describe("runtime overrides", () => {
it("merges object overrides without clobbering siblings", () => {
const cfg = {
channels: { whatsapp: { dmPolicy: "pairing", allowFrom: ["+1"] } },
} as MoltbotConfig;
} as OpenClawConfig;
setConfigOverride("channels.whatsapp.dmPolicy", "open");
const next = applyConfigOverrides(cfg);
expect(next.channels?.whatsapp?.dmPolicy).toBe("open");
+3 -3
View File
@@ -1,5 +1,5 @@
import { parseConfigPath, setConfigValueAtPath, unsetConfigValueAtPath } from "./config-paths.js";
import type { MoltbotConfig } from "./types.js";
import type { OpenClawConfig } from "./types.js";
type OverrideTree = Record<string, unknown>;
@@ -64,7 +64,7 @@ export function unsetConfigOverride(pathRaw: string): {
return { ok: true, removed };
}
export function applyConfigOverrides(cfg: MoltbotConfig): MoltbotConfig {
export function applyConfigOverrides(cfg: OpenClawConfig): OpenClawConfig {
if (!overrides || Object.keys(overrides).length === 0) return cfg;
return mergeOverrides(cfg, overrides) as MoltbotConfig;
return mergeOverrides(cfg, overrides) as OpenClawConfig;
}
+12 -12
View File
@@ -1,6 +1,6 @@
import { CHANNEL_IDS } from "../channels/registry.js";
import { VERSION } from "../version.js";
import { MoltbotSchema } from "./zod-schema.js";
import { OpenClawSchema } from "./zod-schema.js";
export type ConfigUiHint = {
label?: string;
@@ -15,7 +15,7 @@ export type ConfigUiHint = {
export type ConfigUiHints = Record<string, ConfigUiHint>;
export type ConfigSchema = ReturnType<typeof MoltbotSchema.toJSONSchema>;
export type ConfigSchema = ReturnType<typeof OpenClawSchema.toJSONSchema>;
type JsonSchemaNode = Record<string, unknown>;
@@ -365,7 +365,7 @@ const FIELD_LABELS: Record<string, string> = {
};
const FIELD_HELP: Record<string, string> = {
"meta.lastTouchedVersion": "Auto-set when Moltbot writes the config.",
"meta.lastTouchedVersion": "Auto-set when OpenClaw writes the config.",
"meta.lastTouchedAt": "ISO timestamp of the last config write (auto-set).",
"update.channel": 'Update channel for git + npm installs ("stable", "beta", or "dev").',
"update.checkOnStart": "Check for npm updates when the gateway starts (default: true).",
@@ -383,7 +383,7 @@ const FIELD_HELP: Record<string, string> = {
"Required by default for gateway access (unless using Tailscale Serve identity); required for non-loopback binds.",
"gateway.auth.password": "Required for Tailscale funnel.",
"gateway.controlUi.basePath":
"Optional URL prefix where the Control UI is served (e.g. /moltbot).",
"Optional URL prefix where the Control UI is served (e.g. /openclaw).",
"gateway.controlUi.allowInsecureAuth":
"Allow Control UI auth over insecure HTTP (token-only; not recommended).",
"gateway.controlUi.dangerouslyDisableDeviceAuth":
@@ -407,7 +407,7 @@ const FIELD_HELP: Record<string, string> = {
"diagnostics.cacheTrace.enabled":
"Log cache trace snapshots for embedded agent runs (default: false).",
"diagnostics.cacheTrace.filePath":
"JSONL output path for cache trace logs (default: $CLAWDBOT_STATE_DIR/logs/cache-trace.jsonl).",
"JSONL output path for cache trace logs (default: $OPENCLAW_STATE_DIR/logs/cache-trace.jsonl).",
"diagnostics.cacheTrace.includeMessages":
"Include full message payloads in trace output (default: true).",
"diagnostics.cacheTrace.includePrompt": "Include prompt text in trace output (default: true).",
@@ -525,7 +525,7 @@ const FIELD_HELP: Record<string, string> = {
"agents.defaults.memorySearch.fallback":
'Fallback provider when embeddings fail ("openai", "gemini", "local", or "none").',
"agents.defaults.memorySearch.store.path":
"SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).",
"SQLite index path (default: ~/.openclaw/memory/{agentId}.sqlite).",
"agents.defaults.memorySearch.store.vector.enabled":
"Enable sqlite-vec extension for vector search (default: true).",
"agents.defaults.memorySearch.store.vector.extensionPath":
@@ -560,12 +560,12 @@ const FIELD_HELP: Record<string, string> = {
"plugins.entries.*.enabled": "Overrides plugin enable/disable for this entry (restart required).",
"plugins.entries.*.config": "Plugin-defined config payload (schema is provided by the plugin).",
"plugins.installs":
"CLI-managed install metadata (used by `moltbot plugins update` to locate install sources).",
"CLI-managed install metadata (used by `openclaw plugins update` to locate install sources).",
"plugins.installs.*.source": 'Install source ("npm", "archive", or "path").',
"plugins.installs.*.spec": "Original npm spec used for install (if source is npm).",
"plugins.installs.*.sourcePath": "Original archive/path used for install (if any).",
"plugins.installs.*.installPath":
"Resolved install directory (usually ~/.clawdbot/extensions/<id>).",
"Resolved install directory (usually ~/.openclaw/extensions/<id>).",
"plugins.installs.*.version": "Version recorded at install time (if available).",
"plugins.installs.*.installedAt": "ISO timestamp of last install/update.",
"agents.list.*.identity.avatar":
@@ -682,9 +682,9 @@ const FIELD_PLACEHOLDERS: Record<string, string> = {
"gateway.remote.url": "ws://host:18789",
"gateway.remote.tlsFingerprint": "sha256:ab12cd34…",
"gateway.remote.sshTarget": "user@host",
"gateway.controlUi.basePath": "/moltbot",
"gateway.controlUi.basePath": "/openclaw",
"channels.mattermost.baseUrl": "https://chat.example.com",
"agents.list[].identity.avatar": "avatars/clawd.png",
"agents.list[].identity.avatar": "avatars/openclaw.png",
};
const SENSITIVE_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i];
@@ -951,11 +951,11 @@ function stripChannelSchema(schema: ConfigSchema): ConfigSchema {
function buildBaseConfigSchema(): ConfigSchemaResponse {
if (cachedBase) return cachedBase;
const schema = MoltbotSchema.toJSONSchema({
const schema = OpenClawSchema.toJSONSchema({
target: "draft-07",
unrepresentable: "any",
});
schema.title = "MoltbotConfig";
schema.title = "OpenClawConfig";
const hints = applySensitiveHints(buildBaseHints());
const next = {
schema: stripChannelSchema(schema),
+4 -4
View File
@@ -23,7 +23,7 @@ describe("Session Store Cache", () => {
clearSessionStoreCacheForTest();
// Reset environment variable
delete process.env.CLAWDBOT_SESSION_CACHE_TTL_MS;
delete process.env.OPENCLAW_SESSION_CACHE_TTL_MS;
});
afterEach(() => {
@@ -32,7 +32,7 @@ describe("Session Store Cache", () => {
fs.rmSync(testDir, { recursive: true, force: true });
}
clearSessionStoreCacheForTest();
delete process.env.CLAWDBOT_SESSION_CACHE_TTL_MS;
delete process.env.OPENCLAW_SESSION_CACHE_TTL_MS;
});
it("should load session store from disk on first call", async () => {
@@ -161,8 +161,8 @@ describe("Session Store Cache", () => {
expect(loaded2["session:1"].displayName).toBe("Updated Session 1");
});
it("should respect CLAWDBOT_SESSION_CACHE_TTL_MS=0 to disable cache", async () => {
process.env.CLAWDBOT_SESSION_CACHE_TTL_MS = "0";
it("should respect OPENCLAW_SESSION_CACHE_TTL_MS=0 to disable cache", async () => {
process.env.OPENCLAW_SESSION_CACHE_TTL_MS = "0";
clearSessionStoreCacheForTest();
const testStore: Record<string, SessionEntry> = {
+22 -22
View File
@@ -56,11 +56,11 @@ describe("sessions", () => {
buildGroupDisplayName({
provider: "discord",
groupChannel: "#general",
space: "friends-of-clawd",
space: "friends-of-openclaw",
id: "123",
key: "discord:group:123",
}),
).toBe("discord:friends-of-clawd#general");
).toBe("discord:friends-of-openclaw#general");
});
it("collapses direct chats to main by default", () => {
@@ -95,7 +95,7 @@ describe("sessions", () => {
it("updateLastRoute persists channel and target", async () => {
const mainSessionKey = "agent:main:main";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,
@@ -148,7 +148,7 @@ describe("sessions", () => {
it("updateLastRoute prefers explicit deliveryContext", async () => {
const mainSessionKey = "agent:main:main";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(storePath, "{}", "utf-8");
@@ -178,7 +178,7 @@ describe("sessions", () => {
it("updateLastRoute records origin + group metadata when ctx is provided", async () => {
const sessionKey = "agent:main:whatsapp:group:123@g.us";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(storePath, "{}", "utf-8");
@@ -208,7 +208,7 @@ describe("sessions", () => {
it("updateSessionStoreEntry preserves existing fields when patching", async () => {
const sessionKey = "agent:main:main";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,
@@ -238,7 +238,7 @@ describe("sessions", () => {
});
it("updateSessionStore preserves concurrent additions", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(storePath, "{}", "utf-8");
@@ -257,7 +257,7 @@ describe("sessions", () => {
});
it("recovers from array-backed session stores", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(storePath, "[]", "utf-8");
@@ -273,7 +273,7 @@ describe("sessions", () => {
});
it("normalizes last route fields on write", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(storePath, "{}", "utf-8");
@@ -299,7 +299,7 @@ describe("sessions", () => {
});
it("updateSessionStore keeps deletions when concurrent writes happen", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,
@@ -331,7 +331,7 @@ describe("sessions", () => {
it("loadSessionStore auto-migrates legacy provider keys to channel keys", async () => {
const mainSessionKey = "agent:main:main";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,
@@ -359,17 +359,17 @@ describe("sessions", () => {
expect(entry.lastProvider).toBeUndefined();
});
it("derives session transcripts dir from CLAWDBOT_STATE_DIR", () => {
it("derives session transcripts dir from OPENCLAW_STATE_DIR", () => {
const dir = resolveSessionTranscriptsDir(
{ CLAWDBOT_STATE_DIR: "/custom/state" } as NodeJS.ProcessEnv,
{ OPENCLAW_STATE_DIR: "/custom/state" } as NodeJS.ProcessEnv,
() => "/home/ignored",
);
expect(dir).toBe(path.join(path.resolve("/custom/state"), "agents", "main", "sessions"));
});
it("includes topic ids in session transcript filenames", () => {
const prev = process.env.CLAWDBOT_STATE_DIR;
process.env.CLAWDBOT_STATE_DIR = "/custom/state";
const prev = process.env.OPENCLAW_STATE_DIR;
process.env.OPENCLAW_STATE_DIR = "/custom/state";
try {
const sessionFile = resolveSessionTranscriptPath("sess-1", "main", 123);
expect(sessionFile).toBe(
@@ -383,16 +383,16 @@ describe("sessions", () => {
);
} finally {
if (prev === undefined) {
delete process.env.CLAWDBOT_STATE_DIR;
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.CLAWDBOT_STATE_DIR = prev;
process.env.OPENCLAW_STATE_DIR = prev;
}
}
});
it("uses agent id when resolving session file fallback paths", () => {
const prev = process.env.CLAWDBOT_STATE_DIR;
process.env.CLAWDBOT_STATE_DIR = "/custom/state";
const prev = process.env.OPENCLAW_STATE_DIR;
process.env.OPENCLAW_STATE_DIR = "/custom/state";
try {
const sessionFile = resolveSessionFilePath("sess-2", undefined, {
agentId: "codex",
@@ -402,16 +402,16 @@ describe("sessions", () => {
);
} finally {
if (prev === undefined) {
delete process.env.CLAWDBOT_STATE_DIR;
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.CLAWDBOT_STATE_DIR = prev;
process.env.OPENCLAW_STATE_DIR = prev;
}
}
});
it("updateSessionStoreEntry merges concurrent patches", async () => {
const mainSessionKey = "agent:main:main";
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,
+1 -1
View File
@@ -35,7 +35,7 @@ function isSessionStoreRecord(value: unknown): value is Record<string, SessionEn
function getSessionStoreTtl(): number {
return resolveCacheTtlMs({
envValue: process.env.CLAWDBOT_SESSION_CACHE_TTL_MS,
envValue: process.env.OPENCLAW_SESSION_CACHE_TTL_MS,
defaultTtlMs: DEFAULT_SESSION_STORE_TTL_MS,
});
}
+1 -1
View File
@@ -99,7 +99,7 @@ export async function appendAssistantMessageToSessionTranscript(params: {
role: "assistant",
content: [{ type: "text", text: mirrorText }],
api: "openai-responses",
provider: "moltbot",
provider: "openclaw",
model: "delivery-mirror",
usage: {
input: 0,
+1 -1
View File
@@ -3,7 +3,7 @@ import { vi } from "vitest";
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
export async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
return withTempHomeBase(fn, { prefix: "moltbot-config-" });
return withTempHomeBase(fn, { prefix: "openclaw-config-" });
}
/**
+3 -3
View File
@@ -3,8 +3,8 @@ export type BrowserProfileConfig = {
cdpPort?: number;
/** CDP URL for this profile (use for remote Chrome). */
cdpUrl?: string;
/** Profile driver (default: clawd). */
driver?: "clawd" | "extension";
/** Profile driver (default: openclaw). */
driver?: "openclaw" | "extension";
/** Profile color (hex). Auto-assigned at creation. */
color: string;
};
@@ -22,7 +22,7 @@ export type BrowserConfig = {
remoteCdpTimeoutMs?: number;
/** Remote CDP WebSocket handshake timeout (ms). Default: max(remoteCdpTimeoutMs * 2, 2000). */
remoteCdpHandshakeTimeoutMs?: number;
/** Accent color for the clawd browser profile (hex). Default: #FF4500 */
/** Accent color for the openclaw browser profile (hex). Default: #FF4500 */
color?: string;
/** Override the browser executable path (all platforms). */
executablePath?: string;
+4 -2
View File
@@ -15,6 +15,8 @@ export type GatewayTlsConfig = {
export type WideAreaDiscoveryConfig = {
enabled?: boolean;
/** Optional unicast DNS-SD domain (e.g. "openclaw.internal"). */
domain?: string;
};
export type MdnsDiscoveryMode = "off" | "minimal" | "full";
@@ -36,7 +38,7 @@ export type DiscoveryConfig = {
export type CanvasHostConfig = {
enabled?: boolean;
/** Directory to serve (default: ~/clawd/canvas). */
/** Directory to serve (default: ~/.openclaw/workspace/canvas). */
root?: string;
/** HTTP port to listen on (default: 18793). */
port?: number;
@@ -62,7 +64,7 @@ export type TalkConfig = {
export type GatewayControlUiConfig = {
/** If false, the Gateway will not serve the Control UI (default /). */
enabled?: boolean;
/** Optional base path prefix for the Control UI (e.g. "/moltbot"). */
/** Optional base path prefix for the Control UI (e.g. "/openclaw"). */
basePath?: string;
/** Allow token-only auth over insecure HTTP (default: false). */
allowInsecureAuth?: boolean;
+1 -1
View File
@@ -23,7 +23,7 @@ export type IMessageAccountConfig = {
cliPath?: string;
/** Optional Messages db path override. */
dbPath?: string;
/** Remote host for SCP when attachments live on a different machine (e.g., moltbot@192.168.64.3). */
/** Remote host for SCP when attachments live on a different machine (e.g., openclaw@192.168.64.3). */
remoteHost?: string;
/** Optional default send service (imessage|sms|auto). */
service?: "imessage" | "sms" | "auto";
@@ -24,9 +24,9 @@ import type { PluginsConfig } from "./types.plugins.js";
import type { SkillsConfig } from "./types.skills.js";
import type { ToolsConfig } from "./types.tools.js";
export type MoltbotConfig = {
export type OpenClawConfig = {
meta?: {
/** Last moltbot version that wrote this config. */
/** Last OpenClaw version that wrote this config. */
lastTouchedVersion?: string;
/** ISO timestamp when this config was last written. */
lastTouchedAt?: string;
@@ -65,7 +65,7 @@ export type MoltbotConfig = {
};
browser?: BrowserConfig;
ui?: {
/** Accent color for Moltbot UI chrome (hex). */
/** Accent color for OpenClaw UI chrome (hex). */
seamColor?: string;
assistant?: {
/** Assistant display name for UI surfaces. */
@@ -113,7 +113,7 @@ export type ConfigFileSnapshot = {
raw: string | null;
parsed: unknown;
valid: boolean;
config: MoltbotConfig;
config: OpenClawConfig;
hash?: string;
issues: ConfigValidationIssue[];
warnings: ConfigValidationIssue[];
+1 -1
View File
@@ -60,7 +60,7 @@ export type SlackActionConfig = {
export type SlackSlashCommandConfig = {
/** Enable handling for the configured slash command (default: false). */
enabled?: boolean;
/** Slash command name (default: "clawd"). */
/** Slash command name (default: "openclaw"). */
name?: string;
/** Session key prefix for slash commands (default: "slack:slash"). */
sessionPrefix?: string;
+1 -1
View File
@@ -7,7 +7,7 @@ export * from "./types.auth.js";
export * from "./types.base.js";
export * from "./types.browser.js";
export * from "./types.channels.js";
export * from "./types.clawdbot.js";
export * from "./types.openclaw.js";
export * from "./types.cron.js";
export * from "./types.discord.js";
export * from "./types.googlechat.js";
+1 -1
View File
@@ -27,7 +27,7 @@ export type WhatsAppConfig = {
sendReadReceipts?: boolean;
/**
* Inbound message prefix (WhatsApp only).
* Default: `[{agents.list[].identity.name}]` (or `[moltbot]`) when allowFrom is empty, else `""`.
* Default: `[{agents.list[].identity.name}]` (or `[openclaw]`) when allowFrom is empty, else `""`.
*/
messagePrefix?: string;
/** Direct message access policy (default: pairing). */
+9 -9
View File
@@ -12,8 +12,8 @@ import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
import { findLegacyConfigIssues } from "./legacy.js";
import type { MoltbotConfig, ConfigValidationIssue } from "./types.js";
import { MoltbotSchema } from "./zod-schema.js";
import type { OpenClawConfig, ConfigValidationIssue } from "./types.js";
import { OpenClawSchema } from "./zod-schema.js";
const AVATAR_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
const AVATAR_DATA_RE = /^data:/i;
@@ -29,7 +29,7 @@ function isWorkspaceAvatarPath(value: string, workspaceDir: string): boolean {
return !path.isAbsolute(relative);
}
function validateIdentityAvatar(config: MoltbotConfig): ConfigValidationIssue[] {
function validateIdentityAvatar(config: OpenClawConfig): ConfigValidationIssue[] {
const agents = config.agents?.list;
if (!Array.isArray(agents) || agents.length === 0) return [];
const issues: ConfigValidationIssue[] = [];
@@ -71,7 +71,7 @@ function validateIdentityAvatar(config: MoltbotConfig): ConfigValidationIssue[]
export function validateConfigObject(
raw: unknown,
): { ok: true; config: MoltbotConfig } | { ok: false; issues: ConfigValidationIssue[] } {
): { ok: true; config: OpenClawConfig } | { ok: false; issues: ConfigValidationIssue[] } {
const legacyIssues = findLegacyConfigIssues(raw);
if (legacyIssues.length > 0) {
return {
@@ -82,7 +82,7 @@ export function validateConfigObject(
})),
};
}
const validated = MoltbotSchema.safeParse(raw);
const validated = OpenClawSchema.safeParse(raw);
if (!validated.success) {
return {
ok: false,
@@ -92,7 +92,7 @@ export function validateConfigObject(
})),
};
}
const duplicates = findDuplicateAgentDirs(validated.data as MoltbotConfig);
const duplicates = findDuplicateAgentDirs(validated.data as OpenClawConfig);
if (duplicates.length > 0) {
return {
ok: false,
@@ -104,14 +104,14 @@ export function validateConfigObject(
],
};
}
const avatarIssues = validateIdentityAvatar(validated.data as MoltbotConfig);
const avatarIssues = validateIdentityAvatar(validated.data as OpenClawConfig);
if (avatarIssues.length > 0) {
return { ok: false, issues: avatarIssues };
}
return {
ok: true,
config: applyModelDefaults(
applyAgentDefaults(applySessionDefaults(validated.data as MoltbotConfig)),
applyAgentDefaults(applySessionDefaults(validated.data as OpenClawConfig)),
),
};
}
@@ -123,7 +123,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
export function validateConfigObjectWithPlugins(raw: unknown):
| {
ok: true;
config: MoltbotConfig;
config: OpenClawConfig;
warnings: ConfigValidationIssue[];
}
| {
+5 -5
View File
@@ -1,4 +1,4 @@
export type MoltbotVersion = {
export type OpenClawVersion = {
major: number;
minor: number;
patch: number;
@@ -7,7 +7,7 @@ export type MoltbotVersion = {
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-(\d+))?/;
export function parseMoltbotVersion(raw: string | null | undefined): MoltbotVersion | null {
export function parseOpenClawVersion(raw: string | null | undefined): OpenClawVersion | null {
if (!raw) return null;
const match = raw.trim().match(VERSION_RE);
if (!match) return null;
@@ -20,12 +20,12 @@ export function parseMoltbotVersion(raw: string | null | undefined): MoltbotVers
};
}
export function compareMoltbotVersions(
export function compareOpenClawVersions(
a: string | null | undefined,
b: string | null | undefined,
): number | null {
const parsedA = parseMoltbotVersion(a);
const parsedB = parseMoltbotVersion(b);
const parsedA = parseOpenClawVersion(a);
const parsedB = parseOpenClawVersion(b);
if (!parsedA || !parsedB) return null;
if (parsedA.major !== parsedB.major) return parsedA.major < parsedB.major ? -1 : 1;
if (parsedA.minor !== parsedB.minor) return parsedA.minor < parsedB.minor ? -1 : 1;
+3 -2
View File
@@ -27,7 +27,7 @@ const NodeHostSchema = z
.strict()
.optional();
export const MoltbotSchema = z
export const OpenClawSchema = z
.object({
meta: z
.object({
@@ -154,7 +154,7 @@ export const MoltbotSchema = z
.object({
cdpPort: z.number().int().min(1).max(65535).optional(),
cdpUrl: z.string().optional(),
driver: z.union([z.literal("clawd"), z.literal("extension")]).optional(),
driver: z.union([z.literal("openclaw"), z.literal("extension")]).optional(),
color: HexColorSchema,
})
.strict()
@@ -268,6 +268,7 @@ export const MoltbotSchema = z
wideArea: z
.object({
enabled: z.boolean().optional(),
domain: z.string().optional(),
})
.strict()
.optional(),