@ouro.bot/cli 0.1.0-alpha.552 → 0.1.0-alpha.554

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.
@@ -41,13 +41,15 @@ const path = __importStar(require("path"));
41
41
  const identity_1 = require("../identity");
42
42
  const runtime_1 = require("../../nerves/runtime");
43
43
  const provider_models_1 = require("../provider-models");
44
- const machine_identity_1 = require("../machine-identity");
45
- const provider_state_1 = require("../provider-state");
46
44
  const provider_credentials_1 = require("../provider-credentials");
45
+ const provider_readiness_cache_1 = require("../provider-readiness-cache");
47
46
  const vault_unlock_1 = require("../../repertoire/vault-unlock");
48
47
  const readiness_repair_1 = require("./readiness-repair");
49
48
  const provider_ping_progress_1 = require("./provider-ping-progress");
50
- const drift_detection_1 = require("./drift-detection");
49
+ const LANES = [
50
+ { lane: "outward", facing: "humanFacing" },
51
+ { lane: "inner", facing: "agentFacing" },
52
+ ];
51
53
  function isAgentProvider(value) {
52
54
  return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
53
55
  }
@@ -57,6 +59,9 @@ function agentRootFor(agentName, bundlesRoot) {
57
59
  function configPathFor(agentName, bundlesRoot) {
58
60
  return path.join(agentRootFor(agentName, bundlesRoot), "agent.json");
59
61
  }
62
+ function laneForFacing(facing) {
63
+ return facing === "humanFacing" ? "outward" : "inner";
64
+ }
60
65
  function resolveFacingProvider(parsed, facing, agentName, agentJsonPath) {
61
66
  const raw = parsed[facing];
62
67
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
@@ -90,9 +95,13 @@ function resolveFacingProvider(parsed, facing, agentName, agentJsonPath) {
90
95
  },
91
96
  };
92
97
  }
93
- return { ok: true, selected: { facing, provider } };
98
+ const rawModel = raw.model;
99
+ const model = typeof rawModel === "string" && rawModel.trim().length > 0
100
+ ? rawModel.trim()
101
+ : (0, provider_models_1.getDefaultModelForProvider)(provider);
102
+ return { ok: true, selected: { facing, lane: laneForFacing(facing), provider, model } };
94
103
  }
