@ouro.bot/cli 0.1.0-alpha.551 → 0.1.0-alpha.553
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.json +12 -0
- package/dist/heart/daemon/cli-defaults.js +125 -14
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.553",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Daemon startup on macOS now starts through launchd before opening the daemon socket when the LaunchAgent is available, preventing `ouro up` from leaving both a manual daemon and a launchd daemon alive after current-session bootstrap."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.552",
|
|
12
|
+
"changes": [
|
|
13
|
+
"`ouro up` now bootstraps the daemon LaunchAgent into the current login session when it is not already loaded, waits for the launchd-owned daemon socket to settle, and keeps the launchd adoption non-fatal if macOS refuses the bootstrap."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.551",
|
|
6
18
|
"changes": [
|
|
@@ -41,6 +41,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
42
|
exports.defaultStartDaemonProcess = defaultStartDaemonProcess;
|
|
43
43
|
exports.readFirstBundleMetaVersion = readFirstBundleMetaVersion;
|
|
44
|
+
exports.isDaemonLaunchAgentLoaded = isDaemonLaunchAgentLoaded;
|
|
45
|
+
exports.waitForBootstrappedDaemonSocket = waitForBootstrappedDaemonSocket;
|
|
44
46
|
exports.defaultListDiscoveredAgents = defaultListDiscoveredAgents;
|
|
45
47
|
exports.defaultRunSerpentGuide = defaultRunSerpentGuide;
|
|
46
48
|
exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
|
|
@@ -75,7 +77,10 @@ const cli_parse_1 = require("./cli-parse");
|
|
|
75
77
|
const provider_discovery_1 = require("./provider-discovery");
|
|
76
78
|
const provider_credentials_1 = require("../provider-credentials");
|
|
77
79
|
// ── Default implementations ──
|
|
78
|
-
function defaultStartDaemonProcess(socketPath) {
|
|
80
|
+
async function defaultStartDaemonProcess(socketPath) {
|
|
81
|
+
const launchdStarted = await startDaemonProcessViaLaunchd(socketPath);
|
|
82
|
+
if (launchdStarted)
|
|
83
|
+
return launchdStarted;
|
|
79
84
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
80
85
|
// Redirect stdio to /dev/null via file descriptors — using 'ignore' causes EPIPE
|
|
81
86
|
// when the daemon's logging system writes to stderr after the parent exits.
|
|
@@ -87,7 +92,7 @@ function defaultStartDaemonProcess(socketPath) {
|
|
|
87
92
|
});
|
|
88
93
|
child.unref();
|
|
89
94
|
// Don't close fds — the child process needs them. They'll be cleaned up when the parent exits.
|
|
90
|
-
return
|
|
95
|
+
return { pid: child.pid ?? null };
|
|
91
96
|
}
|
|
92
97
|
function defaultWriteStdout(text) {
|
|
93
98
|
process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
@@ -216,10 +221,13 @@ function defaultFallbackPendingMessage(command) {
|
|
|
216
221
|
});
|
|
217
222
|
return pendingPath;
|
|
218
223
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
function currentUserUid() {
|
|
225
|
+
return process.getuid?.() ?? 0;
|
|
226
|
+
}
|
|
227
|
+
function launchAgentDomain(userUid = currentUserUid()) {
|
|
228
|
+
return `gui/${userUid}`;
|
|
229
|
+
}
|
|
230
|
+
function writeDaemonBootPlist(socketPath) {
|
|
223
231
|
const homeDir = os.homedir();
|
|
224
232
|
const writeDeps = {
|
|
225
233
|
writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
@@ -237,20 +245,123 @@ function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
|
237
245
|
meta: { entryPath },
|
|
238
246
|
});
|
|
239
247
|
}
|
|
240
|
-
|
|
241
|
-
// Write plist only — do NOT launchctl bootstrap.
|
|
242
|
-
// The daemon is already running (started by ouro up). Bootstrapping would
|
|
243
|
-
// start a SECOND daemon via launchd's RunAtLoad, causing a race where
|
|
244
|
-
// killOrphanProcesses kills the first daemon and both end up dead.
|
|
245
|
-
// The plist on disk is sufficient: launchd picks it up on login.
|
|
246
|
-
(0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
|
|
248
|
+
return (0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
|
|
247
249
|
nodePath: process.execPath,
|
|
248
250
|
entryPath,
|
|
249
251
|
socketPath,
|
|
250
|
-
logDir,
|
|
252
|
+
logDir: (0, identity_1.getAgentDaemonLogsDir)(),
|
|
251
253
|
envPath: process.env.PATH,
|
|
252
254
|
});
|
|
253
255
|
}
|
|
256
|
+
function isDaemonLaunchAgentLoaded(deps) {
|
|
257
|
+
const userUid = deps?.userUid ?? currentUserUid();
|
|
258
|
+
const exec = deps?.exec ?? ((cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); });
|
|
259
|
+
try {
|
|
260
|
+
exec(`launchctl print ${launchAgentDomain(userUid)}/${launchd_1.DAEMON_PLIST_LABEL}`);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function readDaemonLaunchAgentPid() {
|
|
268
|
+
try {
|
|
269
|
+
const output = (0, child_process_1.execSync)(`launchctl print ${launchAgentDomain()}/${launchd_1.DAEMON_PLIST_LABEL}`, { encoding: "utf-8" });
|
|
270
|
+
const match = output.match(/^\s*pid = (\d+)/m);
|
|
271
|
+
return match ? Number(match[1]) : null;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async function waitForBootstrappedDaemonSocket(socketPath, deps = {}) {
|
|
278
|
+
const checkSocketAlive = deps.checkSocketAlive ?? socket_client_1.checkDaemonSocketAlive;
|
|
279
|
+
const now = deps.now ?? Date.now;
|
|
280
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
281
|
+
const timeoutMs = deps.timeoutMs ?? 30_000;
|
|
282
|
+
const initialSettleMs = deps.initialSettleMs ?? 1_000;
|
|
283
|
+
const pollIntervalMs = deps.pollIntervalMs ?? 250;
|
|
284
|
+
const requiredConsecutiveAliveChecks = deps.requiredConsecutiveAliveChecks ?? 2;
|
|
285
|
+
const deadline = now() + timeoutMs;
|
|
286
|
+
const firstTrustworthyCheckAt = now() + initialSettleMs;
|
|
287
|
+
let consecutiveAliveChecks = 0;
|
|
288
|
+
while (now() < deadline) {
|
|
289
|
+
await sleep(pollIntervalMs);
|
|
290
|
+
if (now() < firstTrustworthyCheckAt)
|
|
291
|
+
continue;
|
|
292
|
+
if (await checkSocketAlive(socketPath)) {
|
|
293
|
+
consecutiveAliveChecks += 1;
|
|
294
|
+
if (consecutiveAliveChecks >= requiredConsecutiveAliveChecks)
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
consecutiveAliveChecks = 0;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
(0, runtime_1.emitNervesEvent)({
|
|
302
|
+
level: "warn",
|
|
303
|
+
component: "daemon",
|
|
304
|
+
event: "daemon.launchd_bootstrap_socket_wait_timeout",
|
|
305
|
+
message: "launchd bootstrap finished but daemon socket did not settle before timeout",
|
|
306
|
+
meta: { socketPath },
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async function startDaemonProcessViaLaunchd(socketPath) {
|
|
310
|
+
if (process.platform !== "darwin") {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const plistPath = writeDaemonBootPlist(socketPath);
|
|
314
|
+
const userUid = currentUserUid();
|
|
315
|
+
const domain = launchAgentDomain(userUid);
|
|
316
|
+
try {
|
|
317
|
+
(0, runtime_1.emitNervesEvent)({
|
|
318
|
+
component: "daemon",
|
|
319
|
+
event: "daemon.launchd_bootstrap_start",
|
|
320
|
+
message: "starting daemon launch agent for current login session",
|
|
321
|
+
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
322
|
+
});
|
|
323
|
+
if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
|
|
324
|
+
(0, child_process_1.execSync)(`launchctl kickstart -k ${domain}/${launchd_1.DAEMON_PLIST_LABEL}`, { stdio: "ignore" });
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
(0, child_process_1.execSync)(`launchctl bootstrap ${domain} "${plistPath}"`, { stdio: "ignore" });
|
|
328
|
+
}
|
|
329
|
+
(0, runtime_1.emitNervesEvent)({
|
|
330
|
+
component: "daemon",
|
|
331
|
+
event: "daemon.launchd_bootstrap_end",
|
|
332
|
+
message: "daemon launch agent started for current login session",
|
|
333
|
+
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
334
|
+
});
|
|
335
|
+
await waitForBootstrappedDaemonSocket(socketPath);
|
|
336
|
+
return { pid: readDaemonLaunchAgentPid() };
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
(0, runtime_1.emitNervesEvent)({
|
|
340
|
+
level: "warn",
|
|
341
|
+
component: "daemon",
|
|
342
|
+
event: "daemon.launchd_bootstrap_error",
|
|
343
|
+
message: "failed to start daemon launch agent for current login session",
|
|
344
|
+
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL, error: error instanceof Error ? error.message : String(error) },
|
|
345
|
+
});
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
350
|
+
if (process.platform !== "darwin") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const plistPath = writeDaemonBootPlist(socketPath);
|
|
354
|
+
const userUid = currentUserUid();
|
|
355
|
+
if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
|
|
356
|
+
(0, runtime_1.emitNervesEvent)({
|
|
357
|
+
component: "daemon",
|
|
358
|
+
event: "daemon.launchd_bootstrap_skipped_loaded",
|
|
359
|
+
message: "daemon launch agent already loaded",
|
|
360
|
+
meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
|
|
361
|
+
});
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
254
365
|
function defaultPrepareDaemonRuntimeReplacement() {
|
|
255
366
|
if (process.platform !== "darwin") {
|
|
256
367
|
return;
|