import { PassThrough } from "node:stream"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS, LAUNCH_AGENT_UMASK_DECIMAL, } from "./launchd-plist.js"; import { installLaunchAgent, isLaunchAgentListed, parseLaunchctlPrint, repairLaunchAgentBootstrap, restartLaunchAgent, resolveLaunchAgentPlistPath, stopLaunchAgent, } from "./launchd.js"; const state = vi.hoisted(() => ({ launchctlCalls: [] as string[][], listOutput: "", printOutput: "", printNotLoadedRemaining: 0, printError: "", printCode: 1, printFailuresRemaining: 0, bootstrapError: "", bootstrapCode: 1, kickstartError: "", kickstartFailuresRemaining: 0, disableError: "", disableCode: 1, stopError: "", stopCode: 1, bootoutError: "", bootoutCode: 1, serviceLoaded: true, serviceRunning: true, stopLeavesRunning: false, dirs: new Set(), dirModes: new Map(), files: new Map(), fileModes: new Map(), })); const launchdRestartHandoffState = vi.hoisted(() => ({ isCurrentProcessLaunchdServiceLabel: vi.fn<(label: string) => boolean>(() => false), scheduleDetachedLaunchdRestartHandoff: vi.fn((_params: unknown) => ({ ok: true, pid: 7331 })), })); const cleanStaleGatewayProcessesSync = vi.hoisted(() => vi.fn<(port?: number) => number[]>(() => []), ); const defaultProgramArguments = ["node", "-e", "process.exit(0)"]; function expectLaunchctlEnableBootstrapOrder(env: Record) { const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; const label = "ai.openclaw.gateway"; const plistPath = resolveLaunchAgentPlistPath(env); const serviceId = `${domain}/${label}`; const enableIndex = state.launchctlCalls.findIndex( (c) => c[0] === "enable" && c[1] === serviceId, ); const bootstrapIndex = state.launchctlCalls.findIndex( (c) => c[0] === "bootstrap" && c[1] === domain && c[2] === plistPath, ); expect(enableIndex).toBeGreaterThanOrEqual(0); expect(bootstrapIndex).toBeGreaterThanOrEqual(0); expect(enableIndex).toBeLessThan(bootstrapIndex); return { domain, label, serviceId, bootstrapIndex }; } function normalizeLaunchctlArgs(file: string, args: string[]): string[] { if (file === "launchctl") { return args; } const idx = args.indexOf("launchctl"); if (idx >= 0) { return args.slice(idx + 1); } return args; } vi.mock("./exec-file.js", () => ({ execFileUtf8: vi.fn(async (file: string, args: string[]) => { const call = normalizeLaunchctlArgs(file, args); state.launchctlCalls.push(call); if (call[0] === "list") { return { stdout: state.listOutput, stderr: "", code: 0 }; } if (call[0] === "print") { if (state.printNotLoadedRemaining > 0) { state.printNotLoadedRemaining -= 1; return { stdout: "", stderr: "Could not find service", code: 113 }; } if (state.printError && state.printFailuresRemaining > 0) { state.printFailuresRemaining -= 1; return { stdout: "", stderr: state.printError, code: state.printCode }; } if (!state.serviceLoaded) { return { stdout: "", stderr: "Could not find service", code: 113 }; } if (state.printOutput) { return { stdout: state.printOutput, stderr: "", code: 0 }; } if (!state.serviceRunning) { return { stdout: ["state = waiting", "pid = 0"].join("\n"), stderr: "", code: 0 }; } return { stdout: ["state = running", "pid = 4242"].join("\n"), stderr: "", code: 0 }; } if (call[0] === "disable" && state.disableError) { return { stdout: "", stderr: state.disableError, code: state.disableCode }; } if (call[0] === "stop") { if (state.stopError) { return { stdout: "", stderr: state.stopError, code: state.stopCode }; } if (!state.stopLeavesRunning) { state.serviceRunning = false; } return { stdout: "", stderr: "", code: 0 }; } if (call[0] === "bootout") { if (state.bootoutError) { return { stdout: "", stderr: state.bootoutError, code: state.bootoutCode }; } state.serviceLoaded = false; state.serviceRunning = false; return { stdout: "", stderr: "", code: 0 }; } if (call[0] === "enable") { return { stdout: "", stderr: "", code: 0 }; } if (call[0] === "bootstrap") { if (state.bootstrapError) { return { stdout: "", stderr: state.bootstrapError, code: state.bootstrapCode }; } state.serviceLoaded = true; state.serviceRunning = true; return { stdout: "", stderr: "", code: 0 }; } if (call[0] === "kickstart") { if (state.kickstartError && state.kickstartFailuresRemaining > 0) { state.kickstartFailuresRemaining -= 1; return { stdout: "", stderr: state.kickstartError, code: 1 }; } state.serviceLoaded = true; state.serviceRunning = true; return { stdout: "", stderr: "", code: 0 }; } return { stdout: "", stderr: "", code: 0 }; }), })); vi.mock("./launchd-restart-handoff.js", () => ({ isCurrentProcessLaunchdServiceLabel: (label: string) => launchdRestartHandoffState.isCurrentProcessLaunchdServiceLabel(label), scheduleDetachedLaunchdRestartHandoff: (params: unknown) => launchdRestartHandoffState.scheduleDetachedLaunchdRestartHandoff(params), })); vi.mock("../infra/restart-stale-pids.js", () => ({ cleanStaleGatewayProcessesSync: (port?: number) => cleanStaleGatewayProcessesSync(port), })); vi.mock("node:fs/promises", async () => { const actual = await vi.importActual("node:fs/promises"); const wrapped = { ...actual, access: vi.fn(async (p: string) => { const key = p; if (state.files.has(key) || state.dirs.has(key)) { return; } throw new Error(`ENOENT: no such file or directory, access '${key}'`); }), mkdir: vi.fn(async (p: string, opts?: { mode?: number }) => { const key = p; state.dirs.add(key); state.dirModes.set(key, opts?.mode ?? 0o777); }), stat: vi.fn(async (p: string) => { const key = p; if (state.dirs.has(key)) { return { mode: state.dirModes.get(key) ?? 0o777 }; } if (state.files.has(key)) { return { mode: state.fileModes.get(key) ?? 0o666 }; } throw new Error(`ENOENT: no such file or directory, stat '${key}'`); }), chmod: vi.fn(async (p: string, mode: number) => { const key = p; if (state.dirs.has(key)) { state.dirModes.set(key, mode); return; } if (state.files.has(key)) { state.fileModes.set(key, mode); return; } throw new Error(`ENOENT: no such file or directory, chmod '${key}'`); }), unlink: vi.fn(async (p: string) => { state.files.delete(p); }), writeFile: vi.fn(async (p: string, data: string, opts?: { mode?: number }) => { const key = p; state.files.set(key, data); state.dirs.add(key.split("/").slice(0, -1).join("/")); state.fileModes.set(key, opts?.mode ?? 0o666); }), }; return { ...wrapped, default: wrapped }; }); beforeEach(() => { state.launchctlCalls.length = 0; state.listOutput = ""; state.printOutput = ""; state.printNotLoadedRemaining = 0; state.printError = ""; state.printCode = 1; state.printFailuresRemaining = 0; state.bootstrapError = ""; state.bootstrapCode = 1; state.kickstartError = ""; state.kickstartFailuresRemaining = 0; state.disableError = ""; state.disableCode = 1; state.stopError = ""; state.stopCode = 1; state.bootoutError = ""; state.bootoutCode = 1; state.serviceLoaded = true; state.serviceRunning = true; state.stopLeavesRunning = false; state.dirs.clear(); state.dirModes.clear(); state.files.clear(); state.fileModes.clear(); cleanStaleGatewayProcessesSync.mockReset(); cleanStaleGatewayProcessesSync.mockReturnValue([]); launchdRestartHandoffState.isCurrentProcessLaunchdServiceLabel.mockReset(); launchdRestartHandoffState.isCurrentProcessLaunchdServiceLabel.mockReturnValue(false); launchdRestartHandoffState.scheduleDetachedLaunchdRestartHandoff.mockReset(); launchdRestartHandoffState.scheduleDetachedLaunchdRestartHandoff.mockReturnValue({ ok: true, pid: 7331, }); vi.clearAllMocks(); }); describe("launchd runtime parsing", () => { it("parses state, pid, and exit status", () => { const output = [ "state = running", "pid = 4242", "last exit status = 1", "last exit reason = exited", ].join("\n"); expect(parseLaunchctlPrint(output)).toEqual({ state: "running", pid: 4242, lastExitStatus: 1, lastExitReason: "exited", }); }); it("does not set pid when pid = 0", () => { const output = ["state = running", "pid = 0"].join("\n"); const info = parseLaunchctlPrint(output); expect(info.pid).toBeUndefined(); expect(info.state).toBe("running"); }); it("sets pid for positive values", () => { const output = ["state = running", "pid = 1234"].join("\n"); const info = parseLaunchctlPrint(output); expect(info.pid).toBe(1234); }); it("does not set pid for negative values", () => { const output = ["state = waiting", "pid = -1"].join("\n"); const info = parseLaunchctlPrint(output); expect(info.pid).toBeUndefined(); expect(info.state).toBe("waiting"); }); it("rejects pid and exit status values with junk suffixes", () => { const output = [ "state = waiting", "pid = 123abc", "last exit status = 7ms", "last exit reason = exited", ].join("\n"); expect(parseLaunchctlPrint(output)).toEqual({ state: "waiting", lastExitReason: "exited", }); }); }); describe("launchctl list detection", () => { it("detects the resolved label in launchctl list", async () => { state.listOutput = "123 0 ai.openclaw.gateway\n"; const listed = await isLaunchAgentListed({ env: { HOME: "/Users/test", OPENCLAW_PROFILE: "default" }, }); expect(listed).toBe(true); }); it("returns false when the label is missing", async () => { state.listOutput = "123 0 com.other.service\n"; const listed = await isLaunchAgentListed({ env: { HOME: "/Users/test", OPENCLAW_PROFILE: "default" }, }); expect(listed).toBe(false); }); }); describe("launchd bootstrap repair", () => { it("enables, bootstraps, and kickstarts the resolved label", async () => { const env: Record = { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; const repair = await repairLaunchAgentBootstrap({ env }); expect(repair).toEqual({ ok: true, status: "repaired" }); const { serviceId, bootstrapIndex } = expectLaunchctlEnableBootstrapOrder(env); const kickstartIndex = state.launchctlCalls.findIndex( (c) => c[0] === "kickstart" && c[1] === "-k" && c[2] === serviceId, ); expect(kickstartIndex).toBeGreaterThanOrEqual(0); expect(bootstrapIndex).toBeLessThan(kickstartIndex); }); it("treats bootstrap exit 130 as success", async () => { state.bootstrapError = "Service already loaded"; state.bootstrapCode = 130; const env: Record = { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; const repair = await repairLaunchAgentBootstrap({ env }); expect(repair).toEqual({ ok: true, status: "already-loaded" }); expect(state.launchctlCalls.filter((call) => call[0] === "kickstart")).toHaveLength(1); }); it("treats 'already exists in domain' bootstrap failures as success", async () => { state.bootstrapError = "Could not bootstrap service: 5: Input/output error: already exists in domain for gui/501"; const env: Record = { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; const repair = await repairLaunchAgentBootstrap({ env }); expect(repair).toEqual({ ok: true, status: "already-loaded" }); expect(state.launchctlCalls.filter((call) => call[0] === "kickstart")).toHaveLength(1); }); it("keeps genuine bootstrap failures as failures", async () => { state.bootstrapError = "Could not find specified service"; const env: Record = { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; const repair = await repairLaunchAgentBootstrap({ env }); expect(repair).toMatchObject({ ok: false, status: "bootstrap-failed", detail: expect.stringContaining("Could not find specified service"), }); expect(state.launchctlCalls.some((call) => call[0] === "kickstart")).toBe(false); }); it("returns a typed kickstart failure", async () => { state.kickstartError = "launchctl kickstart failed: permission denied"; state.kickstartFailuresRemaining = 1; const env: Record = { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; const repair = await repairLaunchAgentBootstrap({ env }); expect(repair).toEqual({ ok: false, status: "kickstart-failed", detail: "launchctl kickstart failed: permission denied", }); }); }); describe("launchd install", () => { function createDefaultLaunchdEnv(): Record { return { HOME: "/Users/test", OPENCLAW_PROFILE: "default", }; } it("enables service before bootstrap without self-restarting the fresh agent", async () => { const env = createDefaultLaunchdEnv(); await installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, }); const { serviceId } = expectLaunchctlEnableBootstrapOrder(env); const installKickstartIndex = state.launchctlCalls.findIndex( (c) => c[0] === "kickstart" && c[2] === serviceId, ); expect(installKickstartIndex).toBe(-1); }); it("writes TMPDIR to LaunchAgent environment when provided", async () => { const env = createDefaultLaunchdEnv(); const tmpDir = "/var/folders/xy/abc123/T/"; await installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, environment: { TMPDIR: tmpDir }, }); const plistPath = resolveLaunchAgentPlistPath(env); const plist = state.files.get(plistPath) ?? ""; expect(plist).toContain("EnvironmentVariables"); expect(plist).toContain("TMPDIR"); expect(plist).toContain(`${tmpDir}`); }); it("writes KeepAlive=true policy with restrictive umask", async () => { const env = createDefaultLaunchdEnv(); await installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, }); const plistPath = resolveLaunchAgentPlistPath(env); const plist = state.files.get(plistPath) ?? ""; expect(plist).toContain("KeepAlive"); expect(plist).toContain(""); expect(plist).not.toContain("SuccessfulExit"); expect(plist).toContain("Umask"); expect(plist).toContain(`${LAUNCH_AGENT_UMASK_DECIMAL}`); expect(plist).toContain("ThrottleInterval"); expect(plist).toContain(`${LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS}`); }); it("tightens writable bits on launch agent dirs and plist", async () => { const env = createDefaultLaunchdEnv(); state.dirs.add(env.HOME!); state.dirModes.set(env.HOME!, 0o777); state.dirs.add("/Users/test/Library"); state.dirModes.set("/Users/test/Library", 0o777); await installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, }); const plistPath = resolveLaunchAgentPlistPath(env); expect(state.dirModes.get(env.HOME!)).toBe(0o755); expect(state.dirModes.get("/Users/test/Library")).toBe(0o755); expect(state.dirModes.get("/Users/test/Library/LaunchAgents")).toBe(0o755); expect(state.fileModes.get(plistPath)).toBe(0o644); }); it("stops LaunchAgent by disabling relaunch before stopping the process", async () => { const env = createDefaultLaunchdEnv(); const stdout = new PassThrough(); let output = ""; stdout.on("data", (chunk: Buffer) => { output += chunk.toString(); }); await stopLaunchAgent({ env, stdout }); const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; const serviceId = `${domain}/ai.openclaw.gateway`; expect(state.launchctlCalls).toContainEqual(["disable", serviceId]); expect(state.launchctlCalls).toContainEqual(["stop", "ai.openclaw.gateway"]); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(false); expect(output).toContain("Stopped LaunchAgent"); }); it("falls back to bootout when disable fails so stop remains authoritative", async () => { const env = createDefaultLaunchdEnv(); const stdout = new PassThrough(); let output = ""; state.disableError = "Operation not permitted"; stdout.on("data", (chunk: Buffer) => { output += chunk.toString(); }); await stopLaunchAgent({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "stop")).toBe(false); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); expect(output).toContain("Stopped LaunchAgent (degraded)"); expect(output).toContain("used bootout fallback"); }); it("falls back to bootout when stop does not fully stop the service", async () => { const env = createDefaultLaunchdEnv(); const stdout = new PassThrough(); let output = ""; state.stopLeavesRunning = true; stdout.on("data", (chunk: Buffer) => { output += chunk.toString(); }); await stopLaunchAgent({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "stop")).toBe(true); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); expect(output).toContain("Stopped LaunchAgent (degraded)"); expect(output).toContain("did not fully stop the service"); }); it("falls back to bootout when launchctl print cannot confirm the stop state", async () => { const env = createDefaultLaunchdEnv(); const stdout = new PassThrough(); let output = ""; state.printError = "launchctl print permission denied"; state.printFailuresRemaining = 10; stdout.on("data", (chunk: Buffer) => { output += chunk.toString(); }); await stopLaunchAgent({ env, stdout }); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(true); expect(output).toContain("Stopped LaunchAgent (degraded)"); expect(output).toContain("could not confirm stop"); }); it("throws when launchctl print cannot confirm stop and bootout also fails", async () => { const env = createDefaultLaunchdEnv(); state.printError = "launchctl print permission denied"; state.printFailuresRemaining = 10; state.bootoutError = "launchctl bootout permission denied"; await expect(stopLaunchAgent({ env, stdout: new PassThrough() })).rejects.toThrow( "launchctl print could not confirm stop: launchctl print permission denied; launchctl bootout failed: launchctl bootout permission denied", ); }); it("restarts LaunchAgent with kickstart and no bootout", async () => { const env = { ...createDefaultLaunchdEnv(), OPENCLAW_GATEWAY_PORT: "18789", }; const result = await restartLaunchAgent({ env, stdout: new PassThrough(), }); const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; const label = "ai.openclaw.gateway"; const serviceId = `${domain}/${label}`; expect(result).toEqual({ outcome: "completed" }); expect(cleanStaleGatewayProcessesSync).toHaveBeenCalledWith(18789); expect(state.launchctlCalls).toContainEqual(["enable", serviceId]); expect(state.launchctlCalls).toContainEqual(["kickstart", "-k", serviceId]); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(false); expect(state.launchctlCalls.some((call) => call[0] === "bootstrap")).toBe(false); }); it("uses the configured gateway port for stale cleanup", async () => { const env = { ...createDefaultLaunchdEnv(), OPENCLAW_GATEWAY_PORT: "19001", }; await restartLaunchAgent({ env, stdout: new PassThrough(), }); expect(cleanStaleGatewayProcessesSync).toHaveBeenCalledWith(19001); }); it("skips stale cleanup when no explicit launch agent port can be resolved", async () => { const env = createDefaultLaunchdEnv(); state.files.clear(); await restartLaunchAgent({ env, stdout: new PassThrough(), }); expect(cleanStaleGatewayProcessesSync).not.toHaveBeenCalled(); }); it("falls back to bootstrap when kickstart cannot find the service", async () => { const env = createDefaultLaunchdEnv(); state.kickstartError = "Could not find service"; state.kickstartFailuresRemaining = 1; const result = await restartLaunchAgent({ env, stdout: new PassThrough(), }); const { serviceId } = expectLaunchctlEnableBootstrapOrder(env); const kickstartCalls = state.launchctlCalls.filter( (c) => c[0] === "kickstart" && c[1] === "-k" && c[2] === serviceId, ); expect(result).toEqual({ outcome: "completed" }); expect(kickstartCalls).toHaveLength(2); expect(state.launchctlCalls.some((call) => call[0] === "bootout")).toBe(false); }); it("surfaces the original kickstart failure when the service is still loaded", async () => { const env = createDefaultLaunchdEnv(); state.kickstartError = "Input/output error"; state.kickstartFailuresRemaining = 1; await expect( restartLaunchAgent({ env, stdout: new PassThrough(), }), ).rejects.toThrow("launchctl kickstart failed: Input/output error"); expect(state.launchctlCalls.some((call) => call[0] === "enable")).toBe(true); expect(state.launchctlCalls.some((call) => call[0] === "bootstrap")).toBe(false); }); it("re-bootstraps when kickstart failure leaves the service unloaded (#52208)", async () => { const env = createDefaultLaunchdEnv(); state.kickstartError = "Input/output error"; state.kickstartFailuresRemaining = 1; state.printNotLoadedRemaining = 1; await expect( restartLaunchAgent({ env, stdout: new PassThrough(), }), ).rejects.toThrow("launchctl kickstart failed: Input/output error"); expect(state.launchctlCalls.some((call) => call[0] === "enable")).toBe(true); expect(state.launchctlCalls.some((call) => call[0] === "bootstrap")).toBe(true); }); it("skips re-bootstrap when kickstart fails but service is still loaded (#52208)", async () => { const env = createDefaultLaunchdEnv(); state.kickstartError = "Input/output error"; state.kickstartFailuresRemaining = 1; await expect( restartLaunchAgent({ env, stdout: new PassThrough(), }), ).rejects.toThrow("launchctl kickstart failed: Input/output error"); expect(state.launchctlCalls.some((call) => call[0] === "enable")).toBe(true); expect(state.launchctlCalls.some((call) => call[0] === "bootstrap")).toBe(false); }); it("hands restart off to a detached helper when invoked from the current LaunchAgent", async () => { const env = createDefaultLaunchdEnv(); launchdRestartHandoffState.isCurrentProcessLaunchdServiceLabel.mockReturnValue(true); const result = await restartLaunchAgent({ env, stdout: new PassThrough(), }); expect(result).toEqual({ outcome: "scheduled" }); expect(launchdRestartHandoffState.scheduleDetachedLaunchdRestartHandoff).toHaveBeenCalledWith({ env, mode: "kickstart", waitForPid: process.pid, }); expect(state.launchctlCalls).toEqual([]); }); it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => { state.bootstrapError = "Bootstrap failed: 125: Domain does not support specified action"; const env = createDefaultLaunchdEnv(); let message = ""; try { await installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, }); } catch (error) { message = String(error); } expect(message).toContain("logged-in macOS GUI session"); expect(message).toContain("wrong user (including sudo)"); expect(message).toContain("https://docs.openclaw.ai/gateway"); }); it("surfaces generic bootstrap failures without GUI-specific guidance", async () => { state.bootstrapError = "Operation not permitted"; const env = createDefaultLaunchdEnv(); await expect( installLaunchAgent({ env, stdout: new PassThrough(), programArguments: defaultProgramArguments, }), ).rejects.toThrow("launchctl bootstrap failed: Operation not permitted"); }); }); describe("resolveLaunchAgentPlistPath", () => { it.each([ { name: "uses default label when OPENCLAW_PROFILE is unset", env: { HOME: "/Users/test" }, expected: "/Users/test/Library/LaunchAgents/ai.openclaw.gateway.plist", }, { name: "uses profile-specific label when OPENCLAW_PROFILE is set to a custom value", env: { HOME: "/Users/test", OPENCLAW_PROFILE: "jbphoenix" }, expected: "/Users/test/Library/LaunchAgents/ai.openclaw.jbphoenix.plist", }, { name: "prefers OPENCLAW_LAUNCHD_LABEL over OPENCLAW_PROFILE", env: { HOME: "/Users/test", OPENCLAW_PROFILE: "jbphoenix", OPENCLAW_LAUNCHD_LABEL: "com.custom.label", }, expected: "/Users/test/Library/LaunchAgents/com.custom.label.plist", }, { name: "trims whitespace from OPENCLAW_LAUNCHD_LABEL", env: { HOME: "/Users/test", OPENCLAW_LAUNCHD_LABEL: " com.custom.label ", }, expected: "/Users/test/Library/LaunchAgents/com.custom.label.plist", }, { name: "ignores empty OPENCLAW_LAUNCHD_LABEL and falls back to profile", env: { HOME: "/Users/test", OPENCLAW_PROFILE: "myprofile", OPENCLAW_LAUNCHD_LABEL: " ", }, expected: "/Users/test/Library/LaunchAgents/ai.openclaw.myprofile.plist", }, ])("$name", ({ env, expected }) => { expect(resolveLaunchAgentPlistPath(env)).toBe(expected); }); });