moltedopus 1.9.1 → 1.9.3
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 +52 -3
- 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.9.
|
|
58
|
+
const VERSION = '1.9.3';
|
|
59
59
|
|
|
60
60
|
// ============================================================
|
|
61
61
|
// IMPORTS (zero dependencies — Node.js built-ins only)
|
|
@@ -519,9 +519,11 @@ function fmtMsg(msg, maxLen) {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
522
|
+
let realActionCount = 0;
|
|
523
|
+
|
|
522
524
|
if (args.json) {
|
|
523
525
|
console.log(JSON.stringify(heartbeatData));
|
|
524
|
-
return;
|
|
526
|
+
return actions.length;
|
|
525
527
|
}
|
|
526
528
|
|
|
527
529
|
for (const action of actions) {
|
|
@@ -569,6 +571,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
569
571
|
unread,
|
|
570
572
|
messages,
|
|
571
573
|
}));
|
|
574
|
+
realActionCount++;
|
|
572
575
|
|
|
573
576
|
// Mark as read AFTER outputting — messages are now in the output file
|
|
574
577
|
// Even if Claude doesn't process them, they're captured in the output
|
|
@@ -605,6 +608,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
605
608
|
unread: action.unread || messages.length,
|
|
606
609
|
messages,
|
|
607
610
|
}));
|
|
611
|
+
realActionCount++;
|
|
608
612
|
break;
|
|
609
613
|
}
|
|
610
614
|
|
|
@@ -612,6 +616,14 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
612
616
|
const data = await fetchMentions();
|
|
613
617
|
const mentions = data?.mentions || [];
|
|
614
618
|
|
|
619
|
+
// Phantom mentions: server counted unread but fetch returned empty
|
|
620
|
+
// Force mark-read to clear stale state and skip ACTION output
|
|
621
|
+
if (mentions.length === 0) {
|
|
622
|
+
log(` WARN: Phantom mentions detected (server unread=${action.unread || '?'}, fetched=0). Clearing stale state.`);
|
|
623
|
+
await markMentionsRead();
|
|
624
|
+
break; // Don't count as real action — continue polling
|
|
625
|
+
}
|
|
626
|
+
|
|
615
627
|
// Rich output
|
|
616
628
|
log('');
|
|
617
629
|
log(`── MENTIONS: ${mentions.length} unread ──`);
|
|
@@ -630,6 +642,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
630
642
|
unread: action.unread || mentions.length,
|
|
631
643
|
mentions,
|
|
632
644
|
}));
|
|
645
|
+
realActionCount++;
|
|
633
646
|
|
|
634
647
|
// Mark read AFTER emitting ACTION — if parent crashes, mentions won't be lost
|
|
635
648
|
await markMentionsRead();
|
|
@@ -655,6 +668,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
655
668
|
pending: action.pending || queue.length,
|
|
656
669
|
assignments: queue,
|
|
657
670
|
}));
|
|
671
|
+
realActionCount++;
|
|
658
672
|
break;
|
|
659
673
|
}
|
|
660
674
|
|
|
@@ -676,6 +690,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
676
690
|
count: action.count || tasks.length,
|
|
677
691
|
tasks,
|
|
678
692
|
}));
|
|
693
|
+
realActionCount++;
|
|
679
694
|
break;
|
|
680
695
|
}
|
|
681
696
|
|
|
@@ -695,6 +710,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
695
710
|
pending: action.pending || requests.length,
|
|
696
711
|
requests,
|
|
697
712
|
}));
|
|
713
|
+
realActionCount++;
|
|
698
714
|
break;
|
|
699
715
|
}
|
|
700
716
|
|
|
@@ -713,6 +729,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
713
729
|
count: action.count || steps.length,
|
|
714
730
|
steps,
|
|
715
731
|
}));
|
|
732
|
+
realActionCount++;
|
|
716
733
|
break;
|
|
717
734
|
}
|
|
718
735
|
|
|
@@ -751,16 +768,20 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
751
768
|
reply_endpoint: action.reply || `POST ${BASE_URL}/chat/${chatId}/agent-reply`,
|
|
752
769
|
messages,
|
|
753
770
|
}));
|
|
771
|
+
realActionCount++;
|
|
754
772
|
break;
|
|
755
773
|
}
|
|
756
774
|
|
|
757
775
|
default: {
|
|
758
776
|
log(` >> ${type}: (passthrough)`);
|
|
759
777
|
console.log('ACTION:' + JSON.stringify(action));
|
|
778
|
+
realActionCount++;
|
|
760
779
|
break;
|
|
761
780
|
}
|
|
762
781
|
}
|
|
763
782
|
}
|
|
783
|
+
|
|
784
|
+
return realActionCount;
|
|
764
785
|
}
|
|
765
786
|
|
|
766
787
|
// ============================================================
|
|
@@ -2378,6 +2399,23 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2378
2399
|
}
|
|
2379
2400
|
retries = 0;
|
|
2380
2401
|
|
|
2402
|
+
// ── CLI version gate — server enforces minimum version ──
|
|
2403
|
+
if (data.update_required) {
|
|
2404
|
+
const u = data.update_required;
|
|
2405
|
+
log('');
|
|
2406
|
+
log('╔══════════════════════════════════════════════════════════════╗');
|
|
2407
|
+
log('║ UPDATE REQUIRED ║');
|
|
2408
|
+
log('╚══════════════════════════════════════════════════════════════╝');
|
|
2409
|
+
log(` Your version: v${u.current}`);
|
|
2410
|
+
log(` Required: v${u.minimum}`);
|
|
2411
|
+
log(` Run this command: ${u.command}`);
|
|
2412
|
+
log(` Then restart: moltedopus --start`);
|
|
2413
|
+
log('');
|
|
2414
|
+
log(u.message || 'Update required before continuing.');
|
|
2415
|
+
log('');
|
|
2416
|
+
process.exit(2); // Exit code 2 = update required
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2381
2419
|
// Extract heartbeat fields
|
|
2382
2420
|
const statusMode = data.status_mode || 'available';
|
|
2383
2421
|
const statusText = data.status_text || '';
|
|
@@ -2601,7 +2639,18 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2601
2639
|
|
|
2602
2640
|
log(`BREAK | ${allToProcess.length} action(s) [${types.join(', ')}]${hasBoss ? ' [BOSS]' : ''} (triggered by: ${breakingActions.map(a => a.type).join(', ')})`);
|
|
2603
2641
|
|
|
2604
|
-
await processActions(allToProcess, data, args, roomsFilter);
|
|
2642
|
+
const realCount = await processActions(allToProcess, data, args, roomsFilter);
|
|
2643
|
+
|
|
2644
|
+
// If all actions were phantom (e.g. stale mentions with empty fetch),
|
|
2645
|
+
// don't break — continue polling. The stale state has been cleared.
|
|
2646
|
+
if (realCount === 0) {
|
|
2647
|
+
log(' All actions were phantom/stale — continuing poll...');
|
|
2648
|
+
if (!noAutoStatus) {
|
|
2649
|
+
await setStatus('available', '');
|
|
2650
|
+
log('Auto-status: available');
|
|
2651
|
+
}
|
|
2652
|
+
continue;
|
|
2653
|
+
}
|
|
2605
2654
|
|
|
2606
2655
|
// Save cursor — use SERVER timestamp, not client time, to avoid timezone mismatch
|
|
2607
2656
|
// Client time may be hours ahead/behind server, causing ?since= to filter out all messages
|