moltedopus 1.9.2 → 1.9.4
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 +47 -16
- 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.4';
|
|
59
59
|
|
|
60
60
|
// ============================================================
|
|
61
61
|
// IMPORTS (zero dependencies — Node.js built-ins only)
|
|
@@ -510,18 +510,19 @@ function fmtTime(dateStr) {
|
|
|
510
510
|
return `${hh}:${mm} · ${ago}`;
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
function fmtMsg(msg
|
|
513
|
+
function fmtMsg(msg) {
|
|
514
514
|
const content = (msg.content || '').replace(/\n/g, ' ');
|
|
515
515
|
const name = msg.sender_name || msg.from?.name || '?';
|
|
516
516
|
const time = fmtTime(msg.created_at);
|
|
517
|
-
|
|
518
|
-
return ` [${time}] ${name}: ${truncated}`;
|
|
517
|
+
return ` [${time}] ${name}: ${content}`;
|
|
519
518
|
}
|
|
520
519
|
|
|
521
520
|
async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
521
|
+
let realActionCount = 0;
|
|
522
|
+
|
|
522
523
|
if (args.json) {
|
|
523
524
|
console.log(JSON.stringify(heartbeatData));
|
|
524
|
-
return;
|
|
525
|
+
return actions.length;
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
for (const action of actions) {
|
|
@@ -569,6 +570,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
569
570
|
unread,
|
|
570
571
|
messages,
|
|
571
572
|
}));
|
|
573
|
+
realActionCount++;
|
|
572
574
|
|
|
573
575
|
// Mark as read AFTER outputting — messages are now in the output file
|
|
574
576
|
// Even if Claude doesn't process them, they're captured in the output
|
|
@@ -605,6 +607,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
605
607
|
unread: action.unread || messages.length,
|
|
606
608
|
messages,
|
|
607
609
|
}));
|
|
610
|
+
realActionCount++;
|
|
608
611
|
break;
|
|
609
612
|
}
|
|
610
613
|
|
|
@@ -612,15 +615,23 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
612
615
|
const data = await fetchMentions();
|
|
613
616
|
const mentions = data?.mentions || [];
|
|
614
617
|
|
|
618
|
+
// Phantom mentions: server counted unread but fetch returned empty
|
|
619
|
+
// Force mark-read to clear stale state and skip ACTION output
|
|
620
|
+
if (mentions.length === 0) {
|
|
621
|
+
log(` WARN: Phantom mentions detected (server unread=${action.unread || '?'}, fetched=0). Clearing stale state.`);
|
|
622
|
+
await markMentionsRead();
|
|
623
|
+
break; // Don't count as real action — continue polling
|
|
624
|
+
}
|
|
625
|
+
|
|
615
626
|
// Rich output
|
|
616
627
|
log('');
|
|
617
628
|
log(`── MENTIONS: ${mentions.length} unread ──`);
|
|
618
629
|
for (const m of mentions.slice(0, 10)) {
|
|
619
630
|
const time = fmtTime(m.created_at);
|
|
620
631
|
const from = m.from?.name || '?';
|
|
621
|
-
const preview = (m.room_message_preview || m.comment_preview || '').replace(/\n/g, ' ')
|
|
632
|
+
const preview = (m.room_message_preview || m.comment_preview || '').replace(/\n/g, ' ');
|
|
622
633
|
const where = m.room_name ? `#${m.room_name}` : (m.post_title ? `post: ${m.post_title}` : '');
|
|
623
|
-
log(` [${time}] ${from} in ${where}: ${preview}
|
|
634
|
+
log(` [${time}] ${from} in ${where}: ${preview}`);
|
|
624
635
|
}
|
|
625
636
|
if (mentions.length > 10) log(` ... and ${mentions.length - 10} more`);
|
|
626
637
|
log('');
|
|
@@ -630,6 +641,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
630
641
|
unread: action.unread || mentions.length,
|
|
631
642
|
mentions,
|
|
632
643
|
}));
|
|
644
|
+
realActionCount++;
|
|
633
645
|
|
|
634
646
|
// Mark read AFTER emitting ACTION — if parent crashes, mentions won't be lost
|
|
635
647
|
await markMentionsRead();
|
|
@@ -643,7 +655,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
643
655
|
log('');
|
|
644
656
|
log(`── RESOLUTIONS: ${queue.length} pending ──`);
|
|
645
657
|
for (const r of queue.slice(0, 5)) {
|
|
646
|
-
log(` [${r.id?.slice(0, 8)}...] "${
|
|
658
|
+
log(` [${r.id?.slice(0, 8)}...] "${r.title || r.content || ''}" by ${r.author_name || '?'}`);
|
|
647
659
|
}
|
|
648
660
|
log('');
|
|
649
661
|
log(` Commands:`);
|
|
@@ -655,6 +667,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
655
667
|
pending: action.pending || queue.length,
|
|
656
668
|
assignments: queue,
|
|
657
669
|
}));
|
|
670
|
+
realActionCount++;
|
|
658
671
|
break;
|
|
659
672
|
}
|
|
660
673
|
|
|
@@ -676,6 +689,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
676
689
|
count: action.count || tasks.length,
|
|
677
690
|
tasks,
|
|
678
691
|
}));
|
|
692
|
+
realActionCount++;
|
|
679
693
|
break;
|
|
680
694
|
}
|
|
681
695
|
|
|
@@ -686,7 +700,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
686
700
|
log('');
|
|
687
701
|
log(`── SKILL REQUESTS: ${requests.length} pending ──`);
|
|
688
702
|
for (const r of requests.slice(0, 5)) {
|
|
689
|
-
log(` ${r.skill_name || '?'} from ${r.requester_name || '?'}: ${
|
|
703
|
+
log(` ${r.skill_name || '?'} from ${r.requester_name || '?'}: ${r.description || ''}`);
|
|
690
704
|
}
|
|
691
705
|
log('');
|
|
692
706
|
|
|
@@ -695,6 +709,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
695
709
|
pending: action.pending || requests.length,
|
|
696
710
|
requests,
|
|
697
711
|
}));
|
|
712
|
+
realActionCount++;
|
|
698
713
|
break;
|
|
699
714
|
}
|
|
700
715
|
|
|
@@ -713,6 +728,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
713
728
|
count: action.count || steps.length,
|
|
714
729
|
steps,
|
|
715
730
|
}));
|
|
731
|
+
realActionCount++;
|
|
716
732
|
break;
|
|
717
733
|
}
|
|
718
734
|
|
|
@@ -734,7 +750,7 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
734
750
|
log(fmtMsg(m));
|
|
735
751
|
}
|
|
736
752
|
} else {
|
|
737
|
-
log(` ${preview
|
|
753
|
+
log(` ${preview}`);
|
|
738
754
|
}
|
|
739
755
|
log('');
|
|
740
756
|
log(` Commands:`);
|
|
@@ -751,16 +767,20 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
751
767
|
reply_endpoint: action.reply || `POST ${BASE_URL}/chat/${chatId}/agent-reply`,
|
|
752
768
|
messages,
|
|
753
769
|
}));
|
|
770
|
+
realActionCount++;
|
|
754
771
|
break;
|
|
755
772
|
}
|
|
756
773
|
|
|
757
774
|
default: {
|
|
758
775
|
log(` >> ${type}: (passthrough)`);
|
|
759
776
|
console.log('ACTION:' + JSON.stringify(action));
|
|
777
|
+
realActionCount++;
|
|
760
778
|
break;
|
|
761
779
|
}
|
|
762
780
|
}
|
|
763
781
|
}
|
|
782
|
+
|
|
783
|
+
return realActionCount;
|
|
764
784
|
}
|
|
765
785
|
|
|
766
786
|
// ============================================================
|
|
@@ -1052,7 +1072,7 @@ async function cmdShow() {
|
|
|
1052
1072
|
for (const r of rooms) {
|
|
1053
1073
|
const role = r.role || 'member';
|
|
1054
1074
|
console.log(` ${r.name} (${role}) — ${r.id}`);
|
|
1055
|
-
if (r.description) console.log(` ${r.description
|
|
1075
|
+
if (r.description) console.log(` ${r.description}`);
|
|
1056
1076
|
}
|
|
1057
1077
|
console.log('');
|
|
1058
1078
|
}
|
|
@@ -2436,7 +2456,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2436
2456
|
for (const r of b.rooms) {
|
|
2437
2457
|
log(` ${r.name} (${r.role}) — ${r.id}`);
|
|
2438
2458
|
if (r.description) log(` ${r.description}`);
|
|
2439
|
-
if (r.skill) log(` skill: ${r.skill.replace(/\n/g, ' ')
|
|
2459
|
+
if (r.skill) log(` skill: ${r.skill.replace(/\n/g, ' ')}`);
|
|
2440
2460
|
}
|
|
2441
2461
|
}
|
|
2442
2462
|
if (b.orders && b.orders.length > 0) {
|
|
@@ -2450,7 +2470,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2450
2470
|
log('');
|
|
2451
2471
|
log('Config:');
|
|
2452
2472
|
for (const [k, v] of Object.entries(b.config)) {
|
|
2453
|
-
log(` ${k}: ${String(v)
|
|
2473
|
+
log(` ${k}: ${String(v)}`);
|
|
2454
2474
|
}
|
|
2455
2475
|
}
|
|
2456
2476
|
if (b.commands) {
|
|
@@ -2537,7 +2557,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2537
2557
|
// Alive ping every 60s so parent process knows we're polling
|
|
2538
2558
|
if (!lastKeepalive) lastKeepalive = Date.now();
|
|
2539
2559
|
if (Date.now() - lastKeepalive >= 60000) { // 60s
|
|
2540
|
-
log(`---
|
|
2560
|
+
log(`--- ${statusMode} | #${cycle} | ${new Date().toLocaleTimeString()} ---`);
|
|
2541
2561
|
lastKeepalive = Date.now();
|
|
2542
2562
|
}
|
|
2543
2563
|
} else if (showMode) {
|
|
@@ -2589,7 +2609,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2589
2609
|
}
|
|
2590
2610
|
}
|
|
2591
2611
|
if (Date.now() - lastKeepalive >= 60000) {
|
|
2592
|
-
log(`---
|
|
2612
|
+
log(`--- ${statusMode} | #${cycle} | ${new Date().toLocaleTimeString()} ---`);
|
|
2593
2613
|
lastKeepalive = Date.now();
|
|
2594
2614
|
}
|
|
2595
2615
|
} else {
|
|
@@ -2618,7 +2638,18 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2618
2638
|
|
|
2619
2639
|
log(`BREAK | ${allToProcess.length} action(s) [${types.join(', ')}]${hasBoss ? ' [BOSS]' : ''} (triggered by: ${breakingActions.map(a => a.type).join(', ')})`);
|
|
2620
2640
|
|
|
2621
|
-
await processActions(allToProcess, data, args, roomsFilter);
|
|
2641
|
+
const realCount = await processActions(allToProcess, data, args, roomsFilter);
|
|
2642
|
+
|
|
2643
|
+
// If all actions were phantom (e.g. stale mentions with empty fetch),
|
|
2644
|
+
// don't break — continue polling. The stale state has been cleared.
|
|
2645
|
+
if (realCount === 0) {
|
|
2646
|
+
log(' All actions were phantom/stale — continuing poll...');
|
|
2647
|
+
if (!noAutoStatus) {
|
|
2648
|
+
await setStatus('available', '');
|
|
2649
|
+
log('Auto-status: available');
|
|
2650
|
+
}
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2622
2653
|
|
|
2623
2654
|
// Save cursor — use SERVER timestamp, not client time, to avoid timezone mismatch
|
|
2624
2655
|
// Client time may be hours ahead/behind server, causing ?since= to filter out all messages
|