@indigoai-us/hq-cli 5.151.0 → 5.152.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/commands/mesh.js +2 -0
- package/dist/lib/mesh/live/daemon/install.d.ts +9 -6
- package/dist/lib/mesh/live/daemon/install.js +45 -8
- package/dist/lib/service-manager/index.d.ts +1 -2
- package/dist/lib/service-manager/index.js +2 -11
- package/dist/lib/service-manager/launchd.d.ts +30 -2
- package/dist/lib/service-manager/launchd.js +82 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.152.0] — 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq mesh daemon install` (plain and `--if-stale`) now bootouts, bootstraps, and kickstarts the LaunchAgent after writing the plist. Bootstrap retries launchd `5: Input/output error` up to five times, one second apart. After a CLI upgrade, a loaded job whose program no longer matches the plist is reloaded the same way.
|
|
10
|
+
|
|
5
11
|
## [5.151.0] — 2026-09-23
|
|
6
12
|
|
|
7
13
|
- `hq report publish` no longer fails when another app it cannot see holds the report's address. It publishes at the next free address (`<slug>-2`, then `-3`) and saves that address on the report.
|
package/dist/commands/mesh.js
CHANGED
|
@@ -36,6 +36,7 @@ import { readLegacyBacklog } from "../lib/mesh/live/emit-replay.js";
|
|
|
36
36
|
import { resolveMeshEmitMode, writeMeshConfigMode, } from "../lib/mesh/live/daemon/mode.js";
|
|
37
37
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
38
38
|
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, findAncestorHqRoot, resolveInstallHqRoot, detectPlatform, formatCliIdentityLines, formatDaemonDoctor, installDaemonIfStale, installDaemonService, readDaemonState, resolveAbsoluteNodeBinary, resolveRunningCliEntry, runMeshDaemon, uninstallDaemonService, daemonDir, } from "../lib/mesh/live/daemon/index.js";
|
|
39
|
+
import { defaultRunCommand } from "../lib/service-manager/launchd.js";
|
|
39
40
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
40
41
|
import { formatMigrateConfirmation, submitSessionMigration, } from "../lib/work-context/migrate.js";
|
|
41
42
|
import { formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, submitOrganizeDecision, } from "../lib/work-context/organize.js";
|
|
@@ -2050,6 +2051,7 @@ async function runDaemonInstall(opts) {
|
|
|
2050
2051
|
platform: detectPlatform(),
|
|
2051
2052
|
paths,
|
|
2052
2053
|
systemUser: opts.systemUser,
|
|
2054
|
+
run: defaultRunCommand,
|
|
2053
2055
|
});
|
|
2054
2056
|
if (opts.json) {
|
|
2055
2057
|
console.log(JSON.stringify({
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Tests must not actually install services — use a temp HOME/prefix.
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
|
-
import { type RunCommand } from "../../../service-manager/launchd.js";
|
|
15
|
+
import { type RunCommand, type SleepFn } from "../../../service-manager/launchd.js";
|
|
16
16
|
export type DaemonInstallPlatform = "darwin" | "linux" | "win32" | "other";
|
|
17
17
|
/** Result of one schtasks.exe invocation (tests inject; never the real binary). */
|
|
18
18
|
export interface SchtasksResult {
|
|
@@ -93,11 +93,6 @@ export interface RestartDaemonResult {
|
|
|
93
93
|
restarted: boolean;
|
|
94
94
|
message?: string;
|
|
95
95
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Restart the installed user daemon after a CLI upgrade. This is deliberately
|
|
98
|
-
* best-effort: an update must remain successful if a user has no service,
|
|
99
|
-
* launchctl/systemd is unavailable, or the user manager is not running.
|
|
100
|
-
*/
|
|
101
96
|
export declare function restartDaemonServiceIfInstalled(opts?: {
|
|
102
97
|
platform?: DaemonInstallPlatform;
|
|
103
98
|
home?: string;
|
|
@@ -110,7 +105,10 @@ export declare function restartDaemonServiceIfInstalled(opts?: {
|
|
|
110
105
|
writeFileSync?: typeof fs.writeFileSync;
|
|
111
106
|
run?: (command: string, args: string[]) => {
|
|
112
107
|
status: number | null;
|
|
108
|
+
stdout?: string;
|
|
109
|
+
stderr?: string;
|
|
113
110
|
};
|
|
111
|
+
sleep?: SleepFn;
|
|
114
112
|
/** Windows: terminate the recorded daemon pid after `schtasks /End` (tests inject). */
|
|
115
113
|
kill?: (pid: number) => void;
|
|
116
114
|
}): RestartDaemonResult;
|
|
@@ -149,6 +147,7 @@ export declare function installDaemonIfStale(opts: {
|
|
|
149
147
|
systemUser?: string;
|
|
150
148
|
run?: RunCommand;
|
|
151
149
|
uid?: number;
|
|
150
|
+
sleep?: SleepFn;
|
|
152
151
|
writeFileSync?: typeof fs.writeFileSync;
|
|
153
152
|
mkdirSync?: typeof fs.mkdirSync;
|
|
154
153
|
reloadSystemd?: () => {
|
|
@@ -282,6 +281,10 @@ export declare function installDaemonService(opts: {
|
|
|
282
281
|
stderr: string;
|
|
283
282
|
};
|
|
284
283
|
windowsLegacyTaskPresent?: boolean;
|
|
284
|
+
/** launchctl/systemctl seam — when set on darwin, bootout/bootstrap/kickstart after writing the plist. */
|
|
285
|
+
run?: RunCommand;
|
|
286
|
+
uid?: number;
|
|
287
|
+
sleep?: SleepFn;
|
|
285
288
|
}): InstallResult;
|
|
286
289
|
export declare function uninstallDaemonService(opts: {
|
|
287
290
|
platform?: DaemonInstallPlatform;
|
|
@@ -22,7 +22,7 @@ import { isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, verifyHqR
|
|
|
22
22
|
import { defaultPidLockDeps, pidLockStatus, readPidLock } from "./pid-lock.js";
|
|
23
23
|
import { retireLegacyListen } from "./legacy-listen.js";
|
|
24
24
|
import { readDaemonState } from "./state.js";
|
|
25
|
-
import { activateLaunchd } from "../../../service-manager/launchd.js";
|
|
25
|
+
import { activateLaunchd, launchdPlistProgramArguments, launchdProgramArgumentsDiffer, parseLaunchctlPrintArguments, } from "../../../service-manager/launchd.js";
|
|
26
26
|
import { activateSystemd } from "../../../service-manager/systemd.js";
|
|
27
27
|
/**
|
|
28
28
|
* `schtasks /End` terminates the task's root process only. The daemon is
|
|
@@ -139,13 +139,30 @@ function migrateLegacyDaemonUnit(opts) {
|
|
|
139
139
|
* best-effort: an update must remain successful if a user has no service,
|
|
140
140
|
* launchctl/systemd is unavailable, or the user manager is not running.
|
|
141
141
|
*/
|
|
142
|
+
function asRunCommand(run) {
|
|
143
|
+
return (command, args) => {
|
|
144
|
+
const result = run(command, args);
|
|
145
|
+
return {
|
|
146
|
+
status: result.status,
|
|
147
|
+
stdout: result.stdout ?? "",
|
|
148
|
+
stderr: result.stderr ?? "",
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
}
|
|
142
152
|
export function restartDaemonServiceIfInstalled(opts = {}) {
|
|
143
153
|
const platform = opts.platform ?? detectPlatform();
|
|
144
154
|
const home = opts.home ?? os.homedir();
|
|
145
155
|
const existsSync = opts.existsSync ?? fs.existsSync;
|
|
146
156
|
const readFileSync = opts.readFileSync ?? fs.readFileSync;
|
|
147
157
|
const writeFileSync = opts.writeFileSync ?? fs.writeFileSync;
|
|
148
|
-
const run = opts.run ?? ((command, args) =>
|
|
158
|
+
const run = opts.run ?? ((command, args) => {
|
|
159
|
+
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
160
|
+
return {
|
|
161
|
+
status: result.status,
|
|
162
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
163
|
+
stderr: result.error ? result.error.message : (typeof result.stderr === "string" ? result.stderr : ""),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
149
166
|
try {
|
|
150
167
|
migrateLegacyDaemonUnit({ platform, home, hqRoot: opts.hqRoot, env: opts.env, existsSync, readFileSync, writeFileSync, run });
|
|
151
168
|
if (platform === "darwin") {
|
|
@@ -155,7 +172,22 @@ export function restartDaemonServiceIfInstalled(opts = {}) {
|
|
|
155
172
|
const uid = opts.uid ?? process.getuid?.();
|
|
156
173
|
if (typeof uid !== "number")
|
|
157
174
|
return { attempted: false, restarted: false };
|
|
158
|
-
const
|
|
175
|
+
const target = `gui/${uid}/${LAUNCHD_LABEL}`;
|
|
176
|
+
const printed = run("launchctl", ["print", target]);
|
|
177
|
+
const loadedArgs = parseLaunchctlPrintArguments(printed.stdout ?? "");
|
|
178
|
+
const rawPlist = readFileSync(plist);
|
|
179
|
+
const plistText = Buffer.isBuffer(rawPlist) ? rawPlist.toString("utf8") : String(rawPlist);
|
|
180
|
+
const plistArgs = launchdPlistProgramArguments(plistText);
|
|
181
|
+
if (launchdProgramArgumentsDiffer(loadedArgs, plistArgs)) {
|
|
182
|
+
try {
|
|
183
|
+
activateLaunchd(plist, asRunCommand(run), uid, { sleep: opts.sleep });
|
|
184
|
+
return { attempted: true, restarted: true };
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return { attempted: true, restarted: false };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const result = run("launchctl", ["kickstart", "-k", target]);
|
|
159
191
|
return { attempted: true, restarted: result.status === 0 };
|
|
160
192
|
}
|
|
161
193
|
if (platform === "linux") {
|
|
@@ -306,11 +338,11 @@ export function installDaemonIfStale(opts) {
|
|
|
306
338
|
kill: opts.kill,
|
|
307
339
|
runCommand: opts.runCommand,
|
|
308
340
|
windowsLegacyTaskPresent: opts.windowsLegacyTaskPresent,
|
|
341
|
+
run,
|
|
342
|
+
uid: opts.uid,
|
|
343
|
+
sleep: opts.sleep,
|
|
309
344
|
});
|
|
310
|
-
if (platform === "
|
|
311
|
-
activateLaunchd(opts.paths.plistPath, run, opts.uid);
|
|
312
|
-
}
|
|
313
|
-
else if (platform === "linux" && !opts.systemUser && !opts.paths.systemUser) {
|
|
345
|
+
if (platform === "linux" && !opts.systemUser && !opts.paths.systemUser) {
|
|
314
346
|
activateSystemd(SYSTEMD_UNIT_NAME, run);
|
|
315
347
|
}
|
|
316
348
|
return {
|
|
@@ -753,7 +785,12 @@ export function installDaemonService(opts) {
|
|
|
753
785
|
const rendered = renderLaunchdPlist(paths);
|
|
754
786
|
mkdirSync(paths.launchAgentsDir, { recursive: true });
|
|
755
787
|
writeFileSync(paths.plistPath, rendered, { mode: 0o600 });
|
|
756
|
-
opts.
|
|
788
|
+
if (opts.run) {
|
|
789
|
+
activateLaunchd(paths.plistPath, opts.run, opts.uid, { sleep: opts.sleep });
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
opts.activate?.(platform, paths.plistPath);
|
|
793
|
+
}
|
|
757
794
|
return {
|
|
758
795
|
platform,
|
|
759
796
|
action: "install",
|
|
@@ -9,9 +9,8 @@ import { type RunCommand } from "./launchd.js";
|
|
|
9
9
|
import { type ServiceHostPaths, type ServicePlatform, type ServiceSpec } from "./types.js";
|
|
10
10
|
export type { ServiceSpec, ServiceHostPaths, ServicePlatform } from "./types.js";
|
|
11
11
|
export { detectServicePlatform } from "./types.js";
|
|
12
|
-
export { renderLaunchdPlist } from "./launchd.js";
|
|
12
|
+
export { defaultRunCommand, renderLaunchdPlist } from "./launchd.js";
|
|
13
13
|
export { renderSystemdUserUnit, systemdUnitName } from "./systemd.js";
|
|
14
|
-
export declare const defaultRunCommand: RunCommand;
|
|
15
14
|
export interface ServiceManagerDeps {
|
|
16
15
|
platform?: ServicePlatform;
|
|
17
16
|
run?: RunCommand;
|
|
@@ -4,22 +4,13 @@
|
|
|
4
4
|
* injectable so the kit tests exercise the real rendering and dispatch
|
|
5
5
|
* without touching the host's service manager.
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from "node:child_process";
|
|
8
7
|
import * as fs from "node:fs";
|
|
9
|
-
import { activateLaunchd, deactivateLaunchd, launchAgentsDir, launchdIsLoaded, launchdPlistPath, renderLaunchdPlist, } from "./launchd.js";
|
|
8
|
+
import { activateLaunchd, deactivateLaunchd, defaultRunCommand, launchAgentsDir, launchdIsLoaded, launchdPlistPath, renderLaunchdPlist, } from "./launchd.js";
|
|
10
9
|
import { activateSystemd, deactivateSystemd, renderSystemdUserUnit, systemdIsActive, systemdUnitName, systemdUnitPath, systemdUserDir, } from "./systemd.js";
|
|
11
10
|
import { detectServicePlatform, } from "./types.js";
|
|
12
11
|
export { detectServicePlatform } from "./types.js";
|
|
13
|
-
export { renderLaunchdPlist } from "./launchd.js";
|
|
12
|
+
export { defaultRunCommand, renderLaunchdPlist } from "./launchd.js";
|
|
14
13
|
export { renderSystemdUserUnit, systemdUnitName } from "./systemd.js";
|
|
15
|
-
export const defaultRunCommand = (cmd, args) => {
|
|
16
|
-
const r = spawnSync(cmd, args, { encoding: "utf8" });
|
|
17
|
-
return {
|
|
18
|
-
status: r.status,
|
|
19
|
-
stdout: r.stdout ?? "",
|
|
20
|
-
stderr: r.error ? r.error.message : (r.stderr ?? ""),
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
14
|
function unitPathFor(platform, home, spec) {
|
|
24
15
|
return platform === "darwin" ? launchdPlistPath(home, spec) : systemdUnitPath(home, spec);
|
|
25
16
|
}
|
|
@@ -16,8 +16,36 @@ export type RunCommand = (cmd: string, args: string[]) => {
|
|
|
16
16
|
stdout: string;
|
|
17
17
|
stderr: string;
|
|
18
18
|
};
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export type SleepFn = (ms: number) => void;
|
|
20
|
+
/** Bootstrap attempts after bootout (first try + EIO retries). */
|
|
21
|
+
export declare const LAUNCHD_BOOTSTRAP_ATTEMPTS = 5;
|
|
22
|
+
/** Delay between bootstrap retries while the old job is still exiting. */
|
|
23
|
+
export declare const LAUNCHD_BOOTSTRAP_RETRY_MS = 1000;
|
|
24
|
+
export declare const defaultRunCommand: RunCommand;
|
|
25
|
+
export declare function launchdServiceTarget(plistPath: string, uid?: number): string;
|
|
26
|
+
/** launchd returns EIO (5) when bootstrap races the previous job's exit. */
|
|
27
|
+
export declare function isRetryableLaunchdBootstrap(r: {
|
|
28
|
+
status: number | null;
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
}): boolean;
|
|
32
|
+
/** ProgramArguments from a LaunchAgent plist. */
|
|
33
|
+
export declare function launchdPlistProgramArguments(plist: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Arguments of a loaded job from `launchctl print gui/<uid>/<label>`.
|
|
36
|
+
* Prefers the `arguments = { ... }` block; falls back to `program =`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseLaunchctlPrintArguments(stdout: string): string[];
|
|
39
|
+
export declare function launchdProgramArgumentsDiffer(loaded: string[], plist: string[]): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* (Re)load a plist so launchd picks up a rewritten ProgramArguments.
|
|
42
|
+
* bootout is best-effort (not loaded yet is fine). bootstrap retries EIO a
|
|
43
|
+
* bounded number of times — bootout returns before the old job has actually
|
|
44
|
+
* exited. kickstart then starts the newly loaded program.
|
|
45
|
+
*/
|
|
46
|
+
export declare function activateLaunchd(plistPath: string, run: RunCommand, uid?: number, opts?: {
|
|
47
|
+
sleep?: SleepFn;
|
|
48
|
+
}): void;
|
|
21
49
|
export declare function deactivateLaunchd(plistPath: string, run: RunCommand, uid?: number): void;
|
|
22
50
|
export declare function launchdIsLoaded(label: string, run: RunCommand, uid?: number): boolean;
|
|
23
51
|
//# sourceMappingURL=launchd.d.ts.map
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* picks up a changed plist). Command execution is injected so tests never
|
|
7
7
|
* touch launchctl.
|
|
8
8
|
*/
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
9
10
|
import * as os from "node:os";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { escapeXml, } from "./types.js";
|
|
@@ -59,17 +60,92 @@ ${envXml}
|
|
|
59
60
|
</plist>
|
|
60
61
|
`;
|
|
61
62
|
}
|
|
63
|
+
/** Bootstrap attempts after bootout (first try + EIO retries). */
|
|
64
|
+
export const LAUNCHD_BOOTSTRAP_ATTEMPTS = 5;
|
|
65
|
+
/** Delay between bootstrap retries while the old job is still exiting. */
|
|
66
|
+
export const LAUNCHD_BOOTSTRAP_RETRY_MS = 1000;
|
|
67
|
+
export const defaultRunCommand = (cmd, args) => {
|
|
68
|
+
const r = spawnSync(cmd, args, { encoding: "utf8" });
|
|
69
|
+
return {
|
|
70
|
+
status: r.status,
|
|
71
|
+
stdout: typeof r.stdout === "string" ? r.stdout : "",
|
|
72
|
+
stderr: r.error ? r.error.message : (typeof r.stderr === "string" ? r.stderr : ""),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
function blockingSleep(ms) {
|
|
76
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
77
|
+
}
|
|
62
78
|
function guiDomain(uid = os.userInfo().uid) {
|
|
63
79
|
return `gui/${uid}`;
|
|
64
80
|
}
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
export function launchdServiceTarget(plistPath, uid) {
|
|
82
|
+
return `${guiDomain(uid)}/${path.basename(plistPath, ".plist")}`;
|
|
83
|
+
}
|
|
84
|
+
function bootstrapErrorText(r) {
|
|
85
|
+
return `${r.stderr} ${r.stdout} exit ${r.status ?? ""}`;
|
|
86
|
+
}
|
|
87
|
+
/** launchd returns EIO (5) when bootstrap races the previous job's exit. */
|
|
88
|
+
export function isRetryableLaunchdBootstrap(r) {
|
|
89
|
+
if (r.status === 0)
|
|
90
|
+
return false;
|
|
91
|
+
return /Input\/output error|\bEIO\b|Bootstrap failed: 5|Boot-strap failed: 5/i.test(bootstrapErrorText(r));
|
|
92
|
+
}
|
|
93
|
+
function unescapeXml(value) {
|
|
94
|
+
return value
|
|
95
|
+
.replace(/"/g, '"')
|
|
96
|
+
.replace(/</g, "<")
|
|
97
|
+
.replace(/>/g, ">")
|
|
98
|
+
.replace(/&/g, "&");
|
|
99
|
+
}
|
|
100
|
+
/** ProgramArguments from a LaunchAgent plist. */
|
|
101
|
+
export function launchdPlistProgramArguments(plist) {
|
|
102
|
+
const block = plist.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
|
|
103
|
+
if (!block)
|
|
104
|
+
return [];
|
|
105
|
+
return [...block[1].matchAll(/<string>([^<]*)<\/string>/g)].map((m) => unescapeXml(m[1]));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Arguments of a loaded job from `launchctl print gui/<uid>/<label>`.
|
|
109
|
+
* Prefers the `arguments = { ... }` block; falls back to `program =`.
|
|
110
|
+
*/
|
|
111
|
+
export function parseLaunchctlPrintArguments(stdout) {
|
|
112
|
+
const block = /(?:program\s+)?arguments\s*=\s*\{([^}]*)\}/i.exec(stdout);
|
|
113
|
+
if (block) {
|
|
114
|
+
return block[1]
|
|
115
|
+
.split(/\r?\n/)
|
|
116
|
+
.map((line) => line.trim().replace(/;+$/, ""))
|
|
117
|
+
.filter((line) => line.length > 0);
|
|
118
|
+
}
|
|
119
|
+
const program = /^\s*program\s*=\s*(.+?);?\s*$/m.exec(stdout);
|
|
120
|
+
return program ? [program[1].trim()] : [];
|
|
121
|
+
}
|
|
122
|
+
export function launchdProgramArgumentsDiffer(loaded, plist) {
|
|
123
|
+
if (loaded.length === 0 || plist.length === 0)
|
|
124
|
+
return loaded.length !== plist.length;
|
|
125
|
+
if (loaded.length !== plist.length)
|
|
126
|
+
return true;
|
|
127
|
+
return loaded.some((value, i) => value !== plist[i]);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* (Re)load a plist so launchd picks up a rewritten ProgramArguments.
|
|
131
|
+
* bootout is best-effort (not loaded yet is fine). bootstrap retries EIO a
|
|
132
|
+
* bounded number of times — bootout returns before the old job has actually
|
|
133
|
+
* exited. kickstart then starts the newly loaded program.
|
|
134
|
+
*/
|
|
135
|
+
export function activateLaunchd(plistPath, run, uid, opts) {
|
|
67
136
|
const domain = guiDomain(uid);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
137
|
+
const target = launchdServiceTarget(plistPath, uid);
|
|
138
|
+
run("launchctl", ["bootout", target]);
|
|
139
|
+
const sleep = opts?.sleep ?? blockingSleep;
|
|
140
|
+
let last = run("launchctl", ["bootstrap", domain, plistPath]);
|
|
141
|
+
for (let attempt = 1; attempt < LAUNCHD_BOOTSTRAP_ATTEMPTS && last.status !== 0 && isRetryableLaunchdBootstrap(last); attempt++) {
|
|
142
|
+
sleep(LAUNCHD_BOOTSTRAP_RETRY_MS);
|
|
143
|
+
last = run("launchctl", ["bootstrap", domain, plistPath]);
|
|
144
|
+
}
|
|
145
|
+
if (last.status !== 0) {
|
|
146
|
+
throw new Error(`launchctl bootstrap failed for ${plistPath}: ${last.stderr.trim() || last.stdout.trim() || `exit ${last.status}`}`);
|
|
72
147
|
}
|
|
148
|
+
run("launchctl", ["kickstart", "-k", target]);
|
|
73
149
|
}
|
|
74
150
|
export function deactivateLaunchd(plistPath, run, uid) {
|
|
75
151
|
run("launchctl", ["bootout", guiDomain(uid), plistPath]);
|