@ouro.bot/cli 0.1.0-alpha.522 → 0.1.0-alpha.523

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,14 @@
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.523",
6
+ "changes": [
7
+ "Exits the daemon entrypoint after a command-plane `daemon.stop`, preventing a stopped daemon from staying alive without its command socket and leaving Slugger's iMessage worker marked degraded.",
8
+ "Shares daemon entrypoint cleanup between signal stops and command stops, stopping habit schedulers and daemon health polling before the process exits.",
9
+ "Keeps BlueBubbles runtime health green as soon as upstream is reachable while queued captured-inbound, mutation, or catch-up recovery work remains visible in `pendingRecoveryCount` instead of making live iMessage look down."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.522",
6
14
  "changes": [
@@ -149,6 +149,25 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
149
149
  catch { /* recovery is best-effort */ }
150
150
  },
151
151
  });
152
+ const habitSchedulers = [];
153
+ let entryRuntimeStopping = false;
154
+ let stopCommandExitScheduled = false;
155
+ function stopEntryRuntime() {
156
+ if (entryRuntimeStopping)
157
+ return;
158
+ entryRuntimeStopping = true;
159
+ for (const s of habitSchedulers) {
160
+ s.stopWatching();
161
+ s.stop();
162
+ }
163
+ healthMonitor.stopPeriodicChecks();
164
+ }
165
+ function scheduleCleanProcessExitAfterStopCommand() {
166
+ if (stopCommandExitScheduled)
167
+ return;
168
+ stopCommandExitScheduled = true;
169
+ setTimeout(() => process.exit(0), 100);
170
+ }
152
171
  const daemon = new daemon_1.OuroDaemon({
153
172
  socketPath,
154
173
  processManager,
@@ -157,6 +176,10 @@ const daemon = new daemon_1.OuroDaemon({
157
176
  healthMonitor,
158
177
  router,
159
178
  mode,
179
+ onStopCommandComplete: () => {
180
+ stopEntryRuntime();
181
+ scheduleCleanProcessExitAfterStopCommand();
182
+ },
160
183
  });
161
184
  const daemonStartedAt = new Date().toISOString();
162
185
  const degradedComponents = [];
@@ -298,7 +321,6 @@ const healthWriter = new daemon_health_1.DaemonHealthWriter((0, daemon_health_1.
298
321
  const healthSink = (0, daemon_health_1.createHealthNervesSink)(healthWriter, buildDaemonHealthState);
299
322
  (0, index_1.registerGlobalLogSink)(healthSink);
300
323
  /* v8 ignore stop */
301
- const habitSchedulers = [];
302
324
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
303
325
  void daemon.start().then(() => {
304
326
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
@@ -392,22 +414,14 @@ process.on("SIGINT", () => {
392
414
  // tombstone is strictly better than silence.
393
415
  _tombstoneWritten = true;
394
416
  (0, daemon_tombstone_1.writeDaemonTombstone)("sigint", new Error("daemon received SIGINT"));
395
- for (const s of habitSchedulers) {
396
- s.stopWatching();
397
- s.stop();
398
- }
399
- healthMonitor.stopPeriodicChecks();
417
+ stopEntryRuntime();
400
418
  setTimeout(() => process.exit(1), 5_000).unref();
401
419
  void daemon.stop().then(() => process.exit(0));
402
420
  });
403
421
  process.on("SIGTERM", () => {
404
422
  _tombstoneWritten = true;
405
423
  (0, daemon_tombstone_1.writeDaemonTombstone)("sigterm", new Error("daemon received SIGTERM"));
406
- for (const s of habitSchedulers) {
407
- s.stopWatching();
408
- s.stop();
409
- }
410
- healthMonitor.stopPeriodicChecks();
424
+ stopEntryRuntime();
411
425
  setTimeout(() => process.exit(1), 5_000).unref();
412
426
  void daemon.stop().then(() => process.exit(0));
413
427
  });
@@ -418,6 +418,7 @@ class OuroDaemon {
418
418
  socketIdentity = null;
419
419
  senseAutostartTimer = null;
420
420
  outlookServerFactory;
421
+ onStopCommandComplete;
421
422
  constructor(options) {
422
423
  this.socketPath = options.socketPath;
423
424
  this.processManager = options.processManager;
@@ -428,6 +429,7 @@ class OuroDaemon {
428
429
  this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
429
430
  this.mode = options.mode ?? "production";
430
431
  this.outlookServerFactory = options.outlookServerFactory ?? this.createDefaultOutlookServer.bind(this);
432
+ this.onStopCommandComplete = options.onStopCommandComplete ?? null;
431
433
  }
432
434
  /* v8 ignore start -- default outlook server wiring: production-only path, tests inject outlookServerFactory stub instead. startOutlookHttpServer itself has full coverage in outlook-http.test.ts @preserve */
433
435
  createDefaultOutlookServer() {
@@ -906,6 +908,7 @@ class OuroDaemon {
906
908
  (0, update_checker_1.stopUpdateChecker)();
907
909
  (0, mcp_manager_1.shutdownSharedMcpManager)();
908
910
  this.scheduler.stop?.();
911
+ this.healthMonitor.stopPeriodicChecks?.();
909
912
  if (this.senseAutostartTimer) {
910
913
  clearTimeout(this.senseAutostartTimer);
911
914
  this.senseAutostartTimer = null;
@@ -1022,6 +1025,7 @@ class OuroDaemon {
1022
1025
  return { ok: true, message: "daemon started" };
1023
1026
  case "daemon.stop":
1024
1027
  await this.stop();
1028
+ this.onStopCommandComplete?.();
1025
1029
  return { ok: true, message: "daemon stopped" };
1026
1030
  case "daemon.status": {
1027
1031
  const data = this.buildStatusPayload();
@@ -1203,21 +1203,29 @@ async function syncBlueBubblesRuntime(deps = {}) {
1203
1203
  await client.checkHealth();
1204
1204
  const capturedPending = countPendingCapturedInboundMessages(agentName);
1205
1205
  const recoveryPending = countPendingRecoveryCandidates(agentName);
1206
+ (0, runtime_state_1.writeBlueBubblesRuntimeState)(agentName, {
1207
+ upstreamStatus: "ok",
1208
+ detail: "upstream reachable; recovery pass running",
1209
+ lastCheckedAt: checkedAt,
1210
+ pendingRecoveryCount: capturedPending + recoveryPending,
1211
+ lastRecoveredAt: previousState.lastRecoveredAt,
1212
+ lastRecoveredMessageGuid: previousState.lastRecoveredMessageGuid,
1213
+ });
1206
1214
  const catchUp = await catchUpMissedBlueBubblesMessages(resolvedDeps, previousState, {
1207
1215
  processTurns: false,
1208
1216
  });
1209
1217
  const failed = catchUp.failed;
1210
1218
  const queued = capturedPending + recoveryPending + (catchUp.queued ?? 0);
1211
- // upstreamStatus reflects whether BlueBubbles itself is healthy and we
1212
- // have unprocessed work (pendingRecoveryCount). Per-cycle recovery
1213
- // failures are noted in `detail` for transparency but do NOT flip the
1214
- // status to error: a single permanently-unrecoverable message would
1215
- // otherwise stick the sense in "error" forever, contradicting `ouro
1219
+ // upstreamStatus reflects whether BlueBubbles itself is healthy and
1220
+ // whether the local bridge can answer webhook traffic. Queued recovery work
1221
+ // and per-cycle failures are noted in `detail` for transparency but do NOT
1222
+ // flip the status to error: a single permanently-unrecoverable message
1223
+ // would otherwise stick the sense in "error" forever, contradicting `ouro
1216
1224
  // doctor` which only checks upstream reachability.
1217
1225
  (0, runtime_state_1.writeBlueBubblesRuntimeState)(agentName, {
1218
- upstreamStatus: queued > 0 ? "error" : "ok",
1226
+ upstreamStatus: "ok",
1219
1227
  detail: queued > 0
1220
- ? `pending recovery: ${queued}`
1228
+ ? `upstream reachable; ${queued} recovery item(s) queued`
1221
1229
  : failed > 0
1222
1230
  ? `${failed} message(s) unrecoverable this cycle; upstream ok`
1223
1231
  : "upstream reachable",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.522",
3
+ "version": "0.1.0-alpha.523",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",