95
- function readAgentConfigForProviderState(agentName, bundlesRoot) {
104
+ function readAgentConfigForProviderCheck(agentName, bundlesRoot) {
96
105
  const agentJsonPath = configPathFor(agentName, bundlesRoot);
97
106
  let raw;
98
107
  try {
@@ -127,83 +136,25 @@ function readAgentConfigForProviderState(agentName, bundlesRoot) {
127
136
  }
128
137
  return { ok: true, disabled: false, agentJsonPath, parsed };
129
138
  }
130
- function readFacingForBootstrap(parsed, facing, agentName, agentJsonPath) {
131
- const providerResult = resolveFacingProvider(parsed, facing, agentName, agentJsonPath);
132
- if (!providerResult.ok) {
133
- return {
134
- ok: false,
135
- result: {
136
- ok: false,
137
- error: providerResult.result.error,
138
- fix: `Run 'ouro use --agent ${agentName} --lane ${facing === "humanFacing" ? "outward" : "inner"} --provider <provider> --model <model>' to configure this machine's provider binding.`,
139
- },
140
- };
141
- }
142
- const raw = parsed[facing];
143
- const model = typeof raw.model === "string" && raw.model.trim().length > 0
144
- ? raw.model.trim()
145
- : (0, provider_models_1.getDefaultModelForProvider)(providerResult.selected.provider);
146
- return { ok: true, provider: providerResult.selected.provider, model };
147
- }
148
- function bootstrapMissingProviderState(input) {
149
- const outward = readFacingForBootstrap(input.parsed, "humanFacing", input.agentName, input.agentJsonPath);
150
- if (!outward.ok)
151
- return { ok: false, error: outward.result.error, fix: outward.result.fix };
152
- const inner = readFacingForBootstrap(input.parsed, "agentFacing", input.agentName, input.agentJsonPath);
153
- if (!inner.ok)
154
- return { ok: false, error: inner.result.error, fix: inner.result.fix };
155
- const now = new Date();
156
- const homeDir = (0, provider_credentials_1.providerCredentialMachineHomeDir)(input.homeDir);
157
- const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir, now: () => now });
158
- const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
159
- machineId: machine.machineId,
160
- now,
161
- agentConfig: {
162
- humanFacing: { provider: outward.provider, model: outward.model },
163
- agentFacing: { provider: inner.provider, model: inner.model },
164
- },
165
- });
166
- const agentRoot = agentRootFor(input.agentName, input.bundlesRoot);
167
- (0, provider_state_1.writeProviderState)(agentRoot, state);
168
- (0, runtime_1.emitNervesEvent)({
169
- component: "daemon",
170
- event: "daemon.provider_state_bootstrapped",
171
- message: "bootstrapped local provider state from agent config",
172
- meta: { agent: input.agentName, agentRoot },
173
- });
174
- return { ok: true, agentRoot, state };
175
- }
176
- function readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, deps = {}) {
177
- const configResult = readAgentConfigForProviderState(agentName, bundlesRoot);
139
+ function readProviderSelectionForCheck(agentName, bundlesRoot) {
140
+ const configResult = readAgentConfigForProviderCheck(agentName, bundlesRoot);
178
141
  if (!configResult.ok)
179
142
  return { ok: false, result: configResult.result };
180
143
  if (configResult.disabled)
181
144
  return { ok: true, disabled: true };
182
- const agentRoot = agentRootFor(agentName, bundlesRoot);
183
- const stateResult = (0, provider_state_1.readProviderState)(agentRoot);
184
- if (stateResult.ok) {
185
- return { ok: true, disabled: false, agentRoot, state: stateResult.state };
186
- }
187
- if (stateResult.reason === "invalid") {
188
- return {
189
- ok: false,
190
- result: {
191
- ok: false,
192
- error: `provider state for ${agentName} is invalid at ${stateResult.statePath}: ${stateResult.error}`,
193
- fix: `Run 'ouro use --agent ${agentName} --lane outward --provider <provider> --model <model> --force' to rewrite this machine's provider binding.`,
194
- },
195
- };
145
+ const bindings = {};
146
+ for (const { lane, facing } of LANES) {
147
+ const selected = resolveFacingProvider(configResult.parsed, facing, agentName, configResult.agentJsonPath);
148
+ if (!selected.ok)
149
+ return { ok: false, result: selected.result };
150
+ bindings[lane] = selected.selected;
196
151
  }
197
- const bootstrap = bootstrapMissingProviderState({
198
- agentName,
199
- bundlesRoot,
200
- parsed: configResult.parsed,
201
- agentJsonPath: configResult.agentJsonPath,
202
- homeDir: deps.homeDir,
203
- });
204
- if (!bootstrap.ok)
205
- return { ok: false, result: bootstrap };
206
- return { ok: true, disabled: false, agentRoot: bootstrap.agentRoot, state: bootstrap.state };
152
+ return {
153
+ ok: true,
154
+ disabled: false,
155
+ agentRoot: agentRootFor(agentName, bundlesRoot),
156
+ bindings,
157
+ };
207
158
  }
208
159
  function providerCredentialConfig(record) {
209
160
  return {
@@ -216,21 +167,6 @@ function pingAttemptCount(result) {
216
167
  return result.attempts.length;
217
168
  return undefined;
218
169
  }
219
- function writeLaneReadiness(input) {
220
- const binding = input.state.lanes[input.lane];
221
- const checkedAt = new Date().toISOString();
222
- input.state.updatedAt = checkedAt;
223
- input.state.readiness[input.lane] = {
224
- status: input.status,
225
- provider: binding.provider,
226
- model: binding.model,
227
- checkedAt,
228
- credentialRevision: input.credentialRevision,
229
- ...(input.error ? { error: input.error } : {}),
230
- ...(input.attempts !== undefined ? { attempts: input.attempts } : {}),
231
- };
232
- (0, provider_state_1.writeProviderState)(input.agentRoot, input.state);
233
- }
234
170
  function missingCredentialResult(agentName, lane, provider, model, credentialPath) {
235
171
  return {
236
172
  ok: false,
@@ -325,14 +261,14 @@ function laneAudienceLabel(lane) {
325
261
  function bindingLabel(binding) {
326
262
  return `${binding.provider} / ${binding.model}`;
327
263
  }
328
- function selectedProviderPlan(agentName, state) {
264
+ function selectedProviderPlan(agentName, bindings) {
329
265
  return [
330
- `${agentName}: checking the providers this agent uses right now`,
331
- ...["outward", "inner"].map((lane) => `- ${laneAudienceLabel(lane)}: ${bindingLabel(state.lanes[lane])}`),
266
+ `${agentName}: checking the providers in agent.json`,
267
+ ...LANES.map(({ lane }) => `- ${laneAudienceLabel(lane)}: ${bindingLabel(bindings[lane])}`),
332
268
  ].join("\n");
333
269
  }
334
- function selectedProvidersForState(state) {
335
- return [...new Set(["outward", "inner"].map((lane) => state.lanes[lane].provider))];
270
+ function selectedProvidersForBindings(bindings) {
271
+ return [...new Set(LANES.map(({ lane }) => bindings[lane].provider))];
336
272
  }
337
273
  function mapVaultRefreshProgress(agentName, onProgress) {
338
274
  return (message) => {
@@ -359,80 +295,52 @@ function providerPingSubject(agentName, lanes) {
359
295
  * checkAgentConfigWithProviderHealth(), which reads the agent vault and pings.
360
296
  */
361
297
  function checkAgentConfig(agentName, bundlesRoot) {
362
- const configResult = readAgentConfigForProviderState(agentName, bundlesRoot);
363
- if (!configResult.ok)
364
- return configResult.result;
365
- if (configResult.disabled)
298
+ const selectionResult = readProviderSelectionForCheck(agentName, bundlesRoot);
299
+ if (!selectionResult.ok)
300
+ return selectionResult.result;
301
+ if (selectionResult.disabled)
366
302
  return { ok: true };
367
- const outward = readFacingForBootstrap(configResult.parsed, "humanFacing", agentName, configResult.agentJsonPath);
368
- if (!outward.ok)
369
- return outward.result;
370
- const inner = readFacingForBootstrap(configResult.parsed, "agentFacing", agentName, configResult.agentJsonPath);
371
- if (!inner.ok)
372
- return inner.result;
373
303
  (0, runtime_1.emitNervesEvent)({
374
304
  component: "daemon",
375
305
  event: "daemon.agent_config_valid",
376
306
  message: "agent config validation passed",
377
307
  meta: {
378
308
  agent: agentName,
379
- providers: [...new Set([outward.provider, inner.provider])],
309
+ providers: selectedProvidersForBindings(selectionResult.bindings),
380
310
  liveProviderCheck: false,
381
311
  },
382
312
  });
383
313
  return { ok: true };
384
314
  }
385
315
  async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps = {}) {
386
- const stateResult = readOrBootstrapProviderStateForCheck(agentName, bundlesRoot, deps);
387
- if (!stateResult.ok)
388
- return stateResult.result;
389
- if (stateResult.disabled)
316
+ const selectionResult = readProviderSelectionForCheck(agentName, bundlesRoot);
317
+ if (!selectionResult.ok)
318
+ return selectionResult.result;
319
+ if (selectionResult.disabled)
390
320
  return { ok: true };
391
- // Layer 4 drift detection. Runs once per call after state setup so that
392
- // drift findings ride along regardless of ping outcome — consumers
393
- // (Layer 4 rollup, Layer 3 RepairGuide) want to see drift even when a
394
- // live-check is failing for unrelated reasons. Drift detection is pure
395
- // and never throws on a malformed agent.json: any read error here would
396
- // indicate the bundle disappeared between state setup and now (a
397
- // race), which we surface as `[]` rather than a hard failure.
398
- let driftFindings = [];
399
- try {
400
- const inputs = (0, drift_detection_1.loadDriftInputsForAgent)(bundlesRoot, agentName);
401
- driftFindings = (0, drift_detection_1.detectProviderBindingDrift)({
402
- agentName,
403
- agentJson: inputs.agentJson,
404
- providerState: inputs.providerState,
405
- });
406
- }
407
- catch {
408
- /* v8 ignore next 1 -- defensive race-window guard: agent.json went away after state setup. Tested via Unit 5 integration when at all. @preserve */
409
- driftFindings = [];
410
- }
411
- deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
321
+ deps.onProgress?.(selectedProviderPlan(agentName, selectionResult.bindings));
412
322
  const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
413
- const shouldRecordReadiness = deps.recordReadiness ?? true;
414
- const providers = selectedProvidersForState(stateResult.state);
323
+ const providers = selectedProvidersForBindings(selectionResult.bindings);
415
324
  const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, {
416
325
  ...(deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : {}),
417
326
  providers,
418
327
  preserveCachedOnFailure: true,
419
328
  });
420
329
  const pingGroups = new Map();
421
- const lanes = ["outward", "inner"];
422
- for (const lane of lanes) {
423
- const binding = stateResult.state.lanes[lane];
330
+ for (const { lane } of LANES) {
331
+ const binding = selectionResult.bindings[lane];
424
332
  if (!poolResult.ok) {
425
333
  if (poolResult.reason === "missing") {
426
- return { ...missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath), driftFindings };
334
+ return missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath);
427
335
  }
428
- return { ...invalidPoolResult(agentName, lane, binding.provider, binding.model, {
429
- ...poolResult,
430
- reason: poolResult.reason,
431
- }), driftFindings };
336
+ return invalidPoolResult(agentName, lane, binding.provider, binding.model, {
337
+ ...poolResult,
338
+ reason: poolResult.reason,
339
+ });
432
340
  }
433
341
  const record = credentialRecordForLane(poolResult.pool, binding.provider);
434
342
  if (!record) {
435
- return { ...missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath), driftFindings };
343
+ return missingCredentialResult(agentName, lane, binding.provider, binding.model, poolResult.poolPath);
436
344
  }
437
345
  const key = `${binding.provider}\0${binding.model}\0${record.revision}`;
438
346
  const group = pingGroups.get(key);
@@ -466,37 +374,37 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
466
374
  let firstFailure = null;
467
375
  for (const { group, result } of pingResults) {
468
376
  if (!result.ok) {
469
- if (shouldRecordReadiness) {
470
- for (const lane of group.lanes) {
471
- writeLaneReadiness({
472
- agentRoot: stateResult.agentRoot,
473
- state: stateResult.state,
474
- lane,
475
- status: "failed",
476
- credentialRevision: group.record.revision,
477
- error: result.message,
478
- attempts: pingAttemptCount(result),
479
- });
480
- }
481
- }
482
- firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
483
- continue;
484
- }
485
- if (shouldRecordReadiness) {
486
377
  for (const lane of group.lanes) {
487
- writeLaneReadiness({
488
- agentRoot: stateResult.agentRoot,
489
- state: stateResult.state,
378
+ (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
379
+ agentName,
490
380
  lane,
491
- status: "ready",
381
+ provider: group.provider,
382
+ model: group.model,
492
383
  credentialRevision: group.record.revision,
384
+ status: "failed",
385
+ checkedAt: new Date().toISOString(),
386
+ error: result.message,
493
387
  attempts: pingAttemptCount(result),
494
388
  });
495
389
  }
390
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
391
+ continue;
392
+ }
393
+ for (const lane of group.lanes) {
394
+ (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
395
+ agentName,
396
+ lane,
397
+ provider: group.provider,
398
+ model: group.model,
399
+ credentialRevision: group.record.revision,
400
+ status: "ready",
401
+ checkedAt: new Date().toISOString(),
402
+ attempts: pingAttemptCount(result),
403
+ });
496
404
  }
497
405
  }
498
406
  if (firstFailure)
499
- return { ...firstFailure, driftFindings };
407
+ return firstFailure;
500
408
  (0, runtime_1.emitNervesEvent)({
501
409
  component: "daemon",
502
410
  event: "daemon.agent_config_valid",
@@ -505,8 +413,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
505
413
  agent: agentName,
506
414
  providers: [...new Set([...pingGroups.values()].map((group) => group.provider))],
507
415
  liveProviderCheck: true,
508
- driftCount: driftFindings.length,
509
416
  },
510
417
  });
511
- return { ok: true, driftFindings };
418
+ return { ok: true };
512
419
  }
@@ -68,7 +68,7 @@ function buildSystemPrompt(degraded) {
68
68
  "and the simplest fix the user can apply.",
69
69
  ].join("\n");
70
70
  }
71
- function buildUserMessage(degraded, logsTail, driftFindings = [], syncFindings = []) {
71
+ function buildUserMessage(degraded, logsTail, syncFindings = []) {
72
72
  const agentDetails = degraded
73
73
  .map((d) => `Agent: ${d.agent}\n Error: ${d.errorReason}\n Fix hint: ${d.fixHint}`)
74
74
  .join("\n\n");
@@ -80,13 +80,6 @@ function buildUserMessage(degraded, logsTail, driftFindings = [], syncFindings =
80
80
  "Recent daemon logs:",
81
81
  logsTail,
82
82
  ];
83
- // Layer 3: thread Layer 4's structured drift findings into the prompt as
84
- // a JSON block. The `diagnose-bootstrap-drift` skill (loaded into the
85
- // system prompt by `buildSystemPromptWithRepairGuide`) instructs the LLM
86
- // how to read this shape and what `ouro use` repair to propose.
87
- if (driftFindings.length > 0) {
88
- sections.push("", "driftFindings (DriftFinding[]):", "```json", JSON.stringify(driftFindings, null, 2), "```");
89
- }
90
83
  // Layer 3: thread Layer 2's structured sync-probe findings into the
91
84
  // prompt as a JSON block. `diagnose-broken-remote` (auth/404/network/
92
85
  // timeout-hard) and `diagnose-sync-blocked` (dirty/non-fast-forward/
@@ -158,7 +151,7 @@ async function tryAgenticDiagnosis(degraded, provider, deps) {
158
151
  const repoRoot = deps.repoRootOverride ?? (0, identity_1.getRepoRoot)();
159
152
  const repairGuide = loadRepairGuideContent(repoRoot);
160
153
  const systemPrompt = buildSystemPromptWithRepairGuide(degraded, repairGuide);
161
- const userMessage = buildUserMessage(degraded, logsTail, deps.driftFindings, deps.syncFindings);
154
+ const userMessage = buildUserMessage(degraded, logsTail, deps.syncFindings);
162
155
  const messages = [
163
156
  { role: "system", content: systemPrompt },
164
157
  { role: "user", content: userMessage },
@@ -77,7 +77,10 @@ const cli_parse_1 = require("./cli-parse");
77
77
  const provider_discovery_1 = require("./provider-discovery");
78
78
  const provider_credentials_1 = require("../provider-credentials");
79
79
  // ── Default implementations ──
80
- function defaultStartDaemonProcess(socketPath) {
80
+ async function defaultStartDaemonProcess(socketPath) {
81
+ const launchdStarted = await startDaemonProcessViaLaunchd(socketPath);
82
+ if (launchdStarted)
83
+ return launchdStarted;
81
84
  const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
82
85
  // Redirect stdio to /dev/null via file descriptors — using 'ignore' causes EPIPE
83
86
  // when the daemon's logging system writes to stderr after the parent exits.
@@ -89,7 +92,7 @@ function defaultStartDaemonProcess(socketPath) {
89
92
  });
90
93
  child.unref();
91
94
  // Don't close fds — the child process needs them. They'll be cleaned up when the parent exits.
92
- return Promise.resolve({ pid: child.pid ?? null });
95
+ return { pid: child.pid ?? null };
93
96
  }
94
97
  function defaultWriteStdout(text) {
95
98
  process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
@@ -224,6 +227,32 @@ function currentUserUid() {
224
227
  function launchAgentDomain(userUid = currentUserUid()) {
225
228
  return `gui/${userUid}`;
226
229
  }
230
+ function writeDaemonBootPlist(socketPath) {
231
+ const homeDir = os.homedir();
232
+ const writeDeps = {
233
+ writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
234
+ mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
235
+ homeDir,
236
+ };
237
+ const entryPath = resolveDaemonBootEntryPath(homeDir);
238
+ /* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
239
+ if (!fs.existsSync(entryPath)) {
240
+ (0, runtime_1.emitNervesEvent)({
241
+ level: "warn",
242
+ component: "daemon",
243
+ event: "daemon.entry_path_missing",
244
+ message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
245
+ meta: { entryPath },
246
+ });
247
+ }
248
+ return (0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
249
+ nodePath: process.execPath,
250
+ entryPath,
251
+ socketPath,
252
+ logDir: (0, identity_1.getAgentDaemonLogsDir)(),
253
+ envPath: process.env.PATH,
254
+ });
255
+ }
227
256
  function isDaemonLaunchAgentLoaded(deps) {
228
257
  const userUid = deps?.userUid ?? currentUserUid();
229
258
  const exec = deps?.exec ?? ((cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); });
@@ -235,6 +264,16 @@ function isDaemonLaunchAgentLoaded(deps) {
235
264
  return false;
236
265
  }
237
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
+ }
238
277
  async function waitForBootstrappedDaemonSocket(socketPath, deps = {}) {
239
278
  const checkSocketAlive = deps.checkSocketAlive ?? socket_client_1.checkDaemonSocketAlive;
240
279
  const now = deps.now ?? Date.now;
@@ -267,69 +306,60 @@ async function waitForBootstrappedDaemonSocket(socketPath, deps = {}) {
267
306
  meta: { socketPath },
268
307
  });
269
308
  }
270
- async function defaultEnsureDaemonBootPersistence(socketPath) {
309
+ async function startDaemonProcessViaLaunchd(socketPath) {
271
310
  if (process.platform !== "darwin") {
272
- return;
273
- }
274
- const homeDir = os.homedir();
275
- const writeDeps = {
276
- writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
277
- mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
278
- homeDir,
279
- };
280
- const entryPath = resolveDaemonBootEntryPath(homeDir);
281
- /* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
282
- if (!fs.existsSync(entryPath)) {
283
- (0, runtime_1.emitNervesEvent)({
284
- level: "warn",
285
- component: "daemon",
286
- event: "daemon.entry_path_missing",
287
- message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
288
- meta: { entryPath },
289
- });
311
+ return null;
290
312
  }
291
- const logDir = (0, identity_1.getAgentDaemonLogsDir)();
292
- const plistPath = (0, launchd_1.writeLaunchAgentPlist)(writeDeps, {
293
- nodePath: process.execPath,
294
- entryPath,
295
- socketPath,
296
- logDir,
297
- envPath: process.env.PATH,
298
- });
313
+ const plistPath = writeDaemonBootPlist(socketPath);
299
314
  const userUid = currentUserUid();
300
- if (isDaemonLaunchAgentLoaded({ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); }, userUid })) {
301
- (0, runtime_1.emitNervesEvent)({
302
- component: "daemon",
303
- event: "daemon.launchd_bootstrap_skipped_loaded",
304
- message: "daemon launch agent already loaded",
305
- meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
306
- });
307
- return;
308
- }
315
+ const domain = launchAgentDomain(userUid);
309
316
  try {
310
317
  (0, runtime_1.emitNervesEvent)({
311
318
  component: "daemon",
312
319
  event: "daemon.launchd_bootstrap_start",
313
- message: "bootstrapping daemon launch agent for current login session",
320
+ message: "starting daemon launch agent for current login session",
314
321
  meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
315
322
  });
316
- (0, child_process_1.execSync)(`launchctl bootstrap ${launchAgentDomain(userUid)} "${plistPath}"`, { stdio: "ignore" });
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
+ }
317
329
  (0, runtime_1.emitNervesEvent)({
318
330
  component: "daemon",
319
331
  event: "daemon.launchd_bootstrap_end",
320
- message: "daemon launch agent bootstrapped for current login session",
332
+ message: "daemon launch agent started for current login session",
321
333
  meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL },
322
334
  });
323
335
  await waitForBootstrappedDaemonSocket(socketPath);
336
+ return { pid: readDaemonLaunchAgentPid() };
324
337
  }
325
338
  catch (error) {
326
339
  (0, runtime_1.emitNervesEvent)({
327
340
  level: "warn",
328
341
  component: "daemon",
329
342
  event: "daemon.launchd_bootstrap_error",
330
- message: "failed to bootstrap daemon launch agent for current login session",
343
+ message: "failed to start daemon launch agent for current login session",
331
344
  meta: { plistPath, label: launchd_1.DAEMON_PLIST_LABEL, error: error instanceof Error ? error.message : String(error) },
332
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;
333
363
  }
334
364
  }
335
365
  function defaultPrepareDaemonRuntimeReplacement() {