refactor(daemon): simplify managed Gateway bindings (#162836)

Build native service selectors in the existing discovery loop and reuse the profile naming owner for deduplication. Preserve exact service scope, Startup identity and host environment forwarding.

Drop a stale PID-probe count assertion while retaining EPERM/ESRCH behavior and signal-zero checks. Validation: 137 local cases, the Linux-only system-template case, complete selected checks and independent P2 review pass. Production delta: -31 lines.
This commit is contained in:
Peter Steinberger 2026-10-01 10:54:44 -07:00 • committed by GitHub
parent b66471a26d
commit 1c57a6f9ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 57 deletions

View file

@ -1,8 +1,8 @@
/** Bind discovered native service selectors without granting lifecycle authority. */ /** Bind discovered native service selectors without granting lifecycle authority. */
import path from "node:path"; import path from "node:path";
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import { hasCommandProcessCleanupError } from "../process/exec-result.js"; import { hasCommandProcessCleanupError } from "../process/exec-result.js";
import { listManagedOpenClawGatewayServices, type ExtraGatewayService } from "./inspect.js"; import { resolveGatewayProfileSuffix } from "./constants.js";
import { listManagedOpenClawGatewayServices } from "./inspect.js";
import type { LoadedLaunchAgentState } from "./launchd-runtime.js"; import type { LoadedLaunchAgentState } from "./launchd-runtime.js";
import { resolveTaskName } from "./schtasks-layout.js"; import { resolveTaskName } from "./schtasks-layout.js";
import type { GatewayServiceEnv, SystemdServiceReadTarget } from "./service-types.js"; import type { GatewayServiceEnv, SystemdServiceReadTarget } from "./service-types.js";
@ -51,7 +51,7 @@ export async function readManagedGatewayBindingState(
function bindingSelectorKey(binding: ManagedGatewayBinding): string { function bindingSelectorKey(binding: ManagedGatewayBinding): string {
return [ return [
normalizeDiscoveredProfile(binding.env.OPENCLAW_PROFILE), resolveGatewayProfileSuffix(binding.env.OPENCLAW_PROFILE),
binding.scope ?? binding.systemdReadTarget?.scope ?? "", binding.scope ?? binding.systemdReadTarget?.scope ?? "",
binding.systemdReadTarget?.unitPath ?? "", binding.systemdReadTarget?.unitPath ?? "",
binding.launchAgentPlistPath ?? "", binding.launchAgentPlistPath ?? "",
@ -64,14 +64,6 @@ function bindingSelectorKey(binding: ManagedGatewayBinding): string {
].join("\0"); ].join("\0");
} }
function normalizeDiscoveredProfile(value: string | undefined): string {
const trimmed = value?.trim();
if (!trimmed || normalizeLowercaseStringOrEmpty(trimmed) === "default") {
return "default";
}
return trimmed;
}
function hostBindingEnv( function hostBindingEnv(
env: Record<string, string | undefined>, env: Record<string, string | undefined>,
extras: GatewayServiceEnv, extras: GatewayServiceEnv,
@ -98,48 +90,6 @@ function profileEnvFields(profile: string): GatewayServiceEnv {
return profile === "default" ? {} : { OPENCLAW_PROFILE: profile }; return profile === "default" ? {} : { OPENCLAW_PROFILE: profile };
} }
function bindingFromSystemdService(
svc: ExtraGatewayService,
env: Record<string, string | undefined>,
): ManagedGatewayBinding {
const unitPath = svc.sourcePath;
const unitName = resolveSystemdTemplateInstanceName(svc.label, {
OPENCLAW_SYSTEMD_UNIT: svc.label,
});
const systemdReadTarget = unitPath ? { scope: svc.scope, unitName, unitPath } : undefined;
return {
scope: svc.scope,
...(systemdReadTarget ? { systemdReadTarget } : {}),
env: hostBindingEnv(env, { OPENCLAW_SYSTEMD_UNIT: unitName }),
};
}
function bindingFromLaunchdService(
svc: ExtraGatewayService,
env: Record<string, string | undefined>,
): ManagedGatewayBinding {
const plistPath = svc.sourcePath;
return {
scope: svc.scope,
...(plistPath ? { launchAgentPlistPath: plistPath } : {}),
env: hostBindingEnv(env, { OPENCLAW_LAUNCHD_LABEL: svc.label }),
};
}
function bindingFromWindowsTask(
name: string,
profile: string,
env: Record<string, string | undefined>,
): ManagedGatewayBinding {
return {
scope: "system",
env: hostBindingEnv(env, {
...profileEnvFields(profile),
OPENCLAW_WINDOWS_TASK_NAME: name.replace(/^\\+/, "").trim() || name,
}),
};
}
/** /**
* Enumerate installed managed Gateway selectors for runtime mutation checks. * Enumerate installed managed Gateway selectors for runtime mutation checks.
*/ */
@ -171,11 +121,24 @@ export async function discoverManagedGatewayBindings(
// Best-effort callers retain known bindings; automatic writers require complete discovery. // Best-effort callers retain known bindings; automatic writers require complete discovery.
for (const svc of services) { for (const svc of services) {
if (svc.platform === "linux") { if (svc.platform === "linux") {
push(bindingFromSystemdService(svc, env)); const unitName = resolveSystemdTemplateInstanceName(svc.label, {
OPENCLAW_SYSTEMD_UNIT: svc.label,
});
push({
scope: svc.scope,
...(svc.sourcePath
? { systemdReadTarget: { scope: svc.scope, unitName, unitPath: svc.sourcePath } }
: {}),
env: hostBindingEnv(env, { OPENCLAW_SYSTEMD_UNIT: unitName }),
});
continue; continue;
} }
if (svc.platform === "darwin") { if (svc.platform === "darwin") {
push(bindingFromLaunchdService(svc, env)); push({
scope: svc.scope,
...(svc.sourcePath ? { launchAgentPlistPath: svc.sourcePath } : {}),
env: hostBindingEnv(env, { OPENCLAW_LAUNCHD_LABEL: svc.label }),
});
continue; continue;
} }
if (svc.windowsProfile === undefined) { if (svc.windowsProfile === undefined) {
@ -189,7 +152,13 @@ export async function discoverManagedGatewayBindings(
}); });
continue; continue;
} }
push(bindingFromWindowsTask(svc.label, svc.windowsProfile, env)); push({
scope: "system",
env: hostBindingEnv(env, {
...profileEnvFields(svc.windowsProfile),
OPENCLAW_WINDOWS_TASK_NAME: svc.label.replace(/^\\+/, "").trim() || svc.label,
}),
});
} }
} catch (error) { } catch (error) {
if (options.requireComplete || hasCommandProcessCleanupError(error)) { if (options.requireComplete || hasCommandProcessCleanupError(error)) {

View file

@ -232,7 +232,6 @@ describe("live-gateway-dist-fence", () => {
runtime: { status: "stopped", state: "inactive", pid: process.pid }, runtime: { status: "stopped", state: "inactive", pid: process.pid },
}), }),
}); });
expect(kill).toHaveBeenCalledTimes(1);
expect(kill).toHaveBeenCalledWith(process.pid, 0); expect(kill).toHaveBeenCalledWith(process.pid, 0);
expect(result.refuse).toBe(refuse); expect(result.refuse).toBe(refuse);
}); });