

















@@ -202,6 +202,34 @@ async function writeFakeSystemctl(): Promise<{ binDir: string; recordPath: strin
202202return { binDir, recordPath };
203203}
204204205+async function writeFakeLaunchctl(): Promise<{ binDir: string; recordPath: string }> {
206+const binDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-launchctl-bin-"));
207+tempDirs.add(binDir);
208+const recordPath = path.join(binDir, "launchctl-calls.log");
209+const countPath = path.join(binDir, "launchctl-kickstart-count");
210+await fs.writeFile(
211+path.join(binDir, "launchctl"),
212+`#!/bin/sh
213+echo "$@" >> '${recordPath}'
214+if [ "$1" = "kickstart" ]; then
215+ count=0
216+ if [ -f '${countPath}' ]; then
217+ count=$(cat '${countPath}')
218+ fi
219+ count=$((count + 1))
220+ echo "$count" > '${countPath}'
221+ [ "$count" -gt 1 ]
222+ exit $?
223+fi
224+[ "$1" = "enable" ] && exit 0
225+[ "$1" = "bootstrap" ] && exit 1
226+exit 1
227+`,
228+{ mode: 0o755 },
229+);
230+return { binDir, recordPath };
231+}
232+205233describe("managed service update handoff", () => {
206234it("strips process supervisor hints while preserving service identity for the CLI handoff", async () => {
207235const { startManagedServiceUpdateHandoff, stripSupervisorHintEnv } =
@@ -381,6 +409,31 @@ describe("managed service update handoff", () => {
381409await expect(pathExists(recordPath)).resolves.toBe(false);
382410});
383411412+it("retries launchd start when bootstrap reports an already-loaded label", async () => {
413+const { binDir, recordPath } = await writeFakeLaunchctl();
414+const result = await runHelperWithCommand({
415+commandArgv: [process.execPath, "-e", "process.exit(7)"],
416+serviceRecovery: {
417+kind: "launchd",
418+uid: 501,
419+label: "com.example.openclaw",
420+plistPath: "/Users/test/Library/LaunchAgents/com.example.openclaw.plist",
421+},
422+pathPrepend: binDir,
423+});
424+425+expect(result.code).toBe(7);
426+await expect(fs.readFile(recordPath, "utf-8")).resolves.toBe(
427+[
428+"kickstart gui/501/com.example.openclaw",
429+"enable gui/501/com.example.openclaw",
430+"bootstrap gui/501 /Users/test/Library/LaunchAgents/com.example.openclaw.plist",
431+"kickstart gui/501/com.example.openclaw",
432+"",
433+].join("\n"),
434+);
435+});
436+384437it("passes a gateway service recovery descriptor for each supervisor", async () => {
385438const { startManagedServiceUpdateHandoff } =
386439await import("./update-managed-service-handoff.js");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。