refactor: rename to openclaw

This commit is contained in:
Peter Steinberger
2026-01-30 03:15:10 +01:00
parent 4583f88626
commit 9a7160786a
2357 changed files with 16688 additions and 16788 deletions

View File

@@ -14,7 +14,7 @@ describe("resolveGatewayLaunchAgentLabel", () => {
it("returns default label when no profile is set", () => {
const result = resolveGatewayLaunchAgentLabel();
expect(result).toBe(GATEWAY_LAUNCH_AGENT_LABEL);
expect(result).toBe("bot.molt.gateway");
expect(result).toBe("ai.openclaw.gateway");
});
it("returns default label when profile is undefined", () => {
@@ -34,17 +34,17 @@ describe("resolveGatewayLaunchAgentLabel", () => {
it("returns profile-specific label when profile is set", () => {
const result = resolveGatewayLaunchAgentLabel("dev");
expect(result).toBe("bot.molt.dev");
expect(result).toBe("ai.openclaw.dev");
});
it("returns profile-specific label for custom profile", () => {
const result = resolveGatewayLaunchAgentLabel("work");
expect(result).toBe("bot.molt.work");
expect(result).toBe("ai.openclaw.work");
});
it("trims whitespace from profile", () => {
const result = resolveGatewayLaunchAgentLabel(" staging ");
expect(result).toBe("bot.molt.staging");
expect(result).toBe("ai.openclaw.staging");
});
it("returns default label for empty string profile", () => {
@@ -62,7 +62,7 @@ describe("resolveGatewaySystemdServiceName", () => {
it("returns default service name when no profile is set", () => {
const result = resolveGatewaySystemdServiceName();
expect(result).toBe(GATEWAY_SYSTEMD_SERVICE_NAME);
expect(result).toBe("moltbot-gateway");
expect(result).toBe("openclaw-gateway");
});
it("returns default service name when profile is undefined", () => {
@@ -82,17 +82,17 @@ describe("resolveGatewaySystemdServiceName", () => {
it("returns profile-specific service name when profile is set", () => {
const result = resolveGatewaySystemdServiceName("dev");
expect(result).toBe("moltbot-gateway-dev");
expect(result).toBe("openclaw-gateway-dev");
});
it("returns profile-specific service name for custom profile", () => {
const result = resolveGatewaySystemdServiceName("production");
expect(result).toBe("moltbot-gateway-production");
expect(result).toBe("openclaw-gateway-production");
});
it("trims whitespace from profile", () => {
const result = resolveGatewaySystemdServiceName(" test ");
expect(result).toBe("moltbot-gateway-test");
expect(result).toBe("openclaw-gateway-test");
});
it("returns default service name for empty string profile", () => {
@@ -110,7 +110,7 @@ describe("resolveGatewayWindowsTaskName", () => {
it("returns default task name when no profile is set", () => {
const result = resolveGatewayWindowsTaskName();
expect(result).toBe(GATEWAY_WINDOWS_TASK_NAME);
expect(result).toBe("Moltbot Gateway");
expect(result).toBe("OpenClaw Gateway");
});
it("returns default task name when profile is undefined", () => {
@@ -130,17 +130,17 @@ describe("resolveGatewayWindowsTaskName", () => {
it("returns profile-specific task name when profile is set", () => {
const result = resolveGatewayWindowsTaskName("dev");
expect(result).toBe("Moltbot Gateway (dev)");
expect(result).toBe("OpenClaw Gateway (dev)");
});
it("returns profile-specific task name for custom profile", () => {
const result = resolveGatewayWindowsTaskName("work");
expect(result).toBe("Moltbot Gateway (work)");
expect(result).toBe("OpenClaw Gateway (work)");
});
it("trims whitespace from profile", () => {
const result = resolveGatewayWindowsTaskName(" ci ");
expect(result).toBe("Moltbot Gateway (ci)");
expect(result).toBe("OpenClaw Gateway (ci)");
});
it("returns default task name for empty string profile", () => {
@@ -175,24 +175,24 @@ describe("resolveGatewayProfileSuffix", () => {
describe("formatGatewayServiceDescription", () => {
it("returns default description when no profile/version", () => {
expect(formatGatewayServiceDescription()).toBe("Moltbot Gateway");
expect(formatGatewayServiceDescription()).toBe("OpenClaw Gateway");
});
it("includes profile when set", () => {
expect(formatGatewayServiceDescription({ profile: "work" })).toBe(
"Moltbot Gateway (profile: work)",
"OpenClaw Gateway (profile: work)",
);
});
it("includes version when set", () => {
expect(formatGatewayServiceDescription({ version: "2026.1.10" })).toBe(
"Moltbot Gateway (v2026.1.10)",
"OpenClaw Gateway (v2026.1.10)",
);
});
it("includes profile and version when set", () => {
expect(formatGatewayServiceDescription({ profile: "dev", version: "1.2.3" })).toBe(
"Moltbot Gateway (profile: dev, v1.2.3)",
"OpenClaw Gateway (profile: dev, v1.2.3)",
);
});
});

View File

@@ -1,19 +1,16 @@
// Default service labels (for backward compatibility and when no profile specified)
export const GATEWAY_LAUNCH_AGENT_LABEL = "bot.molt.gateway";
export const GATEWAY_SYSTEMD_SERVICE_NAME = "moltbot-gateway";
export const GATEWAY_WINDOWS_TASK_NAME = "Moltbot Gateway";
export const GATEWAY_SERVICE_MARKER = "moltbot";
// Default service labels (canonical + legacy compatibility)
export const GATEWAY_LAUNCH_AGENT_LABEL = "ai.openclaw.gateway";
export const GATEWAY_SYSTEMD_SERVICE_NAME = "openclaw-gateway";
export const GATEWAY_WINDOWS_TASK_NAME = "OpenClaw Gateway";
export const GATEWAY_SERVICE_MARKER = "openclaw";
export const GATEWAY_SERVICE_KIND = "gateway";
export const NODE_LAUNCH_AGENT_LABEL = "bot.molt.node";
export const NODE_SYSTEMD_SERVICE_NAME = "moltbot-node";
export const NODE_WINDOWS_TASK_NAME = "Moltbot Node";
export const NODE_SERVICE_MARKER = "moltbot";
export const NODE_LAUNCH_AGENT_LABEL = "ai.openclaw.node";
export const NODE_SYSTEMD_SERVICE_NAME = "openclaw-node";
export const NODE_WINDOWS_TASK_NAME = "OpenClaw Node";
export const NODE_SERVICE_MARKER = "openclaw";
export const NODE_SERVICE_KIND = "node";
export const NODE_WINDOWS_TASK_SCRIPT_NAME = "node.cmd";
export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS = [
"com.clawdbot.gateway",
"com.steipete.clawdbot.gateway",
];
export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS: string[] = [];
export const LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES: string[] = [];
export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES: string[] = [];
@@ -33,27 +30,24 @@ export function resolveGatewayLaunchAgentLabel(profile?: string): string {
if (!normalized) {
return GATEWAY_LAUNCH_AGENT_LABEL;
}
return `bot.molt.${normalized}`;
return `ai.openclaw.${normalized}`;
}
export function resolveLegacyGatewayLaunchAgentLabels(profile?: string): string[] {
const normalized = normalizeGatewayProfile(profile);
if (!normalized) {
return [...LEGACY_GATEWAY_LAUNCH_AGENT_LABELS];
}
return [...LEGACY_GATEWAY_LAUNCH_AGENT_LABELS, `com.clawdbot.${normalized}`];
void profile;
return [];
}
export function resolveGatewaySystemdServiceName(profile?: string): string {
const suffix = resolveGatewayProfileSuffix(profile);
if (!suffix) return GATEWAY_SYSTEMD_SERVICE_NAME;
return `moltbot-gateway${suffix}`;
return `openclaw-gateway${suffix}`;
}
export function resolveGatewayWindowsTaskName(profile?: string): string {
const normalized = normalizeGatewayProfile(profile);
if (!normalized) return GATEWAY_WINDOWS_TASK_NAME;
return `Moltbot Gateway (${normalized})`;
return `OpenClaw Gateway (${normalized})`;
}
export function formatGatewayServiceDescription(params?: {
@@ -65,8 +59,8 @@ export function formatGatewayServiceDescription(params?: {
const parts: string[] = [];
if (profile) parts.push(`profile: ${profile}`);
if (version) parts.push(`v${version}`);
if (parts.length === 0) return "Moltbot Gateway";
return `Moltbot Gateway (${parts.join(", ")})`;
if (parts.length === 0) return "OpenClaw Gateway";
return `OpenClaw Gateway (${parts.join(", ")})`;
}
export function resolveNodeLaunchAgentLabel(): string {
@@ -83,6 +77,6 @@ export function resolveNodeWindowsTaskName(): string {
export function formatNodeServiceDescription(params?: { version?: string }): string {
const version = params?.version?.trim();
if (!version) return "Moltbot Node Host";
return `Moltbot Node Host (v${version})`;
if (!version) return "OpenClaw Node Host";
return `OpenClaw Node Host (v${version})`;
}

View File

@@ -6,12 +6,9 @@ import { promisify } from "node:util";
import {
GATEWAY_SERVICE_KIND,
GATEWAY_SERVICE_MARKER,
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES,
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
resolveGatewayLaunchAgentLabel,
resolveGatewaySystemdServiceName,
resolveGatewayWindowsTaskName,
resolveLegacyGatewayLaunchAgentLabels,
} from "./constants.js";
export type ExtraGatewayService = {
@@ -25,13 +22,13 @@ export type FindExtraGatewayServicesOptions = {
deep?: boolean;
};
const EXTRA_MARKERS = ["moltbot"];
const EXTRA_MARKERS = ["openclaw"];
const execFileAsync = promisify(execFile);
export function renderGatewayServiceCleanupHints(
env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
): string[] {
const profile = env.CLAWDBOT_PROFILE;
const profile = env.OPENCLAW_PROFILE;
switch (process.platform) {
case "darwin": {
const label = resolveGatewayLaunchAgentLabel(profile);
@@ -66,32 +63,38 @@ function containsMarker(content: string): boolean {
function hasGatewayServiceMarker(content: string): boolean {
const lower = content.toLowerCase();
const markerKeys = ["openclaw_service_marker"];
const kindKeys = ["openclaw_service_kind"];
const markerValues = [GATEWAY_SERVICE_MARKER.toLowerCase()];
const hasMarkerKey = markerKeys.some((key) => lower.includes(key));
const hasKindKey = kindKeys.some((key) => lower.includes(key));
const hasMarkerValue = markerValues.some((value) => lower.includes(value));
return (
lower.includes("moltbot_service_marker") &&
lower.includes(GATEWAY_SERVICE_MARKER.toLowerCase()) &&
lower.includes("moltbot_service_kind") &&
hasMarkerKey &&
hasKindKey &&
hasMarkerValue &&
lower.includes(GATEWAY_SERVICE_KIND.toLowerCase())
);
}
function isMoltbotGatewayLaunchdService(label: string, contents: string): boolean {
function isOpenClawGatewayLaunchdService(label: string, contents: string): boolean {
if (hasGatewayServiceMarker(contents)) return true;
const lowerContents = contents.toLowerCase();
if (!lowerContents.includes("gateway")) return false;
return label.startsWith("bot.molt.") || label.startsWith("com.clawdbot.");
return label.startsWith("ai.openclaw.");
}
function isMoltbotGatewaySystemdService(name: string, contents: string): boolean {
function isOpenClawGatewaySystemdService(name: string, contents: string): boolean {
if (hasGatewayServiceMarker(contents)) return true;
if (!name.startsWith("moltbot-gateway")) return false;
if (!name.startsWith("openclaw-gateway")) return false;
return contents.toLowerCase().includes("gateway");
}
function isMoltbotGatewayTaskName(name: string): boolean {
function isOpenClawGatewayTaskName(name: string): boolean {
const normalized = name.trim().toLowerCase();
if (!normalized) return false;
const defaultName = resolveGatewayWindowsTaskName().toLowerCase();
return normalized === defaultName || normalized.startsWith("moltbot gateway");
return normalized === defaultName || normalized.startsWith("openclaw gateway");
}
function tryExtractPlistLabel(contents: string): string | null {
@@ -101,17 +104,11 @@ function tryExtractPlistLabel(contents: string): string | null {
}
function isIgnoredLaunchdLabel(label: string): boolean {
return (
label === resolveGatewayLaunchAgentLabel() ||
resolveLegacyGatewayLaunchAgentLabels(process.env.CLAWDBOT_PROFILE).includes(label)
);
return label === resolveGatewayLaunchAgentLabel();
}
function isIgnoredSystemdName(name: string): boolean {
return (
name === resolveGatewaySystemdServiceName() ||
LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES.includes(name)
);
return name === resolveGatewaySystemdServiceName();
}
async function scanLaunchdDir(params: {
@@ -140,7 +137,7 @@ async function scanLaunchdDir(params: {
if (!containsMarker(contents)) continue;
const label = tryExtractPlistLabel(contents) ?? labelFromName;
if (isIgnoredLaunchdLabel(label)) continue;
if (isMoltbotGatewayLaunchdService(label, contents)) continue;
if (isOpenClawGatewayLaunchdService(label, contents)) continue;
results.push({
platform: "darwin",
label,
@@ -176,7 +173,7 @@ async function scanSystemdDir(params: {
continue;
}
if (!containsMarker(contents)) continue;
if (isMoltbotGatewaySystemdService(name, contents)) continue;
if (isOpenClawGatewaySystemdService(name, contents)) continue;
results.push({
platform: "linux",
label: entry,
@@ -336,8 +333,7 @@ export async function findExtraGatewayServices(
for (const task of tasks) {
const name = task.name.trim();
if (!name) continue;
if (isMoltbotGatewayTaskName(name)) continue;
if (LEGACY_GATEWAY_WINDOWS_TASK_NAMES.includes(name)) continue;
if (isOpenClawGatewayTaskName(name)) continue;
const lowerName = name.toLowerCase();
const lowerCommand = task.taskToRun?.toLowerCase() ?? "";
const matches = EXTRA_MARKERS.some(

View File

@@ -18,10 +18,10 @@ async function withLaunchctlStub(
run: (context: { env: Record<string, string | undefined>; logPath: string }) => Promise<void>,
) {
const originalPath = process.env.PATH;
const originalLogPath = process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;
const originalListOutput = process.env.CLAWDBOT_TEST_LAUNCHCTL_LIST_OUTPUT;
const originalLogPath = process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;
const originalListOutput = process.env.OPENCLAW_TEST_LAUNCHCTL_LIST_OUTPUT;
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-launchctl-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-test-"));
try {
const binDir = path.join(tmpDir, "bin");
const homeDir = path.join(tmpDir, "home");
@@ -35,12 +35,12 @@ async function withLaunchctlStub(
[
'import fs from "node:fs";',
"const args = process.argv.slice(2);",
"const logPath = process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;",
"const logPath = process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;",
"if (logPath) {",
' fs.appendFileSync(logPath, JSON.stringify(args) + "\\n", "utf8");',
"}",
'if (args[0] === "list") {',
' const output = process.env.CLAWDBOT_TEST_LAUNCHCTL_LIST_OUTPUT || "";',
' const output = process.env.OPENCLAW_TEST_LAUNCHCTL_LIST_OUTPUT || "";',
" process.stdout.write(output);",
"}",
"process.exit(0);",
@@ -61,28 +61,28 @@ async function withLaunchctlStub(
await fs.chmod(shPath, 0o755);
}
process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG = logPath;
process.env.CLAWDBOT_TEST_LAUNCHCTL_LIST_OUTPUT = options.listOutput ?? "";
process.env.OPENCLAW_TEST_LAUNCHCTL_LOG = logPath;
process.env.OPENCLAW_TEST_LAUNCHCTL_LIST_OUTPUT = options.listOutput ?? "";
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
await run({
env: {
HOME: homeDir,
CLAWDBOT_PROFILE: "default",
OPENCLAW_PROFILE: "default",
},
logPath,
});
} finally {
process.env.PATH = originalPath;
if (originalLogPath === undefined) {
delete process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;
delete process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;
} else {
process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG = originalLogPath;
process.env.OPENCLAW_TEST_LAUNCHCTL_LOG = originalLogPath;
}
if (originalListOutput === undefined) {
delete process.env.CLAWDBOT_TEST_LAUNCHCTL_LIST_OUTPUT;
delete process.env.OPENCLAW_TEST_LAUNCHCTL_LIST_OUTPUT;
} else {
process.env.CLAWDBOT_TEST_LAUNCHCTL_LIST_OUTPUT = originalListOutput;
process.env.OPENCLAW_TEST_LAUNCHCTL_LIST_OUTPUT = originalListOutput;
}
await fs.rm(tmpDir, { recursive: true, force: true });
}
@@ -107,7 +107,7 @@ describe("launchd runtime parsing", () => {
describe("launchctl list detection", () => {
it("detects the resolved label in launchctl list", async () => {
await withLaunchctlStub({ listOutput: "123 0 bot.molt.gateway\n" }, async ({ env }) => {
await withLaunchctlStub({ listOutput: "123 0 ai.openclaw.gateway\n" }, async ({ env }) => {
const listed = await isLaunchAgentListed({ env });
expect(listed).toBe(true);
});
@@ -133,7 +133,7 @@ describe("launchd bootstrap repair", () => {
.map((line) => JSON.parse(line) as string[]);
const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501";
const label = "bot.molt.gateway";
const label = "ai.openclaw.gateway";
const plistPath = resolveLaunchAgentPlistPath(env);
expect(calls).toContainEqual(["bootstrap", domain, plistPath]);
@@ -145,9 +145,9 @@ describe("launchd bootstrap repair", () => {
describe("launchd install", () => {
it("enables service before bootstrap (clears persisted disabled state)", async () => {
const originalPath = process.env.PATH;
const originalLogPath = process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;
const originalLogPath = process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-launchctl-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-test-"));
try {
const binDir = path.join(tmpDir, "bin");
const homeDir = path.join(tmpDir, "home");
@@ -160,7 +160,7 @@ describe("launchd install", () => {
stubJsPath,
[
'import fs from "node:fs";',
"const logPath = process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;",
"const logPath = process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;",
"if (logPath) {",
' fs.appendFileSync(logPath, JSON.stringify(process.argv.slice(2)) + "\\n", "utf8");',
"}",
@@ -182,12 +182,12 @@ describe("launchd install", () => {
await fs.chmod(shPath, 0o755);
}
process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG = logPath;
process.env.OPENCLAW_TEST_LAUNCHCTL_LOG = logPath;
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
const env: Record<string, string | undefined> = {
HOME: homeDir,
CLAWDBOT_PROFILE: "default",
OPENCLAW_PROFILE: "default",
};
await installLaunchAgent({
env,
@@ -201,7 +201,7 @@ describe("launchd install", () => {
.map((line) => JSON.parse(line) as string[]);
const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501";
const label = "bot.molt.gateway";
const label = "ai.openclaw.gateway";
const plistPath = resolveLaunchAgentPlistPath(env);
const serviceId = `${domain}/${label}`;
@@ -218,9 +218,9 @@ describe("launchd install", () => {
} finally {
process.env.PATH = originalPath;
if (originalLogPath === undefined) {
delete process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG;
delete process.env.OPENCLAW_TEST_LAUNCHCTL_LOG;
} else {
process.env.CLAWDBOT_TEST_LAUNCHCTL_LOG = originalLogPath;
process.env.OPENCLAW_TEST_LAUNCHCTL_LOG = originalLogPath;
}
await fs.rm(tmpDir, { recursive: true, force: true });
}
@@ -228,77 +228,77 @@ describe("launchd install", () => {
});
describe("resolveLaunchAgentPlistPath", () => {
it("uses default label when CLAWDBOT_PROFILE is default", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "default" };
it("uses default label when OPENCLAW_PROFILE is default", () => {
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "default" };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.gateway.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.gateway.plist",
);
});
it("uses default label when CLAWDBOT_PROFILE is unset", () => {
it("uses default label when OPENCLAW_PROFILE is unset", () => {
const env = { HOME: "/Users/test" };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.gateway.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.gateway.plist",
);
});
it("uses profile-specific label when CLAWDBOT_PROFILE is set to a custom value", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "jbphoenix" };
it("uses profile-specific label when OPENCLAW_PROFILE is set to a custom value", () => {
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "jbphoenix" };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.jbphoenix.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.jbphoenix.plist",
);
});
it("prefers CLAWDBOT_LAUNCHD_LABEL over CLAWDBOT_PROFILE", () => {
it("prefers OPENCLAW_LAUNCHD_LABEL over OPENCLAW_PROFILE", () => {
const env = {
HOME: "/Users/test",
CLAWDBOT_PROFILE: "jbphoenix",
CLAWDBOT_LAUNCHD_LABEL: "com.custom.label",
OPENCLAW_PROFILE: "jbphoenix",
OPENCLAW_LAUNCHD_LABEL: "com.custom.label",
};
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/com.custom.label.plist",
);
});
it("trims whitespace from CLAWDBOT_LAUNCHD_LABEL", () => {
it("trims whitespace from OPENCLAW_LAUNCHD_LABEL", () => {
const env = {
HOME: "/Users/test",
CLAWDBOT_LAUNCHD_LABEL: " com.custom.label ",
OPENCLAW_LAUNCHD_LABEL: " com.custom.label ",
};
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/com.custom.label.plist",
);
});
it("ignores empty CLAWDBOT_LAUNCHD_LABEL and falls back to profile", () => {
it("ignores empty OPENCLAW_LAUNCHD_LABEL and falls back to profile", () => {
const env = {
HOME: "/Users/test",
CLAWDBOT_PROFILE: "myprofile",
CLAWDBOT_LAUNCHD_LABEL: " ",
OPENCLAW_PROFILE: "myprofile",
OPENCLAW_LAUNCHD_LABEL: " ",
};
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.myprofile.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.myprofile.plist",
);
});
it("handles case-insensitive 'Default' profile", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "Default" };
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "Default" };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.gateway.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.gateway.plist",
);
});
it("handles case-insensitive 'DEFAULT' profile", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "DEFAULT" };
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "DEFAULT" };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.gateway.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.gateway.plist",
);
});
it("trims whitespace from CLAWDBOT_PROFILE", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: " myprofile " };
it("trims whitespace from OPENCLAW_PROFILE", () => {
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: " myprofile " };
expect(resolveLaunchAgentPlistPath(env)).toBe(
"/Users/test/Library/LaunchAgents/bot.molt.myprofile.plist",
"/Users/test/Library/LaunchAgents/ai.openclaw.myprofile.plist",
);
});
});

View File

@@ -27,9 +27,9 @@ const formatLine = (label: string, value: string) => {
};
function resolveLaunchAgentLabel(args?: { env?: Record<string, string | undefined> }): string {
const envLabel = args?.env?.CLAWDBOT_LAUNCHD_LABEL?.trim();
const envLabel = args?.env?.OPENCLAW_LAUNCHD_LABEL?.trim();
if (envLabel) return envLabel;
return resolveGatewayLaunchAgentLabel(args?.env?.CLAWDBOT_PROFILE);
return resolveGatewayLaunchAgentLabel(args?.env?.OPENCLAW_PROFILE);
}
function resolveLaunchAgentPlistPathForLabel(
@@ -52,7 +52,7 @@ export function resolveGatewayLogPaths(env: Record<string, string | undefined>):
} {
const stateDir = resolveGatewayStateDir(env);
const logDir = path.join(stateDir, "logs");
const prefix = env.CLAWDBOT_LOG_PREFIX?.trim() || "gateway";
const prefix = env.OPENCLAW_LOG_PREFIX?.trim() || "gateway";
return {
logDir,
stdoutPath: path.join(logDir, `${prefix}.log`),
@@ -248,7 +248,7 @@ export async function findLegacyLaunchAgents(
): Promise<LegacyLaunchAgent[]> {
const domain = resolveGuiDomain();
const results: LegacyLaunchAgent[] = [];
for (const label of resolveLegacyGatewayLaunchAgentLabels(env.CLAWDBOT_PROFILE)) {
for (const label of resolveLegacyGatewayLaunchAgentLabels(env.OPENCLAW_PROFILE)) {
const plistPath = resolveLaunchAgentPlistPathForLabel(env, label);
const res = await execLaunchctl(["print", `${domain}/${label}`]);
const loaded = res.code === 0;
@@ -384,7 +384,7 @@ export async function installLaunchAgent({
const domain = resolveGuiDomain();
const label = resolveLaunchAgentLabel({ env });
for (const legacyLabel of resolveLegacyGatewayLaunchAgentLabels(env.CLAWDBOT_PROFILE)) {
for (const legacyLabel of resolveLegacyGatewayLaunchAgentLabels(env.OPENCLAW_PROFILE)) {
const legacyPlistPath = resolveLaunchAgentPlistPathForLabel(env, legacyLabel);
await execLaunchctl(["bootout", domain, legacyPlistPath]);
await execLaunchctl(["unload", legacyPlistPath]);
@@ -401,8 +401,8 @@ export async function installLaunchAgent({
const serviceDescription =
description ??
formatGatewayServiceDescription({
profile: env.CLAWDBOT_PROFILE,
version: environment?.CLAWDBOT_SERVICE_VERSION ?? env.CLAWDBOT_SERVICE_VERSION,
profile: env.OPENCLAW_PROFILE,
version: environment?.OPENCLAW_SERVICE_VERSION ?? env.OPENCLAW_SERVICE_VERSION,
});
const plist = buildLaunchAgentPlist({
label,

View File

@@ -1,70 +0,0 @@
import { findLegacyLaunchAgents, uninstallLegacyLaunchAgents } from "./launchd.js";
import { findLegacySystemdUnits, uninstallLegacySystemdUnits } from "./systemd.js";
export type LegacyGatewayService = {
platform: "darwin" | "linux" | "win32";
label: string;
detail: string;
};
function formatLegacyLaunchAgents(
agents: Awaited<ReturnType<typeof findLegacyLaunchAgents>>,
): LegacyGatewayService[] {
return agents.map((agent) => ({
platform: "darwin",
label: agent.label,
detail: [
agent.loaded ? "loaded" : "not loaded",
agent.exists ? `plist: ${agent.plistPath}` : "plist missing",
].join(", "),
}));
}
function formatLegacySystemdUnits(
units: Awaited<ReturnType<typeof findLegacySystemdUnits>>,
): LegacyGatewayService[] {
return units.map((unit) => ({
platform: "linux",
label: `${unit.name}.service`,
detail: [
unit.enabled ? "enabled" : "disabled",
unit.exists ? `unit: ${unit.unitPath}` : "unit missing",
].join(", "),
}));
}
export async function findLegacyGatewayServices(
env: Record<string, string | undefined>,
): Promise<LegacyGatewayService[]> {
if (process.platform === "darwin") {
const agents = await findLegacyLaunchAgents(env);
return formatLegacyLaunchAgents(agents);
}
if (process.platform === "linux") {
const units = await findLegacySystemdUnits(env);
return formatLegacySystemdUnits(units);
}
return [];
}
export async function uninstallLegacyGatewayServices({
env,
stdout,
}: {
env: Record<string, string | undefined>;
stdout: NodeJS.WritableStream;
}): Promise<LegacyGatewayService[]> {
if (process.platform === "darwin") {
const agents = await uninstallLegacyLaunchAgents({ env, stdout });
return formatLegacyLaunchAgents(agents);
}
if (process.platform === "linux") {
const units = await uninstallLegacySystemdUnits({ env, stdout });
return formatLegacySystemdUnits(units);
}
return [];
}

View File

@@ -14,13 +14,13 @@ function withNodeServiceEnv(
): Record<string, string | undefined> {
return {
...env,
CLAWDBOT_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
CLAWDBOT_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
CLAWDBOT_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
CLAWDBOT_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
CLAWDBOT_LOG_PREFIX: "node",
CLAWDBOT_SERVICE_MARKER: NODE_SERVICE_MARKER,
CLAWDBOT_SERVICE_KIND: NODE_SERVICE_KIND,
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
OPENCLAW_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
OPENCLAW_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
OPENCLAW_LOG_PREFIX: "node",
OPENCLAW_SERVICE_MARKER: NODE_SERVICE_MARKER,
OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND,
};
}
@@ -30,13 +30,13 @@ function withNodeInstallEnv(args: GatewayServiceInstallArgs): GatewayServiceInst
env: withNodeServiceEnv(args.env),
environment: {
...args.environment,
CLAWDBOT_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
CLAWDBOT_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
CLAWDBOT_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
CLAWDBOT_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
CLAWDBOT_LOG_PREFIX: "node",
CLAWDBOT_SERVICE_MARKER: NODE_SERVICE_MARKER,
CLAWDBOT_SERVICE_KIND: NODE_SERVICE_KIND,
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
OPENCLAW_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
OPENCLAW_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
OPENCLAW_LOG_PREFIX: "node",
OPENCLAW_SERVICE_MARKER: NODE_SERVICE_MARKER,
OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND,
},
};
}

View File

@@ -7,31 +7,31 @@ import { resolveGatewayStateDir } from "./paths.js";
describe("resolveGatewayStateDir", () => {
it("uses the default state dir when no overrides are set", () => {
const env = { HOME: "/Users/test" };
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".clawdbot"));
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".openclaw"));
});
it("appends the profile suffix when set", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "rescue" };
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".clawdbot-rescue"));
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "rescue" };
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".openclaw-rescue"));
});
it("treats default profiles as the base state dir", () => {
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "Default" };
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".clawdbot"));
const env = { HOME: "/Users/test", OPENCLAW_PROFILE: "Default" };
expect(resolveGatewayStateDir(env)).toBe(path.join("/Users/test", ".openclaw"));
});
it("uses CLAWDBOT_STATE_DIR when provided", () => {
const env = { HOME: "/Users/test", CLAWDBOT_STATE_DIR: "/var/lib/moltbot" };
expect(resolveGatewayStateDir(env)).toBe(path.resolve("/var/lib/moltbot"));
it("uses OPENCLAW_STATE_DIR when provided", () => {
const env = { HOME: "/Users/test", OPENCLAW_STATE_DIR: "/var/lib/openclaw" };
expect(resolveGatewayStateDir(env)).toBe(path.resolve("/var/lib/openclaw"));
});
it("expands ~ in CLAWDBOT_STATE_DIR", () => {
const env = { HOME: "/Users/test", CLAWDBOT_STATE_DIR: "~/moltbot-state" };
expect(resolveGatewayStateDir(env)).toBe(path.resolve("/Users/test/moltbot-state"));
it("expands ~ in OPENCLAW_STATE_DIR", () => {
const env = { HOME: "/Users/test", OPENCLAW_STATE_DIR: "~/openclaw-state" };
expect(resolveGatewayStateDir(env)).toBe(path.resolve("/Users/test/openclaw-state"));
});
it("preserves Windows absolute paths without HOME", () => {
const env = { CLAWDBOT_STATE_DIR: "C:\\State\\moltbot" };
expect(resolveGatewayStateDir(env)).toBe("C:\\State\\moltbot");
const env = { OPENCLAW_STATE_DIR: "C:\\State\\openclaw" };
expect(resolveGatewayStateDir(env)).toBe("C:\\State\\openclaw");
});
});

View File

@@ -26,12 +26,12 @@ export function resolveUserPathWithHome(input: string, home?: string): string {
}
export function resolveGatewayStateDir(env: Record<string, string | undefined>): string {
const override = env.CLAWDBOT_STATE_DIR?.trim();
const override = env.OPENCLAW_STATE_DIR?.trim();
if (override) {
const home = override.startsWith("~") ? resolveHomeDir(env) : undefined;
return resolveUserPathWithHome(override, home);
}
const home = resolveHomeDir(env);
const suffix = resolveGatewayProfileSuffix(env.CLAWDBOT_PROFILE);
return path.join(home, `.clawdbot${suffix}`);
const suffix = resolveGatewayProfileSuffix(env.OPENCLAW_PROFILE);
return path.join(home, `.openclaw${suffix}`);
}

View File

@@ -23,8 +23,8 @@ afterEach(() => {
describe("resolveGatewayProgramArguments", () => {
it("uses realpath-resolved dist entry when running via npx shim", async () => {
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/moltbot");
const entryPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/moltbot/dist/entry.js");
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/openclaw");
const entryPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/openclaw/dist/entry.js");
process.argv = ["node", argv1];
fsMocks.realpath.mockResolvedValue(entryPath);
fsMocks.access.mockImplementation(async (target: string) => {
@@ -46,13 +46,13 @@ describe("resolveGatewayProgramArguments", () => {
});
it("prefers symlinked path over realpath for stable service config", async () => {
// Simulates pnpm global install where node_modules/moltbot is a symlink
// to .pnpm/moltbot@X.Y.Z/node_modules/moltbot
// Simulates pnpm global install where node_modules/openclaw is a symlink
// to .pnpm/openclaw@X.Y.Z/node_modules/openclaw
const symlinkPath = path.resolve(
"/Users/test/Library/pnpm/global/5/node_modules/moltbot/dist/entry.js",
"/Users/test/Library/pnpm/global/5/node_modules/openclaw/dist/entry.js",
);
const realpathResolved = path.resolve(
"/Users/test/Library/pnpm/global/5/node_modules/.pnpm/moltbot@2026.1.21-2/node_modules/moltbot/dist/entry.js",
"/Users/test/Library/pnpm/global/5/node_modules/.pnpm/openclaw@2026.1.21-2/node_modules/openclaw/dist/entry.js",
);
process.argv = ["node", symlinkPath];
fsMocks.realpath.mockResolvedValue(realpathResolved);
@@ -66,8 +66,8 @@ describe("resolveGatewayProgramArguments", () => {
});
it("falls back to node_modules package dist when .bin path is not resolved", async () => {
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/moltbot");
const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/moltbot/dist/index.js");
const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/openclaw");
const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/openclaw/dist/index.js");
process.argv = ["node", argv1];
fsMocks.realpath.mockRejectedValue(new Error("no realpath"));
fsMocks.access.mockImplementation(async (target: string) => {

View File

@@ -29,7 +29,7 @@ async function resolveCliEntrypointPathForService(): Promise<string> {
await fs.access(resolvedPath);
// Prefer the original (possibly symlinked) path over the resolved realpath.
// This keeps LaunchAgent/systemd paths stable across package version updates,
// since symlinks like node_modules/moltbot -> .pnpm/moltbot@X.Y.Z/...
// since symlinks like node_modules/openclaw -> .pnpm/openclaw@X.Y.Z/...
// are automatically updated by pnpm, while the resolved path contains
// version-specific directories that break after updates.
const normalizedLooksLikeDist = /[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(normalized);

View File

@@ -9,7 +9,7 @@ import { parseSchtasksQuery, readScheduledTaskCommand, resolveTaskScriptPath } f
describe("schtasks runtime parsing", () => {
it("parses status and last run info", () => {
const output = [
"TaskName: \\Moltbot Gateway",
"TaskName: \\OpenClaw Gateway",
"Status: Ready",
"Last Run Time: 1/8/2026 1:23:45 AM",
"Last Run Result: 0x0",
@@ -23,7 +23,7 @@ describe("schtasks runtime parsing", () => {
it("parses running status", () => {
const output = [
"TaskName: \\Moltbot Gateway",
"TaskName: \\OpenClaw Gateway",
"Status: Running",
"Last Run Time: 1/8/2026 1:23:45 AM",
"Last Run Result: 0x0",
@@ -37,68 +37,68 @@ describe("schtasks runtime parsing", () => {
});
describe("resolveTaskScriptPath", () => {
it("uses default path when CLAWDBOT_PROFILE is default", () => {
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "default" };
it("uses default path when OPENCLAW_PROFILE is default", () => {
const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "default" };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"),
);
});
it("uses default path when CLAWDBOT_PROFILE is unset", () => {
it("uses default path when OPENCLAW_PROFILE is unset", () => {
const env = { USERPROFILE: "C:\\Users\\test" };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"),
);
});
it("uses profile-specific path when CLAWDBOT_PROFILE is set to a custom value", () => {
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "jbphoenix" };
it("uses profile-specific path when OPENCLAW_PROFILE is set to a custom value", () => {
const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "jbphoenix" };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot-jbphoenix", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw-jbphoenix", "gateway.cmd"),
);
});
it("prefers CLAWDBOT_STATE_DIR over profile-derived defaults", () => {
it("prefers OPENCLAW_STATE_DIR over profile-derived defaults", () => {
const env = {
USERPROFILE: "C:\\Users\\test",
CLAWDBOT_PROFILE: "rescue",
CLAWDBOT_STATE_DIR: "C:\\State\\moltbot",
OPENCLAW_PROFILE: "rescue",
OPENCLAW_STATE_DIR: "C:\\State\\openclaw",
};
expect(resolveTaskScriptPath(env)).toBe(path.join("C:\\State\\moltbot", "gateway.cmd"));
expect(resolveTaskScriptPath(env)).toBe(path.join("C:\\State\\openclaw", "gateway.cmd"));
});
it("handles case-insensitive 'Default' profile", () => {
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "Default" };
const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "Default" };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"),
);
});
it("handles case-insensitive 'DEFAULT' profile", () => {
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "DEFAULT" };
const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "DEFAULT" };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"),
);
});
it("trims whitespace from CLAWDBOT_PROFILE", () => {
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: " myprofile " };
it("trims whitespace from OPENCLAW_PROFILE", () => {
const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: " myprofile " };
expect(resolveTaskScriptPath(env)).toBe(
path.join("C:\\Users\\test", ".clawdbot-myprofile", "gateway.cmd"),
path.join("C:\\Users\\test", ".openclaw-myprofile", "gateway.cmd"),
);
});
it("falls back to HOME when USERPROFILE is not set", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "default" };
expect(resolveTaskScriptPath(env)).toBe(path.join("/home/test", ".clawdbot", "gateway.cmd"));
const env = { HOME: "/home/test", OPENCLAW_PROFILE: "default" };
expect(resolveTaskScriptPath(env)).toBe(path.join("/home/test", ".openclaw", "gateway.cmd"));
});
});
describe("readScheduledTaskCommand", () => {
it("parses basic command script", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
await fs.writeFile(
scriptPath,
@@ -106,7 +106,7 @@ describe("readScheduledTaskCommand", () => {
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["node", "gateway.js", "--port", "18789"],
@@ -117,21 +117,21 @@ describe("readScheduledTaskCommand", () => {
});
it("parses script with working directory", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
await fs.writeFile(
scriptPath,
["@echo off", "cd /d C:\\Projects\\moltbot", "node gateway.js"].join("\r\n"),
["@echo off", "cd /d C:\\Projects\\openclaw", "node gateway.js"].join("\r\n"),
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["node", "gateway.js"],
workingDirectory: "C:\\Projects\\moltbot",
workingDirectory: "C:\\Projects\\openclaw",
});
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
@@ -139,9 +139,9 @@ describe("readScheduledTaskCommand", () => {
});
it("parses script with environment variables", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
await fs.writeFile(
scriptPath,
@@ -149,7 +149,7 @@ describe("readScheduledTaskCommand", () => {
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["node", "gateway.js"],
@@ -164,9 +164,9 @@ describe("readScheduledTaskCommand", () => {
});
it("parses script with quoted arguments containing spaces", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
// Use forward slashes which work in Windows cmd and avoid escape parsing issues
await fs.writeFile(
@@ -175,7 +175,7 @@ describe("readScheduledTaskCommand", () => {
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["C:/Program Files/Node/node.exe", "gateway.js"],
@@ -186,9 +186,9 @@ describe("readScheduledTaskCommand", () => {
});
it("returns null when script does not exist", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toBeNull();
} finally {
@@ -197,9 +197,9 @@ describe("readScheduledTaskCommand", () => {
});
it("returns null when script has no command", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
await fs.writeFile(
scriptPath,
@@ -207,7 +207,7 @@ describe("readScheduledTaskCommand", () => {
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toBeNull();
} finally {
@@ -216,31 +216,31 @@ describe("readScheduledTaskCommand", () => {
});
it("parses full script with all components", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-schtasks-test-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-"));
try {
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd");
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
await fs.writeFile(
scriptPath,
[
"@echo off",
"rem Moltbot Gateway",
"cd /d C:\\Projects\\moltbot",
"rem OpenClaw Gateway",
"cd /d C:\\Projects\\openclaw",
"set NODE_ENV=production",
"set CLAWDBOT_PORT=18789",
"set OPENCLAW_PORT=18789",
"node gateway.js --verbose",
].join("\r\n"),
"utf8",
);
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["node", "gateway.js", "--verbose"],
workingDirectory: "C:\\Projects\\moltbot",
workingDirectory: "C:\\Projects\\openclaw",
environment: {
NODE_ENV: "production",
CLAWDBOT_PORT: "18789",
OPENCLAW_PORT: "18789",
},
});
} finally {

View File

@@ -17,15 +17,15 @@ const formatLine = (label: string, value: string) => {
};
function resolveTaskName(env: Record<string, string | undefined>): string {
const override = env.CLAWDBOT_WINDOWS_TASK_NAME?.trim();
const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
if (override) return override;
return resolveGatewayWindowsTaskName(env.CLAWDBOT_PROFILE);
return resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE);
}
export function resolveTaskScriptPath(env: Record<string, string | undefined>): string {
const override = env.CLAWDBOT_TASK_SCRIPT?.trim();
const override = env.OPENCLAW_TASK_SCRIPT?.trim();
if (override) return override;
const scriptName = env.CLAWDBOT_TASK_SCRIPT_NAME?.trim() || "gateway.cmd";
const scriptName = env.OPENCLAW_TASK_SCRIPT_NAME?.trim() || "gateway.cmd";
const stateDir = resolveGatewayStateDir(env);
return path.join(stateDir, scriptName);
}
@@ -225,8 +225,8 @@ export async function installScheduledTask({
const taskDescription =
description ??
formatGatewayServiceDescription({
profile: env.CLAWDBOT_PROFILE,
version: environment?.CLAWDBOT_SERVICE_VERSION ?? env.CLAWDBOT_SERVICE_VERSION,
profile: env.OPENCLAW_PROFILE,
version: environment?.OPENCLAW_SERVICE_VERSION ?? env.OPENCLAW_SERVICE_VERSION,
});
const script = buildTaskScript({
description: taskDescription,

View File

@@ -223,25 +223,25 @@ describe("buildServiceEnvironment", () => {
} else {
expect(env.PATH).toContain("/usr/bin");
}
expect(env.CLAWDBOT_GATEWAY_PORT).toBe("18789");
expect(env.CLAWDBOT_GATEWAY_TOKEN).toBe("secret");
expect(env.CLAWDBOT_SERVICE_MARKER).toBe("moltbot");
expect(env.CLAWDBOT_SERVICE_KIND).toBe("gateway");
expect(typeof env.CLAWDBOT_SERVICE_VERSION).toBe("string");
expect(env.CLAWDBOT_SYSTEMD_UNIT).toBe("moltbot-gateway.service");
expect(env.OPENCLAW_GATEWAY_PORT).toBe("18789");
expect(env.OPENCLAW_GATEWAY_TOKEN).toBe("secret");
expect(env.OPENCLAW_SERVICE_MARKER).toBe("openclaw");
expect(env.OPENCLAW_SERVICE_KIND).toBe("gateway");
expect(typeof env.OPENCLAW_SERVICE_VERSION).toBe("string");
expect(env.OPENCLAW_SYSTEMD_UNIT).toBe("openclaw-gateway.service");
if (process.platform === "darwin") {
expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("bot.molt.gateway");
expect(env.OPENCLAW_LAUNCHD_LABEL).toBe("ai.openclaw.gateway");
}
});
it("uses profile-specific unit and label", () => {
const env = buildServiceEnvironment({
env: { HOME: "/home/user", CLAWDBOT_PROFILE: "work" },
env: { HOME: "/home/user", OPENCLAW_PROFILE: "work" },
port: 18789,
});
expect(env.CLAWDBOT_SYSTEMD_UNIT).toBe("moltbot-gateway-work.service");
expect(env.OPENCLAW_SYSTEMD_UNIT).toBe("openclaw-gateway-work.service");
if (process.platform === "darwin") {
expect(env.CLAWDBOT_LAUNCHD_LABEL).toBe("bot.molt.work");
expect(env.OPENCLAW_LAUNCHD_LABEL).toBe("ai.openclaw.work");
}
});
});

View File

@@ -131,24 +131,26 @@ export function buildServiceEnvironment(params: {
launchdLabel?: string;
}): Record<string, string | undefined> {
const { env, port, token, launchdLabel } = params;
const profile = env.CLAWDBOT_PROFILE;
const profile = env.OPENCLAW_PROFILE;
const resolvedLaunchdLabel =
launchdLabel ||
(process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : undefined);
const systemdUnit = `${resolveGatewaySystemdServiceName(profile)}.service`;
const stateDir = env.OPENCLAW_STATE_DIR;
const configPath = env.OPENCLAW_CONFIG_PATH;
return {
HOME: env.HOME,
PATH: buildMinimalServicePath({ env }),
CLAWDBOT_PROFILE: profile,
CLAWDBOT_STATE_DIR: env.CLAWDBOT_STATE_DIR,
CLAWDBOT_CONFIG_PATH: env.CLAWDBOT_CONFIG_PATH,
CLAWDBOT_GATEWAY_PORT: String(port),
CLAWDBOT_GATEWAY_TOKEN: token,
CLAWDBOT_LAUNCHD_LABEL: resolvedLaunchdLabel,
CLAWDBOT_SYSTEMD_UNIT: systemdUnit,
CLAWDBOT_SERVICE_MARKER: GATEWAY_SERVICE_MARKER,
CLAWDBOT_SERVICE_KIND: GATEWAY_SERVICE_KIND,
CLAWDBOT_SERVICE_VERSION: VERSION,
OPENCLAW_PROFILE: profile,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_GATEWAY_PORT: String(port),
OPENCLAW_GATEWAY_TOKEN: token,
OPENCLAW_LAUNCHD_LABEL: resolvedLaunchdLabel,
OPENCLAW_SYSTEMD_UNIT: systemdUnit,
OPENCLAW_SERVICE_MARKER: GATEWAY_SERVICE_MARKER,
OPENCLAW_SERVICE_KIND: GATEWAY_SERVICE_KIND,
OPENCLAW_SERVICE_VERSION: VERSION,
};
}
@@ -156,18 +158,20 @@ export function buildNodeServiceEnvironment(params: {
env: Record<string, string | undefined>;
}): Record<string, string | undefined> {
const { env } = params;
const stateDir = env.OPENCLAW_STATE_DIR;
const configPath = env.OPENCLAW_CONFIG_PATH;
return {
HOME: env.HOME,
PATH: buildMinimalServicePath({ env }),
CLAWDBOT_STATE_DIR: env.CLAWDBOT_STATE_DIR,
CLAWDBOT_CONFIG_PATH: env.CLAWDBOT_CONFIG_PATH,
CLAWDBOT_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
CLAWDBOT_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
CLAWDBOT_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
CLAWDBOT_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
CLAWDBOT_LOG_PREFIX: "node",
CLAWDBOT_SERVICE_MARKER: NODE_SERVICE_MARKER,
CLAWDBOT_SERVICE_KIND: NODE_SERVICE_KIND,
CLAWDBOT_SERVICE_VERSION: VERSION,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
OPENCLAW_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
OPENCLAW_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
OPENCLAW_LOG_PREFIX: "node",
OPENCLAW_SERVICE_MARKER: NODE_SERVICE_MARKER,
OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND,
OPENCLAW_SERVICE_VERSION: VERSION,
};
}

View File

@@ -22,6 +22,6 @@ export function renderSystemdUnavailableHints(options: { wsl?: boolean } = {}):
}
return [
"systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
`If you're in a container, run the gateway in the foreground instead of \`${formatCliCommand("moltbot gateway")}\`.`,
`If you're in a container, run the gateway in the foreground instead of \`${formatCliCommand("openclaw gateway")}\`.`,
];
}

View File

@@ -4,9 +4,9 @@ import { parseSystemdExecStart } from "./systemd-unit.js";
describe("parseSystemdExecStart", () => {
it("splits on whitespace outside quotes", () => {
const execStart = "/usr/bin/moltbot gateway start --foo bar";
const execStart = "/usr/bin/openclaw gateway start --foo bar";
expect(parseSystemdExecStart(execStart)).toEqual([
"/usr/bin/moltbot",
"/usr/bin/openclaw",
"gateway",
"start",
"--foo",
@@ -15,9 +15,9 @@ describe("parseSystemdExecStart", () => {
});
it("preserves quoted arguments", () => {
const execStart = '/usr/bin/moltbot gateway start --name "My Bot"';
const execStart = '/usr/bin/openclaw gateway start --name "My Bot"';
expect(parseSystemdExecStart(execStart)).toEqual([
"/usr/bin/moltbot",
"/usr/bin/openclaw",
"gateway",
"start",
"--name",
@@ -26,13 +26,13 @@ describe("parseSystemdExecStart", () => {
});
it("parses path arguments", () => {
const execStart = "/usr/bin/moltbot gateway start --path /tmp/moltbot";
const execStart = "/usr/bin/openclaw gateway start --path /tmp/openclaw";
expect(parseSystemdExecStart(execStart)).toEqual([
"/usr/bin/moltbot",
"/usr/bin/openclaw",
"gateway",
"start",
"--path",
"/tmp/moltbot",
"/tmp/openclaw",
]);
});
});

View File

@@ -26,7 +26,7 @@ export function buildSystemdUnit({
environment?: Record<string, string | undefined>;
}): string {
const execStart = programArguments.map(systemdEscapeArg).join(" ");
const descriptionLine = `Description=${description?.trim() || "Moltbot Gateway"}`;
const descriptionLine = `Description=${description?.trim() || "OpenClaw Gateway"}`;
const workingDirLine = workingDirectory
? `WorkingDirectory=${systemdEscapeArg(workingDirectory)}`
: null;

View File

@@ -21,52 +21,52 @@ describe("systemd runtime parsing", () => {
});
describe("resolveSystemdUserUnitPath", () => {
it("uses default service name when CLAWDBOT_PROFILE is default", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "default" };
it("uses default service name when OPENCLAW_PROFILE is default", () => {
const env = { HOME: "/home/test", OPENCLAW_PROFILE: "default" };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway.service",
"/home/test/.config/systemd/user/openclaw-gateway.service",
);
});
it("uses default service name when CLAWDBOT_PROFILE is unset", () => {
it("uses default service name when OPENCLAW_PROFILE is unset", () => {
const env = { HOME: "/home/test" };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway.service",
"/home/test/.config/systemd/user/openclaw-gateway.service",
);
});
it("uses profile-specific service name when CLAWDBOT_PROFILE is set to a custom value", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "jbphoenix" };
it("uses profile-specific service name when OPENCLAW_PROFILE is set to a custom value", () => {
const env = { HOME: "/home/test", OPENCLAW_PROFILE: "jbphoenix" };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway-jbphoenix.service",
"/home/test/.config/systemd/user/openclaw-gateway-jbphoenix.service",
);
});
it("prefers CLAWDBOT_SYSTEMD_UNIT over CLAWDBOT_PROFILE", () => {
it("prefers OPENCLAW_SYSTEMD_UNIT over OPENCLAW_PROFILE", () => {
const env = {
HOME: "/home/test",
CLAWDBOT_PROFILE: "jbphoenix",
CLAWDBOT_SYSTEMD_UNIT: "custom-unit",
OPENCLAW_PROFILE: "jbphoenix",
OPENCLAW_SYSTEMD_UNIT: "custom-unit",
};
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/custom-unit.service",
);
});
it("handles CLAWDBOT_SYSTEMD_UNIT with .service suffix", () => {
it("handles OPENCLAW_SYSTEMD_UNIT with .service suffix", () => {
const env = {
HOME: "/home/test",
CLAWDBOT_SYSTEMD_UNIT: "custom-unit.service",
OPENCLAW_SYSTEMD_UNIT: "custom-unit.service",
};
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/custom-unit.service",
);
});
it("trims whitespace from CLAWDBOT_SYSTEMD_UNIT", () => {
it("trims whitespace from OPENCLAW_SYSTEMD_UNIT", () => {
const env = {
HOME: "/home/test",
CLAWDBOT_SYSTEMD_UNIT: " custom-unit ",
OPENCLAW_SYSTEMD_UNIT: " custom-unit ",
};
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/custom-unit.service",
@@ -74,23 +74,23 @@ describe("resolveSystemdUserUnitPath", () => {
});
it("handles case-insensitive 'Default' profile", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "Default" };
const env = { HOME: "/home/test", OPENCLAW_PROFILE: "Default" };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway.service",
"/home/test/.config/systemd/user/openclaw-gateway.service",
);
});
it("handles case-insensitive 'DEFAULT' profile", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "DEFAULT" };
const env = { HOME: "/home/test", OPENCLAW_PROFILE: "DEFAULT" };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway.service",
"/home/test/.config/systemd/user/openclaw-gateway.service",
);
});
it("trims whitespace from CLAWDBOT_PROFILE", () => {
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: " myprofile " };
it("trims whitespace from OPENCLAW_PROFILE", () => {
const env = { HOME: "/home/test", OPENCLAW_PROFILE: " myprofile " };
expect(resolveSystemdUserUnitPath(env)).toBe(
"/home/test/.config/systemd/user/moltbot-gateway-myprofile.service",
"/home/test/.config/systemd/user/openclaw-gateway-myprofile.service",
);
});
});

View File

@@ -39,11 +39,11 @@ function resolveSystemdUnitPathForName(
}
function resolveSystemdServiceName(env: Record<string, string | undefined>): string {
const override = env.CLAWDBOT_SYSTEMD_UNIT?.trim();
const override = env.OPENCLAW_SYSTEMD_UNIT?.trim();
if (override) {
return override.endsWith(".service") ? override.slice(0, -".service".length) : override;
}
return resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
return resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE);
}
function resolveSystemdUnitPath(env: Record<string, string | undefined>): string {
@@ -202,8 +202,8 @@ export async function installSystemdService({
const serviceDescription =
description ??
formatGatewayServiceDescription({
profile: env.CLAWDBOT_PROFILE,
version: environment?.CLAWDBOT_SERVICE_VERSION ?? env.CLAWDBOT_SERVICE_VERSION,
profile: env.OPENCLAW_PROFILE,
version: environment?.OPENCLAW_SERVICE_VERSION ?? env.OPENCLAW_SERVICE_VERSION,
});
const unit = buildSystemdUnit({
description: serviceDescription,
@@ -213,7 +213,7 @@ export async function installSystemdService({
});
await fs.writeFile(unitPath, unit, "utf8");
const serviceName = resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
const serviceName = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE);
const unitName = `${serviceName}.service`;
const reload = await execSystemctl(["--user", "daemon-reload"]);
if (reload.code !== 0) {
@@ -244,7 +244,7 @@ export async function uninstallSystemdService({
stdout: NodeJS.WritableStream;
}): Promise<void> {
await assertSystemdAvailable();
const serviceName = resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
const serviceName = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE);
const unitName = `${serviceName}.service`;
await execSystemctl(["--user", "disable", "--now", unitName]);