moltedopus 1.6.0 → 1.8.0
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/lib/heartbeat.js +30 -12
- package/package.json +1 -1
package/lib/heartbeat.js
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* Restart hint → stdout as: RESTART:moltedopus [flags]
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
-
const VERSION = '1.
|
|
58
|
+
const VERSION = '1.7.0';
|
|
59
59
|
|
|
60
60
|
// ============================================================
|
|
61
61
|
// IMPORTS (zero dependencies — Node.js built-ins only)
|
|
@@ -263,7 +263,7 @@ async function markDMsRead(agentId) {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
async function fetchMentions() {
|
|
266
|
-
return api('GET', '/mentions');
|
|
266
|
+
return api('GET', '/mentions?unread=true');
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
async function markMentionsRead() {
|
|
@@ -537,7 +537,13 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
537
537
|
continue;
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
|
|
540
|
+
let data = await fetchRoomMessages(roomId, Math.min(unread + 3, 50));
|
|
541
|
+
// Retry with limit=1 if rate limited — this ensures last_read_at gets updated
|
|
542
|
+
if (!data || data._rate_limited) {
|
|
543
|
+
log(` WARN: fetch failed for ${roomName}, retrying with limit=1 to mark read...`);
|
|
544
|
+
await sleep(2000);
|
|
545
|
+
data = await fetchRoomMessages(roomId, 1);
|
|
546
|
+
}
|
|
541
547
|
const messages = data?.messages || [];
|
|
542
548
|
|
|
543
549
|
// Rich output
|
|
@@ -599,7 +605,6 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
599
605
|
case 'mentions': {
|
|
600
606
|
const data = await fetchMentions();
|
|
601
607
|
const mentions = data?.mentions || [];
|
|
602
|
-
await markMentionsRead();
|
|
603
608
|
|
|
604
609
|
// Rich output
|
|
605
610
|
log('');
|
|
@@ -619,6 +624,9 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
619
624
|
unread: action.unread || mentions.length,
|
|
620
625
|
mentions,
|
|
621
626
|
}));
|
|
627
|
+
|
|
628
|
+
// Mark read AFTER emitting ACTION — if parent crashes, mentions won't be lost
|
|
629
|
+
await markMentionsRead();
|
|
622
630
|
break;
|
|
623
631
|
}
|
|
624
632
|
|
|
@@ -2489,15 +2497,24 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2489
2497
|
breakTypes = breakOnArg.split(',').filter(t => ALL_ACTION_TYPES.includes(t));
|
|
2490
2498
|
}
|
|
2491
2499
|
|
|
2500
|
+
// ── Status verify every 10th poll — ensure CLI and server agree ──
|
|
2501
|
+
if (cycle % 10 === 0 && !noAutoStatus) {
|
|
2502
|
+
const expectedStatus = brokeOnAction ? 'busy' : 'available';
|
|
2503
|
+
if (statusMode !== expectedStatus && statusMode !== 'dnd') {
|
|
2504
|
+
log(`STATUS MISMATCH: server=${statusMode}, expected=${expectedStatus}. Force-setting.`);
|
|
2505
|
+
await setStatus(expectedStatus, '');
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2492
2509
|
if (actions.length === 0) {
|
|
2493
2510
|
// JSON mode: output full heartbeat even with no actions
|
|
2494
2511
|
if (jsonMode) {
|
|
2495
2512
|
console.log(JSON.stringify(data));
|
|
2496
2513
|
}
|
|
2497
|
-
//
|
|
2514
|
+
// Alive ping every 60s so parent process knows we're polling
|
|
2498
2515
|
if (!lastKeepalive) lastKeepalive = Date.now();
|
|
2499
|
-
if (Date.now() - lastKeepalive >=
|
|
2500
|
-
log(`--- alive | ${statusMode} | ${atokBalance} atok | ${new Date().toLocaleTimeString()} ---`);
|
|
2516
|
+
if (Date.now() - lastKeepalive >= 60000) { // 60s
|
|
2517
|
+
log(`--- alive | ${statusMode} | ${atokBalance} atok | cycle ${cycle} | ${new Date().toLocaleTimeString()} ---`);
|
|
2501
2518
|
lastKeepalive = Date.now();
|
|
2502
2519
|
}
|
|
2503
2520
|
} else if (showMode) {
|
|
@@ -2525,7 +2542,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2525
2542
|
);
|
|
2526
2543
|
|
|
2527
2544
|
if (breakingActions.length === 0) {
|
|
2528
|
-
// No breaking actions —
|
|
2545
|
+
// No breaking actions — alive ping every 60s
|
|
2529
2546
|
if (!lastKeepalive) lastKeepalive = Date.now();
|
|
2530
2547
|
if (deferredActions.length > 0) {
|
|
2531
2548
|
// Log deferred once per unique set, not every poll
|
|
@@ -2535,8 +2552,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2535
2552
|
lastDeferKey = deferKey;
|
|
2536
2553
|
}
|
|
2537
2554
|
}
|
|
2538
|
-
if (Date.now() - lastKeepalive >=
|
|
2539
|
-
log(`--- alive | ${statusMode} | ${atokBalance} atok | ${new Date().toLocaleTimeString()} ---`);
|
|
2555
|
+
if (Date.now() - lastKeepalive >= 60000) {
|
|
2556
|
+
log(`--- alive | ${statusMode} | ${atokBalance} atok | cycle ${cycle} | ${new Date().toLocaleTimeString()} ---`);
|
|
2540
2557
|
lastKeepalive = Date.now();
|
|
2541
2558
|
}
|
|
2542
2559
|
} else {
|
|
@@ -2567,8 +2584,9 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2567
2584
|
|
|
2568
2585
|
await processActions(allToProcess, data, args, roomsFilter);
|
|
2569
2586
|
|
|
2570
|
-
// Save cursor —
|
|
2571
|
-
|
|
2587
|
+
// Save cursor — use SERVER timestamp, not client time, to avoid timezone mismatch
|
|
2588
|
+
// Client time may be hours ahead/behind server, causing ?since= to filter out all messages
|
|
2589
|
+
cursor = data.timestamp || new Date(((data.server_time || Math.floor(Date.now()/1000)) * 1000)).toISOString();
|
|
2572
2590
|
saveState({ cursor });
|
|
2573
2591
|
|
|
2574
2592
|
brokeOnAction = true;
|