@ouro.bot/cli 0.1.0-alpha.541 → 0.1.0-alpha.542
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.542",
|
|
6
|
+
"changes": [
|
|
7
|
+
"BlueBubbles recovery timeouts now leave iMessage messages pending instead of marking them processed, and legacy `recovery-timeout` processed records no longer suppress retries.",
|
|
8
|
+
"Recovery turns now get a longer bounded timeout and keep their in-flight guard until the abandoned turn settles, preventing duplicate retries while preserving truthful pending recovery state.",
|
|
9
|
+
"iMessage recovery health now counts pending work by message GUID across captured inbound and mutation sidecars, preventing one missed message from being reported as multiple queued items."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.541",
|
|
6
14
|
"changes": [
|
|
@@ -170,7 +170,7 @@ const defaultDeps = {
|
|
|
170
170
|
const BLUEBUBBLES_RUNTIME_SYNC_INTERVAL_MS = 30_000;
|
|
171
171
|
const BLUEBUBBLES_RECOVERY_PASS_DELAY_MS = 1_000;
|
|
172
172
|
const BLUEBUBBLES_RECOVERY_PASS_INTERVAL_MS = 30_000;
|
|
173
|
-
const BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS = 60_000;
|
|
173
|
+
const BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS = 10 * 60_000;
|
|
174
174
|
const BLUEBUBBLES_CATCHUP_PAGE_SIZE = 50;
|
|
175
175
|
const BLUEBUBBLES_CATCHUP_MAX_PAGES = 20;
|
|
176
176
|
const BLUEBUBBLES_HEALTHY_CATCHUP_OVERLAP_MS = 90_000;
|
|
@@ -182,10 +182,6 @@ class BlueBubblesRecoveryTurnTimeoutError extends Error {
|
|
|
182
182
|
this.name = "BlueBubblesRecoveryTurnTimeoutError";
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
function isBlueBubblesRecoveryTurnTimeoutError(error) {
|
|
186
|
-
return error instanceof BlueBubblesRecoveryTurnTimeoutError
|
|
187
|
-
|| (error instanceof Error && error.name === "BlueBubblesRecoveryTurnTimeoutError");
|
|
188
|
-
}
|
|
189
185
|
function resolveFriendParams(event) {
|
|
190
186
|
if (event.chat.isGroup) {
|
|
191
187
|
const groupKey = event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.sender.externalId;
|
|
@@ -827,6 +823,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
827
823
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "mutation_state_only" };
|
|
828
824
|
}
|
|
829
825
|
let ownsInFlightMessage = false;
|
|
826
|
+
let releaseInFlightAfterTurnSettles = false;
|
|
830
827
|
if (event.kind === "message") {
|
|
831
828
|
if ((0, processed_log_1.hasProcessedBlueBubblesMessage)(agentName, event.chat.sessionKey, event.messageGuid)
|
|
832
829
|
|| (0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, event.messageGuid)) {
|
|
@@ -989,6 +986,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
989
986
|
timeoutTimer = setTimeout(() => {
|
|
990
987
|
const reason = new BlueBubblesRecoveryTurnTimeoutError(timeoutMs);
|
|
991
988
|
recoveryTimedOut = true;
|
|
989
|
+
releaseInFlightAfterTurnSettles = true;
|
|
992
990
|
controller.abort(reason);
|
|
993
991
|
timeoutReject?.(reason);
|
|
994
992
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1072,7 +1070,8 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1072
1070
|
});
|
|
1073
1071
|
/* v8 ignore start -- detached late-rejection telemetry is asserted in timeout tests, but V8 does not reliably attribute Promise.catch callbacks @preserve */
|
|
1074
1072
|
if (timeoutPromise) {
|
|
1075
|
-
void turnPromise
|
|
1073
|
+
void turnPromise
|
|
1074
|
+
.catch((error) => {
|
|
1076
1075
|
if (!recoveryTimedOut)
|
|
1077
1076
|
return;
|
|
1078
1077
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1087,6 +1086,11 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1087
1086
|
reason: error instanceof Error ? error.message : String(error),
|
|
1088
1087
|
},
|
|
1089
1088
|
});
|
|
1089
|
+
})
|
|
1090
|
+
.finally(() => {
|
|
1091
|
+
if (releaseInFlightAfterTurnSettles && ownsInFlightMessage && event.kind === "message") {
|
|
1092
|
+
endBlueBubblesMessageInFlight(event.chat.sessionKey, event.messageGuid);
|
|
1093
|
+
}
|
|
1090
1094
|
});
|
|
1091
1095
|
}
|
|
1092
1096
|
/* v8 ignore stop */
|
|
@@ -1160,7 +1164,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1160
1164
|
});
|
|
1161
1165
|
}
|
|
1162
1166
|
finally {
|
|
1163
|
-
if (ownsInFlightMessage && event.kind === "message") {
|
|
1167
|
+
if (ownsInFlightMessage && event.kind === "message" && !releaseInFlightAfterTurnSettles) {
|
|
1164
1168
|
endBlueBubblesMessageInFlight(event.chat.sessionKey, event.messageGuid);
|
|
1165
1169
|
}
|
|
1166
1170
|
}
|
|
@@ -1237,14 +1241,6 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
|
1237
1241
|
const event = await client.repairEvent(normalized);
|
|
1238
1242
|
return handleBlueBubblesNormalizedEvent(event, resolvedDeps, "webhook");
|
|
1239
1243
|
}
|
|
1240
|
-
function countPendingRecoveryCandidates(agentName) {
|
|
1241
|
-
return (0, mutation_log_1.listBlueBubblesRecoveryCandidates)(agentName)
|
|
1242
|
-
.filter((entry) => !(0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid))
|
|
1243
|
-
.length;
|
|
1244
|
-
}
|
|
1245
|
-
function countPendingCapturedInboundMessages(agentName) {
|
|
1246
|
-
return listPendingCapturedInboundMessages(agentName).length;
|
|
1247
|
-
}
|
|
1248
1244
|
function listPendingCapturedInboundMessages(agentName) {
|
|
1249
1245
|
const seenMessageGuids = new Set();
|
|
1250
1246
|
return (0, inbound_log_1.listRecordedBlueBubblesInbound)(agentName)
|
|
@@ -1256,6 +1252,30 @@ function listPendingCapturedInboundMessages(agentName) {
|
|
|
1256
1252
|
})
|
|
1257
1253
|
.filter((entry) => !(0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid));
|
|
1258
1254
|
}
|
|
1255
|
+
function listPendingRecoveryEntries(agentName) {
|
|
1256
|
+
const pendingByGuid = new Map();
|
|
1257
|
+
const add = (messageGuid, recordedAt) => {
|
|
1258
|
+
if ((0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, messageGuid))
|
|
1259
|
+
return;
|
|
1260
|
+
const previous = pendingByGuid.get(messageGuid);
|
|
1261
|
+
if (!previous) {
|
|
1262
|
+
pendingByGuid.set(messageGuid, recordedAt);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const previousMs = Date.parse(previous);
|
|
1266
|
+
const nextMs = Date.parse(recordedAt);
|
|
1267
|
+
if (Number.isFinite(nextMs) && (!Number.isFinite(previousMs) || nextMs < previousMs)) {
|
|
1268
|
+
pendingByGuid.set(messageGuid, recordedAt);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
for (const entry of listPendingCapturedInboundMessages(agentName)) {
|
|
1272
|
+
add(entry.messageGuid, entry.recordedAt);
|
|
1273
|
+
}
|
|
1274
|
+
for (const entry of (0, mutation_log_1.listBlueBubblesRecoveryCandidates)(agentName)) {
|
|
1275
|
+
add(entry.messageGuid, entry.recordedAt);
|
|
1276
|
+
}
|
|
1277
|
+
return [...pendingByGuid].map(([messageGuid, recordedAt]) => ({ messageGuid, recordedAt }));
|
|
1278
|
+
}
|
|
1259
1279
|
function parseTimestampMs(value) {
|
|
1260
1280
|
if (!value)
|
|
1261
1281
|
return null;
|
|
@@ -1280,18 +1300,15 @@ function formatBlueBubblesRuntimeDetail(queued, failed) {
|
|
|
1280
1300
|
return "upstream reachable";
|
|
1281
1301
|
}
|
|
1282
1302
|
function blueBubblesPendingRecoverySnapshot(agentName, nowMs = Date.now()) {
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
.filter((entry) => !(0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid))
|
|
1287
|
-
.map((entry) => entry.recordedAt),
|
|
1288
|
-
]
|
|
1303
|
+
const pendingEntries = listPendingRecoveryEntries(agentName);
|
|
1304
|
+
const pendingRecordedAt = pendingEntries
|
|
1305
|
+
.map((entry) => entry.recordedAt)
|
|
1289
1306
|
.map((value) => ({ value, ms: Date.parse(value) }))
|
|
1290
1307
|
.filter((entry) => Number.isFinite(entry.ms))
|
|
1291
1308
|
.sort((left, right) => left.ms - right.ms);
|
|
1292
1309
|
const oldest = pendingRecordedAt[0];
|
|
1293
1310
|
return {
|
|
1294
|
-
pendingRecoveryCount:
|
|
1311
|
+
pendingRecoveryCount: pendingEntries.length,
|
|
1295
1312
|
oldestPendingRecoveryAt: oldest?.value,
|
|
1296
1313
|
oldestPendingRecoveryAgeMs: oldest ? Math.max(0, nowMs - oldest.ms) : undefined,
|
|
1297
1314
|
};
|
|
@@ -1484,14 +1501,12 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
1484
1501
|
result.queued = (result.queued ?? 0) + 1;
|
|
1485
1502
|
continue;
|
|
1486
1503
|
}
|
|
1487
|
-
let repairedMessage = null;
|
|
1488
1504
|
try {
|
|
1489
1505
|
const repaired = await client.repairEvent(event);
|
|
1490
1506
|
if (repaired.kind !== "message") {
|
|
1491
1507
|
result.skipped++;
|
|
1492
1508
|
continue;
|
|
1493
1509
|
}
|
|
1494
|
-
repairedMessage = repaired;
|
|
1495
1510
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "upstream-catchup", {
|
|
1496
1511
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
1497
1512
|
});
|
|
@@ -1505,9 +1520,6 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
1505
1520
|
}
|
|
1506
1521
|
catch (error) {
|
|
1507
1522
|
result.failed++;
|
|
1508
|
-
if (repairedMessage && isBlueBubblesRecoveryTurnTimeoutError(error)) {
|
|
1509
|
-
(0, processed_log_1.recordProcessedBlueBubblesMessage)(agentName, repairedMessage, "upstream-catchup", "recovery-timeout");
|
|
1510
|
-
}
|
|
1511
1523
|
(0, runtime_1.emitNervesEvent)({
|
|
1512
1524
|
level: "warn",
|
|
1513
1525
|
component: "senses",
|
|
@@ -1585,14 +1597,12 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
1585
1597
|
result.skipped++;
|
|
1586
1598
|
continue;
|
|
1587
1599
|
}
|
|
1588
|
-
let repairedMessage = null;
|
|
1589
1600
|
try {
|
|
1590
1601
|
const repaired = await client.repairEvent(inboundEntryToRecoveryEvent(entry));
|
|
1591
1602
|
if (repaired.kind !== "message") {
|
|
1592
1603
|
result.skipped++;
|
|
1593
1604
|
continue;
|
|
1594
1605
|
}
|
|
1595
|
-
repairedMessage = repaired;
|
|
1596
1606
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, entry.source, {
|
|
1597
1607
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
1598
1608
|
});
|
|
@@ -1605,9 +1615,6 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
1605
1615
|
}
|
|
1606
1616
|
catch (error) {
|
|
1607
1617
|
result.failed++;
|
|
1608
|
-
if (repairedMessage && isBlueBubblesRecoveryTurnTimeoutError(error)) {
|
|
1609
|
-
(0, processed_log_1.recordProcessedBlueBubblesMessage)(agentName, repairedMessage, entry.source, "recovery-timeout");
|
|
1610
|
-
}
|
|
1611
1618
|
(0, runtime_1.emitNervesEvent)({
|
|
1612
1619
|
level: "warn",
|
|
1613
1620
|
component: "senses",
|
|
@@ -1635,14 +1642,12 @@ async function recoverMissedBlueBubblesMessages(deps = {}) {
|
|
|
1635
1642
|
result.skipped++;
|
|
1636
1643
|
continue;
|
|
1637
1644
|
}
|
|
1638
|
-
let repairedMessage = null;
|
|
1639
1645
|
try {
|
|
1640
1646
|
const repaired = await client.repairEvent(mutationEntryToEvent(candidate));
|
|
1641
1647
|
if (repaired.kind !== "message") {
|
|
1642
1648
|
result.pending++;
|
|
1643
1649
|
continue;
|
|
1644
1650
|
}
|
|
1645
|
-
repairedMessage = repaired;
|
|
1646
1651
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "mutation-recovery", {
|
|
1647
1652
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
1648
1653
|
});
|
|
@@ -1655,9 +1660,6 @@ async function recoverMissedBlueBubblesMessages(deps = {}) {
|
|
|
1655
1660
|
}
|
|
1656
1661
|
catch (error) {
|
|
1657
1662
|
result.failed++;
|
|
1658
|
-
if (repairedMessage && isBlueBubblesRecoveryTurnTimeoutError(error)) {
|
|
1659
|
-
(0, processed_log_1.recordProcessedBlueBubblesMessage)(agentName, repairedMessage, "mutation-recovery", "recovery-timeout");
|
|
1660
|
-
}
|
|
1661
1663
|
(0, runtime_1.emitNervesEvent)({
|
|
1662
1664
|
level: "warn",
|
|
1663
1665
|
component: "senses",
|
|
@@ -71,22 +71,26 @@ function readAllEntries(agentName) {
|
|
|
71
71
|
return [];
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
function isCompletedProcessedEntry(entry) {
|
|
75
|
+
return entry.outcome !== "recovery-timeout";
|
|
76
|
+
}
|
|
74
77
|
function hasProcessedBlueBubblesMessage(agentName, sessionKey, messageGuid) {
|
|
75
78
|
if (!messageGuid.trim())
|
|
76
79
|
return false;
|
|
77
80
|
const filePath = getBlueBubblesProcessedLogPath(agentName, sessionKey);
|
|
78
|
-
return readEntries(filePath).some((entry) => entry.messageGuid === messageGuid);
|
|
81
|
+
return readEntries(filePath).some((entry) => entry.messageGuid === messageGuid && isCompletedProcessedEntry(entry));
|
|
79
82
|
}
|
|
80
83
|
function hasProcessedBlueBubblesMessageGuid(agentName, messageGuid) {
|
|
81
84
|
if (!messageGuid.trim())
|
|
82
85
|
return false;
|
|
83
|
-
return readAllEntries(agentName).some((entry) => entry.messageGuid === messageGuid);
|
|
86
|
+
return readAllEntries(agentName).some((entry) => entry.messageGuid === messageGuid && isCompletedProcessedEntry(entry));
|
|
84
87
|
}
|
|
85
88
|
function recordProcessedBlueBubblesMessage(agentName, event, source, outcome) {
|
|
86
89
|
const filePath = getBlueBubblesProcessedLogPath(agentName, event.chat.sessionKey);
|
|
87
90
|
try {
|
|
88
91
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
89
|
-
if (event.messageGuid.trim() && readEntries(filePath).some((entry) => entry.messageGuid === event.messageGuid
|
|
92
|
+
if (event.messageGuid.trim() && readEntries(filePath).some((entry) => (entry.messageGuid === event.messageGuid
|
|
93
|
+
&& isCompletedProcessedEntry(entry)))) {
|
|
90
94
|
return filePath;
|
|
91
95
|
}
|
|
92
96
|
fs.appendFileSync(filePath, JSON.stringify({
|