|
| 1 | +/** macOS LaunchAgent installer, runtime inspection, and lifecycle controls. */ |
1 | 2 | import fs from "node:fs/promises"; |
2 | 3 | import path from "node:path"; |
3 | 4 | import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; |
@@ -61,6 +62,8 @@ function normalizeOpenClawUpdateLaunchdLabel(label: unknown): string | null { |
61 | 62 | if (trimmed.startsWith(OPENCLAW_UPDATE_LAUNCHD_LABEL_PREFIX)) { |
62 | 63 | return trimmed; |
63 | 64 | } |
| 65 | + // Manual update jobs include a timestamp-like suffix and should be cleaned up |
| 66 | + // without matching arbitrary ai.openclaw labels. |
64 | 67 | return OPENCLAW_MANUAL_UPDATE_LAUNCHD_LABEL_PATTERN.test(trimmed) ? trimmed : null; |
65 | 68 | } |
66 | 69 |
|
@@ -200,6 +203,8 @@ async function prepareLaunchAgentProgramArguments(params: { |
200 | 203 | return { programArguments: params.programArguments }; |
201 | 204 | } |
202 | 205 |
|
| 206 | + // Environment values with secrets live in an owner-only env file instead of |
| 207 | + // inline plist XML, which can be harder to rotate and audit. |
203 | 208 | const envDir = resolveLaunchAgentEnvDir(params.env); |
204 | 209 | const envFilePath = resolveLaunchAgentEnvFilePath(params.env, params.label); |
205 | 210 | const wrapperPath = resolveLaunchAgentEnvWrapperPath(params.env, params.label); |
@@ -327,6 +332,8 @@ export async function findStaleOpenClawUpdateLaunchdJobs( |
327 | 332 | if (result.code !== 0) { |
328 | 333 | return []; |
329 | 334 | } |
| 335 | + // Never report the active gateway label as stale even when a wrapper exposes |
| 336 | + // update-like launchd metadata through the current environment. |
330 | 337 | return parseLaunchctlListOpenClawUpdateJobs(result.stdout).filter( |
331 | 338 | (job) => !isCurrentGatewayLaunchdLabel(job.label, env), |
332 | 339 | ); |
|
0 commit comments