File size: 2,466 Bytes
d197cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/** Detects launchd service membership from environment markers or process ancestry. */
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { getSelfAndAncestorPidsSync } from "../infra/restart-stale-pids.js";
import { probeLaunchAgentState, resolveLaunchAgentGuiDomain } from "./launchd-runtime.js";

/** Checks whether the current process appears to be running under the requested launchd label. */
export function isCurrentProcessLaunchdServiceLabel(
  label: string,
  env: NodeJS.ProcessEnv = process.env,
): boolean {
  const currentLabels = [env.LAUNCH_JOB_LABEL, env.LAUNCH_JOB_NAME, env.XPC_SERVICE_NAME].flatMap(
    (value) => {
      const normalized = normalizeOptionalString(value);
      return normalized ? [normalized] : [];
    },
  );

  for (const currentLabel of currentLabels) {
    if (currentLabel === label) {
      return true;
    }
  }

  // Detached update/restart handoffs keep OPENCLAW_LAUNCHD_LABEL as the service
  // identity to manage while running outside the job, so the configured label
  // alone never proves membership: a restart that trusted it would schedule a
  // detached handoff instead of restarting and health-proving the service.
  // Managed wrappers inject the service marker; trust it when launchd's own
  // label variables are absent or renamed by the host environment.
  return (
    normalizeOptionalString(env.OPENCLAW_LAUNCHD_LABEL) === label &&
    normalizeOptionalString(env.OPENCLAW_SERVICE_MARKER) === "openclaw" &&
    Boolean(normalizeOptionalString(env.OPENCLAW_SERVICE_KIND))
  );
}

/**
 * Env markers are the fast path. A hand-written plist can omit them, so the PID
 * launchd reports for the job is checked against the caller's ancestry instead.
 * The detached update helper's recovery CLI inherits OPENCLAW_LAUNCHD_LABEL but
 * descends from no running Gateway, so it stays on the synchronous path.
 */
export async function isCurrentProcessInsideLaunchdService(
  label: string,
  env: NodeJS.ProcessEnv = process.env,
): Promise<boolean> {
  if (isCurrentProcessLaunchdServiceLabel(label, env)) {
    return true;
  }
  // Probe failures resolve to "unknown"; only a running job has a PID to be inside.
  const probe = await probeLaunchAgentState(`${resolveLaunchAgentGuiDomain()}/${label}`);
  if (probe.state !== "running" || probe.runtime.pid === undefined) {
    return false;
  }
  return getSelfAndAncestorPidsSync().has(probe.runtime.pid);
}