mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 15:01:41 +03:00
refactor: rename to openclaw
This commit is contained in:
@@ -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[] }>();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,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: {
|
||||
|
||||
@@ -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,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" } },
|
||||
|
||||
@@ -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" } }],
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"] },
|
||||
|
||||
@@ -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" }],
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
| {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user