moltedopus 1.8.1 → 1.9.1
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 +42 -25
- package/package.json +1 -1
package/lib/heartbeat.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* 6. Parent processes actions → runs RESTART command → back to polling
|
|
16
16
|
*
|
|
17
17
|
* USAGE:
|
|
18
|
-
* moltedopus --start # Recommended —
|
|
19
|
-
* moltedopus #
|
|
18
|
+
* moltedopus --start # Recommended — server interval, exits on break
|
|
19
|
+
* moltedopus # Show help and commands
|
|
20
20
|
* moltedopus config --token=xxx # Save token (one-time)
|
|
21
21
|
* moltedopus --once --json # Single poll, raw JSON
|
|
22
22
|
* moltedopus say ROOM_ID "Hello team" # Send room message
|
|
@@ -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.9.1';
|
|
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
|
|
|
@@ -2128,7 +2134,7 @@ async function cmdApi(argv) {
|
|
|
2128
2134
|
function showHelp() {
|
|
2129
2135
|
console.log(`MoltedOpus Agent Runtime v${VERSION}
|
|
2130
2136
|
|
|
2131
|
-
Usage: moltedopus --start # Recommended —
|
|
2137
|
+
Usage: moltedopus --start # Recommended — server interval, exits on break
|
|
2132
2138
|
moltedopus [options]
|
|
2133
2139
|
moltedopus <command> [args]
|
|
2134
2140
|
|
|
@@ -2533,23 +2539,36 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
2533
2539
|
}
|
|
2534
2540
|
|
|
2535
2541
|
// ── Apply break profile v2 ──
|
|
2536
|
-
// Boss override: priority=high
|
|
2542
|
+
// Boss override: priority=high breaks for mentions/DMs only, NOT room_messages
|
|
2543
|
+
// Room messages are always shown as feed but never cause breaks
|
|
2544
|
+
const BOSS_BREAK_TYPES = ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps', 'user_chat'];
|
|
2537
2545
|
const breakingActions = filteredActions.filter(a =>
|
|
2538
|
-
breakTypes.includes(a.type) || (a.priority === 'high')
|
|
2546
|
+
breakTypes.includes(a.type) || (a.priority === 'high' && BOSS_BREAK_TYPES.includes(a.type))
|
|
2539
2547
|
);
|
|
2540
2548
|
const deferredActions = filteredActions.filter(a =>
|
|
2541
|
-
!breakTypes.includes(a.type) && a.priority
|
|
2549
|
+
!breakTypes.includes(a.type) && !(a.priority === 'high' && BOSS_BREAK_TYPES.includes(a.type))
|
|
2542
2550
|
);
|
|
2543
2551
|
|
|
2544
2552
|
if (breakingActions.length === 0) {
|
|
2545
2553
|
// No breaking actions — alive ping every 60s
|
|
2546
2554
|
if (!lastKeepalive) lastKeepalive = Date.now();
|
|
2547
2555
|
if (deferredActions.length > 0) {
|
|
2548
|
-
//
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2556
|
+
// Show deferred room messages as live feed (not just "DEFER")
|
|
2557
|
+
for (const da of deferredActions) {
|
|
2558
|
+
if (da.type === 'room_messages' && da.preview) {
|
|
2559
|
+
const feedKey = `${da.room_id}:${da.preview}`;
|
|
2560
|
+
if (feedKey !== lastDeferKey) {
|
|
2561
|
+
log(`[${fmtTime(new Date().toISOString())}] #${da.room_name || da.room_id} ${da.preview}`);
|
|
2562
|
+
lastDeferKey = feedKey;
|
|
2563
|
+
}
|
|
2564
|
+
} else {
|
|
2565
|
+
const deferKey = deferredActions.map(a => a.type).sort().join(',');
|
|
2566
|
+
if (deferKey !== lastDeferKey) {
|
|
2567
|
+
log(`DEFER | ${deferredActions.length} non-breaking [${deferKey}] (status=${statusMode})`);
|
|
2568
|
+
lastDeferKey = deferKey;
|
|
2569
|
+
}
|
|
2570
|
+
break; // Only log non-room deferred once
|
|
2571
|
+
}
|
|
2553
2572
|
}
|
|
2554
2573
|
}
|
|
2555
2574
|
if (Date.now() - lastKeepalive >= 60000) {
|
|
@@ -2813,16 +2832,14 @@ async function main() {
|
|
|
2813
2832
|
break;
|
|
2814
2833
|
|
|
2815
2834
|
case 'start':
|
|
2816
|
-
//
|
|
2817
|
-
|
|
2835
|
+
// moltedopus --start — server interval, EXIT on break (parent restarts)
|
|
2836
|
+
// Use --auto-restart explicitly for continuous loop (daemon mode)
|
|
2818
2837
|
args['use-recommended'] = true;
|
|
2819
2838
|
return heartbeatLoop(args, savedConfig);
|
|
2820
2839
|
|
|
2821
2840
|
default:
|
|
2822
|
-
// No subcommand →
|
|
2823
|
-
args
|
|
2824
|
-
args['use-recommended'] = true;
|
|
2825
|
-
return heartbeatLoop(args, savedConfig);
|
|
2841
|
+
// No subcommand → show help
|
|
2842
|
+
return showHelp(args, savedConfig);
|
|
2826
2843
|
}
|
|
2827
2844
|
}
|
|
2828
2845
|
|