@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
|
-
|
|
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
|
-
|
|
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
|
|
1212
|
-
//
|
|
1213
|
-
// failures are noted in `detail` for transparency but do NOT
|
|
1214
|
-
// status to error: a single permanently-unrecoverable message
|
|
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:
|
|
1226
|
+
upstreamStatus: "ok",
|
|
1219
1227
|
detail: queued > 0
|
|
1220
|
-
? `
|
|
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",
|