File size: 2,340 Bytes
f778c12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { assertNoSystemLaunchDaemonOwnership } from "../../daemon/launchd-system.js";
// macOS LaunchAgent recovery helper for daemon lifecycle commands.
import {
  formatLaunchAgentGuiSessionError,
  launchAgentPlistExists,
  repairLaunchAgentBootstrap,
  resolveLaunchAgentLabel,
} from "../../daemon/launchd.js";

const LAUNCH_AGENT_RECOVERY_MESSAGE =
  "Gateway LaunchAgent was installed but not loaded; re-bootstrapped launchd service.";

type LaunchAgentRecoveryAction = "started" | "restarted";

type LaunchAgentRecoveryResult<TResult extends LaunchAgentRecoveryAction> = {
  result: TResult;
  loaded: true;
  message: string;
};

/** Re-bootstrap an installed but unloaded LaunchAgent after a daemon start/restart command. */
export async function recoverInstalledLaunchAgent<
  TResult extends LaunchAgentRecoveryAction,
>(params: {
  result: TResult;
  env?: Record<string, string | undefined>;
}): Promise<LaunchAgentRecoveryResult<TResult> | null> {
  if (process.platform !== "darwin") {
    return null;
  }
  const env = params.env ?? (process.env as Record<string, string | undefined>);
  // Check host-wide ownership even when no user plist exists. Otherwise start
  // and restart would report "not installed" for a gateway already supervised
  // by a same-label system LaunchDaemon.
  await assertNoSystemLaunchDaemonOwnership(resolveLaunchAgentLabel(env));
  const plistExists = await launchAgentPlistExists(env).catch(() => false);
  if (!plistExists) {
    return null;
  }
  const repaired = await repairLaunchAgentBootstrap({ env }).catch(() => ({
    ok: false as const,
    status: "bootstrap-failed" as const,
  }));
  if (!repaired.ok) {
    if (
      repaired.status === "system-launchdaemon-conflict" ||
      repaired.status === "system-launchdaemon-unverifiable"
    ) {
      throw new Error(repaired.detail);
    }
    if (repaired.status === "gui-session-unavailable") {
      const actionHint =
        params.result === "started" ? "openclaw gateway start" : "openclaw gateway restart";
      throw new Error(
        formatLaunchAgentGuiSessionError({
          detail: repaired.detail,
          domain: repaired.domain,
          actionHint,
        }),
      );
    }
    return null;
  }
  return {
    result: params.result,
    loaded: true,
    message: LAUNCH_AGENT_RECOVERY_MESSAGE,
  };
}