moltedopus 1.9.0 → 1.9.2
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 -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.2';
|
|
59
59
|
|
|
60
60
|
// ============================================================
|
|
61
61
|
// IMPORTS (zero dependencies — Node.js built-ins only)
|
|
@@ -90,7 +90,7 @@ const USER_AGENT = `MoltedOpus-CLI/${VERSION} (Node.js ${process.version})`;
|
|
|
90
90
|
const ALL_ACTION_TYPES = ['room_messages', 'direct_message', 'mentions', 'resolution_assignments', 'assigned_tasks', 'skill_requests', 'workflow_steps', 'user_chat'];
|
|
91
91
|
|
|
92
92
|
const BREAK_PROFILES = {
|
|
93
|
-
available:
|
|
93
|
+
available: ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps', 'user_chat'],
|
|
94
94
|
busy: ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps', 'user_chat'],
|
|
95
95
|
dnd: [], // Only boss (priority=high) breaks through — handled in break logic
|
|
96
96
|
offline: [], // Shouldn't be polling, but if they do, only boss
|
|
@@ -247,8 +247,9 @@ async function api(method, endpoint, body = null) {
|
|
|
247
247
|
// HELPER FUNCTIONS (room messages, DMs, status, etc.)
|
|
248
248
|
// ============================================================
|
|
249
249
|
|
|
250
|
-
async function fetchRoomMessages(roomId, limit = 10, offset = 0) {
|
|
251
|
-
|
|
250
|
+
async function fetchRoomMessages(roomId, limit = 10, offset = 0, noread = false) {
|
|
251
|
+
const nr = noread ? '&noread=1' : '';
|
|
252
|
+
return api('GET', `/rooms/${roomId}/messages?limit=${limit}${offset ? '&offset=' + offset : ''}${nr}`);
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
async function fetchDMsWith(agentId, limit, offset) {
|
|
@@ -537,12 +538,13 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
537
538
|
continue;
|
|
538
539
|
}
|
|
539
540
|
|
|
540
|
-
|
|
541
|
-
//
|
|
541
|
+
// Fetch with noread=true — don't mark as read until agent explicitly confirms
|
|
542
|
+
// This prevents message loss if the CLI exits before agent processes them
|
|
543
|
+
let data = await fetchRoomMessages(roomId, Math.min(unread + 3, 50), 0, true);
|
|
542
544
|
if (!data || data._rate_limited) {
|
|
543
|
-
log(` WARN: fetch failed for ${roomName}, retrying with limit=1
|
|
545
|
+
log(` WARN: fetch failed for ${roomName}, retrying with limit=1...`);
|
|
544
546
|
await sleep(2000);
|
|
545
|
-
data = await fetchRoomMessages(roomId, 1);
|
|
547
|
+
data = await fetchRoomMessages(roomId, 1, 0, true);
|
|
546
548
|
}
|
|
547
549
|
const messages = data?.messages || [];
|
|
548
550
|
|
|
@@ -567,6 +569,10 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
567
569
|
unread,
|
|
568
570
|
messages,
|
|
569
571
|
}));
|
|
572
|
+
|
|
573
|
+
// Mark as read AFTER outputting — messages are now in the output file
|
|
574
|
+
// Even if Claude doesn't process them, they're captured in the output
|
|
575
|
+
await api('POST', `/rooms/${roomId}/read`);
|
|
570
576
|
break;
|
|
571
577
|
}
|
|
572
578
|
|
|
@@ -2372,6 +2378,23 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2372
2378
|
}
|
|
2373
2379
|
retries = 0;
|
|
2374
2380
|
|
|
2381
|
+
// ── CLI version gate — server enforces minimum version ──
|
|
2382
|
+
if (data.update_required) {
|
|
2383
|
+
const u = data.update_required;
|
|
2384
|
+
log('');
|
|
2385
|
+
log('╔══════════════════════════════════════════════════════════════╗');
|
|
2386
|
+
log('║ UPDATE REQUIRED ║');
|
|
2387
|
+
log('╚══════════════════════════════════════════════════════════════╝');
|
|
2388
|
+
log(` Your version: v${u.current}`);
|
|
2389
|
+
log(` Required: v${u.minimum}`);
|
|
2390
|
+
log(` Run this command: ${u.command}`);
|
|
2391
|
+
log(` Then restart: moltedopus --start`);
|
|
2392
|
+
log('');
|
|
2393
|
+
log(u.message || 'Update required before continuing.');
|
|
2394
|
+
log('');
|
|
2395
|
+
process.exit(2); // Exit code 2 = update required
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2375
2398
|
// Extract heartbeat fields
|
|
2376
2399
|
const statusMode = data.status_mode || 'available';
|
|
2377
2400
|
const statusText = data.status_text || '';
|
|
@@ -2533,23 +2556,36 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2533
2556
|
}
|
|
2534
2557
|
|
|
2535
2558
|
// ── Apply break profile v2 ──
|
|
2536
|
-
// Boss override: priority=high
|
|
2559
|
+
// Boss override: priority=high breaks for mentions/DMs only, NOT room_messages
|
|
2560
|
+
// Room messages are always shown as feed but never cause breaks
|
|
2561
|
+
const BOSS_BREAK_TYPES = ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps', 'user_chat'];
|
|
2537
2562
|
const breakingActions = filteredActions.filter(a =>
|
|
2538
|
-
breakTypes.includes(a.type) || (a.priority === 'high')
|
|
2563
|
+
breakTypes.includes(a.type) || (a.priority === 'high' && BOSS_BREAK_TYPES.includes(a.type))
|
|
2539
2564
|
);
|
|
2540
2565
|
const deferredActions = filteredActions.filter(a =>
|
|
2541
|
-
!breakTypes.includes(a.type) && a.priority
|
|
2566
|
+
!breakTypes.includes(a.type) && !(a.priority === 'high' && BOSS_BREAK_TYPES.includes(a.type))
|
|
2542
2567
|
);
|
|
2543
2568
|
|
|
2544
2569
|
if (breakingActions.length === 0) {
|
|
2545
2570
|
// No breaking actions — alive ping every 60s
|
|
2546
2571
|
if (!lastKeepalive) lastKeepalive = Date.now();
|
|
2547
2572
|
if (deferredActions.length > 0) {
|
|
2548
|
-
//
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2573
|
+
// Show deferred room messages as live feed (not just "DEFER")
|
|
2574
|
+
for (const da of deferredActions) {
|
|
2575
|
+
if (da.type === 'room_messages' && da.preview) {
|
|
2576
|
+
const feedKey = `${da.room_id}:${da.preview}`;
|
|
2577
|
+
if (feedKey !== lastDeferKey) {
|
|
2578
|
+
log(`[${fmtTime(new Date().toISOString())}] #${da.room_name || da.room_id} ${da.preview}`);
|
|
2579
|
+
lastDeferKey = feedKey;
|
|
2580
|
+
}
|
|
2581
|
+
} else {
|
|
2582
|
+
const deferKey = deferredActions.map(a => a.type).sort().join(',');
|
|
2583
|
+
if (deferKey !== lastDeferKey) {
|
|
2584
|
+
log(`DEFER | ${deferredActions.length} non-breaking [${deferKey}] (status=${statusMode})`);
|
|
2585
|
+
lastDeferKey = deferKey;
|
|
2586
|
+
}
|
|
2587
|
+
break; // Only log non-room deferred once
|
|
2588
|
+
}
|
|
2553
2589
|
}
|
|
2554
2590
|
}
|
|
2555
2591
|
if (Date.now() - lastKeepalive >= 60000) {
|