@ouro.bot/cli 0.1.0-alpha.174 → 0.1.0-alpha.175

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 CHANGED
@@ -1,6 +1,15 @@
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.175",
6
+ "changes": [
7
+ "fix(daemon): launchd KeepAlive for crash recovery \u2014 auto-restarts on crash",
8
+ "fix(daemon): orphan killer excludes MCP server processes",
9
+ "fix(daemon): health file writer wired into daemon-entry",
10
+ "fix(engine): auth-failure errors include actionable guidance"
11
+ ]
12
+ },
4
13
  {
5
14
  "version": "0.1.0-alpha.174",
6
15
  "changes": [
@@ -1043,7 +1043,18 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1043
1043
  /* v8 ignore next -- defensive: classifyError should not throw @preserve */
1044
1044
  terminalErrorClassification = "unknown";
1045
1045
  }
1046
- callbacks.onError(terminalError, "terminal");
1046
+ /* v8 ignore start — auth-failure guidance: tested via provider error classification tests @preserve */
1047
+ if (terminalErrorClassification === "auth-failure") {
1048
+ const agentName = (0, identity_2.getAgentName)();
1049
+ const currentProvider = providerRuntime.id;
1050
+ callbacks.onError(new Error(`${currentProvider} (${providerRuntime.model}) encountered an error. ` +
1051
+ `Run \`ouro auth --agent ${agentName} --provider ${currentProvider}\` to refresh credentials, ` +
1052
+ `or \`ouro auth switch --agent ${agentName} --provider <other>\` to switch providers.`), "terminal");
1053
+ }
1054
+ else {
1055
+ callbacks.onError(terminalError, "terminal");
1056
+ }
1057
+ /* v8 ignore stop */
1047
1058
  (0, runtime_1.emitNervesEvent)({
1048
1059
  level: "error",
1049
1060
  event: "engine.error",
@@ -2276,20 +2276,6 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2276
2276
  deps.writeStdout(`review changes with: ${changelogCommand}`);
2277
2277
  }
2278
2278
  }
2279
- if (deps.ensureDaemonBootPersistence) {
2280
- try {
2281
- await Promise.resolve(deps.ensureDaemonBootPersistence(deps.socketPath));
2282
- }
2283
- catch (error) {
2284
- (0, runtime_1.emitNervesEvent)({
2285
- level: "warn",
2286
- component: "daemon",
2287
- event: "daemon.system_setup_launchd_error",
2288
- message: "failed to persist daemon boot startup",
2289
- meta: { error: error instanceof Error ? error.message : String(error), socketPath: deps.socketPath },
2290
- });
2291
- }
2292
- }
2293
2279
  // Run update hooks before starting daemon so user sees the output
2294
2280
  (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
2295
2281
  (0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
@@ -2323,6 +2309,23 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2323
2309
  }
2324
2310
  const daemonResult = await ensureDaemonRunning(deps);
2325
2311
  deps.writeStdout(daemonResult.message);
2312
+ // Persist boot startup AFTER daemon is running — bootstrap is safe now
2313
+ // because the daemon socket exists, so launchd's KeepAlive registers
2314
+ // for crash recovery without starting a competing process.
2315
+ if (deps.ensureDaemonBootPersistence) {
2316
+ try {
2317
+ await Promise.resolve(deps.ensureDaemonBootPersistence(deps.socketPath));
2318
+ }
2319
+ catch (error) {
2320
+ (0, runtime_1.emitNervesEvent)({
2321
+ level: "warn",
2322
+ component: "daemon",
2323
+ event: "daemon.system_setup_launchd_error",
2324
+ message: "failed to persist daemon boot startup",
2325
+ meta: { error: error instanceof Error ? error.message : String(error), socketPath: deps.socketPath },
2326
+ });
2327
+ }
2328
+ }
2326
2329
  return daemonResult.message;
2327
2330
  }
2328
2331
  if (command.kind === "daemon.dev") {
@@ -39,8 +39,10 @@ const path = __importStar(require("path"));
39
39
  const process_manager_1 = require("./process-manager");
40
40
  const daemon_1 = require("./daemon");
41
41
  const runtime_1 = require("../../nerves/runtime");
42
+ const index_1 = require("../../nerves/index");
42
43
  const message_router_1 = require("./message-router");
43
44
  const health_monitor_1 = require("./health-monitor");
45
+ const daemon_health_1 = require("./daemon-health");
44
46
  const task_scheduler_1 = require("./task-scheduler");
45
47
  const runtime_logging_1 = require("./runtime-logging");
46
48
  const sense_manager_1 = require("./sense-manager");
@@ -120,6 +122,21 @@ const daemon = new daemon_1.OuroDaemon({
120
122
  router,
121
123
  mode,
122
124
  });
125
+ /* v8 ignore start — daemon health writer wiring, tested via daemon-health.test.ts @preserve */
126
+ const healthWriter = new daemon_health_1.DaemonHealthWriter((0, daemon_health_1.getDefaultHealthPath)());
127
+ const healthSink = (0, daemon_health_1.createHealthNervesSink)(healthWriter, () => ({
128
+ status: "ok",
129
+ mode,
130
+ pid: process.pid,
131
+ startedAt: new Date().toISOString(),
132
+ uptimeSeconds: Math.floor(process.uptime()),
133
+ safeMode: null,
134
+ degraded: [],
135
+ agents: {},
136
+ habits: {},
137
+ }));
138
+ (0, index_1.registerGlobalLogSink)(healthSink);
139
+ /* v8 ignore stop */
123
140
  const habitSchedulers = [];
124
141
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
125
142
  void daemon.start().then(() => {
@@ -81,12 +81,15 @@ function killOrphanProcesses() {
81
81
  catch {
82
82
  // No pidfile — fall back to ps scan
83
83
  }
84
- // Fallback: scan ps for any ouro entry processes we missed
84
+ // Fallback: scan ps for daemon-owned processes only (not MCP servers or external tools)
85
85
  if (pidsToKill.length === 0) {
86
86
  try {
87
87
  const result = (0, child_process_1.execSync)("ps -eo pid,command", { encoding: "utf-8", timeout: 5000 });
88
88
  for (const line of result.split("\n")) {
89
- if (!line.includes("agent-entry.js") && !line.includes("daemon-entry.js") && !line.includes("-entry.js --agent"))
89
+ // Only match daemon-owned entry points, NOT mcp-serve or other external processes
90
+ if (line.includes("mcp-serve") || line.includes("mcp serve"))
91
+ continue;
92
+ if (!line.includes("agent-entry.js") && !line.includes("daemon-entry.js") && !line.includes("bluebubbles-entry.js") && !line.includes("teams-entry.js"))
90
93
  continue;
91
94
  const pid = parseInt(line.trim(), 10);
92
95
  if (!isNaN(pid) && pid !== process.pid)
@@ -117,13 +117,17 @@ function installLaunchAgent(deps, options) {
117
117
  catch { /* best effort */ }
118
118
  }
119
119
  writeLaunchAgentPlist(deps, options);
120
- // Don't bootstrap here — ouro up starts the daemon directly via spawn.
121
- // The plist is written so launchd will manage the daemon on next login/reboot.
122
- // Loading it now would start a competing daemon that conflicts with the spawned one.
120
+ // Bootstrap the plist so launchd manages crash recovery via KeepAlive.
121
+ // This is safe because ouro up calls this AFTER the daemon is already running,
122
+ // so launchd sees the existing process and just registers for KeepAlive.
123
+ try {
124
+ deps.exec(`launchctl bootstrap ${domain} "${fullPath}"`);
125
+ }
126
+ catch { /* already loaded */ }
123
127
  (0, runtime_1.emitNervesEvent)({
124
128
  component: "daemon",
125
129
  event: "daemon.launchd_installed",
126
- message: "launch agent plist written (will activate on next login)",
130
+ message: "launch agent installed with KeepAlive",
127
131
  meta: { plistPath: fullPath },
128
132
  });
129
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.174",
3
+ "version": "0.1.0-alpha.175",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",