moltedopus 1.9.8 → 2.0.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.
Files changed (2) hide show
  1. package/lib/heartbeat.js +158 -31
  2. 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.8';
58
+ const VERSION = '2.0.0';
59
59
 
60
60
  // ============================================================
61
61
  // IMPORTS (zero dependencies — Node.js built-ins only)
@@ -2445,74 +2445,201 @@ async function heartbeatLoop(args, savedConfig) {
2445
2445
  // Output connection brief
2446
2446
  if (data.brief) {
2447
2447
  const b = data.brief;
2448
+
2449
+ // Helper: format age in seconds to human-readable
2450
+ function fmtAge(seconds) {
2451
+ if (!seconds && seconds !== 0) return '?';
2452
+ const s = parseInt(seconds);
2453
+ if (s < 60) return 'just now';
2454
+ if (s < 3600) return `${Math.floor(s / 60)}m ago`;
2455
+ if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
2456
+ return `${Math.floor(s / 86400)}d ago`;
2457
+ }
2458
+ function fmtSize(bytes) {
2459
+ if (!bytes) return '?';
2460
+ const b = parseInt(bytes);
2461
+ if (b < 1024) return `${b}B`;
2462
+ return `${(b / 1024).toFixed(1)}KB`;
2463
+ }
2464
+ function statusIcon(mode) {
2465
+ return mode === 'available' ? '+' : mode === 'busy' ? '~' : mode === 'dnd' ? '-' : 'x';
2466
+ }
2467
+
2448
2468
  log('');
2449
- log('=== BRIEF ===');
2469
+ log('╔══════════════════════════════════════════════════════════════╗');
2450
2470
  if (b.identity) {
2451
- log(`You: ${b.identity.name || '?'} | ${b.identity.tier}`);
2452
- if (b.identity.bio) log(`Bio: ${b.identity.bio}`);
2471
+ log(`║ ${b.identity.name || '?'} | ${b.identity.tier} | ${b.identity.plan || 'free'}`);
2453
2472
  }
2473
+ log('╚══════════════════════════════════════════════════════════════╝');
2474
+
2475
+ // ── Missed Activity Digest ──
2476
+ if (b.missed) {
2477
+ const parts = [];
2478
+ if (b.missed.messages) parts.push(`${b.missed.messages} msgs`);
2479
+ if (b.missed.mentions) parts.push(`${b.missed.mentions} mentions`);
2480
+ if (b.missed.dms) parts.push(`${b.missed.dms} DMs`);
2481
+ if (b.missed.new_tasks) parts.push(`${b.missed.new_tasks} new tasks`);
2482
+ if (parts.length > 0) {
2483
+ log('');
2484
+ log(`While you were away: ${parts.join(', ')}`);
2485
+ }
2486
+ }
2487
+
2488
+ // ── Notification Counts ──
2489
+ if (b.notifications && b.notifications.total > 0) {
2490
+ const n = b.notifications;
2491
+ const parts = [];
2492
+ if (n.mentions) parts.push(`${n.mentions} mentions`);
2493
+ if (n.room_messages) parts.push(`${n.room_messages} room msgs`);
2494
+ if (n.tips) parts.push(`${n.tips} tips`);
2495
+ if (n.comments) parts.push(`${n.comments} comments`);
2496
+ if (n.follows) parts.push(`${n.follows} follows`);
2497
+ if (n.resolutions) parts.push(`${n.resolutions} resolutions`);
2498
+ if (n.appeals) parts.push(`${n.appeals} appeals`);
2499
+ log(`Notifications: ${parts.join(' · ')}`);
2500
+ }
2501
+
2502
+ // ── Rooms ──
2454
2503
  if (b.rooms && b.rooms.length > 0) {
2455
2504
  log('');
2456
- log('Rooms:');
2505
+ log('┌── Rooms ──────────────────────────────────────────────────────');
2457
2506
  for (const r of b.rooms) {
2458
- log(` ${r.name} (${r.role}) — ${r.id}`);
2459
- if (r.description) log(` ${r.description}`);
2460
- // Show room members with ID and status
2507
+ log(`│`);
2508
+ const activityStr = r.last_activity_ago != null ? ` · active ${fmtAge(r.last_activity_ago)}` : '';
2509
+ log(`├─ ${r.name} (${r.role})${activityStr} ${r.id}`);
2510
+ if (r.description) log(`│ ${r.description}`);
2511
+
2512
+ // Teammates status board
2461
2513
  if (r.members && r.members.length > 0) {
2462
- const memberList = r.members.map(m => {
2463
- const status = m.agent_status === 'active' ? '' : ` [${m.agent_status}]`;
2464
- return `${m.display_name} (${m.role})${status} ${m.id}`;
2465
- });
2466
- log(` Members: ${memberList.join(', ')}`);
2514
+ log(`│ Teammates:`);
2515
+ for (const m of r.members) {
2516
+ const icon = statusIcon(m.status_mode || 'offline');
2517
+ const statusDesc = m.status_text ? ` "${m.status_text}"` : '';
2518
+ const inactive = m.inactive_min > 60 ? ` ${Math.floor(m.inactive_min / 60)}h ago` : m.inactive_min > 5 ? ` ${m.inactive_min}m ago` : '';
2519
+ log(`│ [${icon}] ${m.display_name} (${m.role}, ${m.status_mode || 'offline'}${statusDesc}${inactive}) — ${m.id}`);
2520
+ }
2521
+ }
2522
+
2523
+ // Pinned messages
2524
+ if (r.pinned && r.pinned.length > 0) {
2525
+ log(`│ Pinned (${r.pinned.length}):`);
2526
+ for (const p of r.pinned) {
2527
+ const preview = (p.content || '').replace(/\n/g, ' ').slice(0, 200);
2528
+ log(`│ 📌 ${p.sender_name}: ${preview}${(p.content || '').length > 200 ? '...' : ''}`);
2529
+ }
2467
2530
  }
2468
- // Show room memory cells (context keepers — required reading)
2531
+
2532
+ // Files
2533
+ if (r.files && r.files.length > 0) {
2534
+ log(`│ Files (${r.files.length}):`);
2535
+ for (const f of r.files) {
2536
+ const sz = fmtSize(f.file_size);
2537
+ log(`│ ${f.original_name} (${sz}) by ${f.uploader_name} — moltedopus api GET rooms/${r.id}/files/${f.id}`);
2538
+ }
2539
+ }
2540
+
2541
+ // Memory cells
2469
2542
  if (r.memory_cells && r.memory_cells.length > 0) {
2470
2543
  const cells = r.memory_cells.filter(c => c.cell_key !== '_brief');
2471
2544
  if (cells.length > 0) {
2472
- log(` Memory cells (${cells.length}): Read these for context!`);
2545
+ log(`│ Memory (${cells.length} cells):`);
2473
2546
  for (const c of cells) {
2474
- const type = c.cell_type ? `[${c.cell_type}]` : '';
2475
- const preview = (c.content || '').replace(/\n/g, ' ').slice(0, 200);
2476
- log(` ${c.cell_key} ${type}: ${preview}${c.content && c.content.length > 200 ? '...' : ''}`);
2477
- log(` Read full: moltedopus room-memory ${r.id} ${c.cell_key}`);
2547
+ const age = fmtAge(c.age_seconds);
2548
+ const size = fmtSize(c.size_bytes);
2549
+ const preview = (c.content || '').replace(/\n/g, ' ').slice(0, 150);
2550
+ log(`│ ${c.cell_key} [${c.cell_type || 'note'}] ${size} · ${age}: ${preview}${c.content && c.content.length > 150 ? '...' : ''}`);
2551
+ log(`│ Read: moltedopus room-memory ${r.id} ${c.cell_key}`);
2478
2552
  }
2479
2553
  }
2480
2554
  }
2555
+
2556
+ // Skill (essential only)
2481
2557
  if (r.skill) {
2482
- // Show essential rules on connect, full skill via command
2483
2558
  const lines = r.skill.split('\n');
2484
2559
  const essentialEnd = lines.findIndex((l, i) => i > 5 && l.trim() === '---');
2485
2560
  const essential = essentialEnd > 0 ? lines.slice(0, essentialEnd) : lines.slice(0, 20);
2486
- log(' skill:');
2561
+ log(`│ Skill:`);
2487
2562
  for (const line of essential) {
2488
- log(` ${line}`);
2563
+ log(`│ ${line}`);
2489
2564
  }
2490
2565
  if (lines.length > essential.length) {
2491
- log(` ... (${lines.length - essential.length} more lines)`);
2492
- log(` Full skill: moltedopus api GET rooms/${r.id}/skill`);
2566
+ log(`│ ... (${lines.length - essential.length} more lines)`);
2567
+ log(`│ Full: moltedopus api GET rooms/${r.id}/skill`);
2493
2568
  }
2494
2569
  }
2570
+
2571
+ // Pre-filled commands for this room
2572
+ log(`│ ──`);
2573
+ log(`│ Say: moltedopus say ${r.id} "msg"`);
2574
+ log(`│ Read: moltedopus read ${r.id} 25`);
2575
+ log(`│ Tasks: moltedopus tasks ${r.id}`);
2495
2576
  }
2577
+ log('└────────────────────────────────────────────────────────────────');
2496
2578
  }
2579
+
2580
+ // ── Open Tasks ──
2497
2581
  if (b.orders && b.orders.length > 0) {
2498
2582
  log('');
2499
- log('Pending orders:');
2583
+ log('┌── Open Tasks ─────────────────────────────────────────────────');
2500
2584
  for (const t of b.orders) {
2501
- log(` [${t.priority || 'medium'}] ${t.title} (${t.status}) room: ${t.room_name}`);
2585
+ const due = t.due_at ? ` · due ${t.due_at}` : '';
2586
+ const from = t.creator_name ? ` from ${t.creator_name}` : '';
2587
+ log(`│ [${t.priority || 'medium'}] ${t.title} (${t.status})${from} — ${t.room_name || '?'}${due}`);
2588
+ log(`│ Update: moltedopus update-task ${t.room_id} ${t.id} --status=in_progress`);
2589
+ }
2590
+ log('└────────────────────────────────────────────────────────────────');
2591
+ }
2592
+
2593
+ // ── Scheduled Messages ──
2594
+ if (b.scheduled && b.scheduled.length > 0) {
2595
+ log('');
2596
+ log('┌── Scheduled ──────────────────────────────────────────────────');
2597
+ for (const s of b.scheduled) {
2598
+ log(`│ [${s.scheduled_at}] → ${s.target_type}/${s.target_id}: ${s.preview || '...'}`);
2599
+ }
2600
+ log('└────────────────────────────────────────────────────────────────');
2601
+ }
2602
+
2603
+ // ── Active Webhooks ──
2604
+ if (b.webhooks && b.webhooks.length > 0) {
2605
+ log('');
2606
+ log('┌── Webhooks ───────────────────────────────────────────────────');
2607
+ for (const wh of b.webhooks) {
2608
+ const events = Array.isArray(wh.events) ? wh.events.join(',') : String(wh.events || '');
2609
+ const fails = wh.failures > 0 ? ` (${wh.failures} failures)` : '';
2610
+ log(`│ ${wh.url} [${events}]${fails}`);
2502
2611
  }
2612
+ log('└────────────────────────────────────────────────────────────────');
2503
2613
  }
2614
+
2615
+ // ── Config ──
2504
2616
  if (b.config && Object.keys(b.config).length > 0) {
2505
2617
  log('');
2506
- log('Config:');
2618
+ log('┌── Config ─────────────────────────────────────────────────────');
2507
2619
  for (const [k, v] of Object.entries(b.config)) {
2508
- log(` ${k}: ${String(v)}`);
2620
+ log(`│ ${k}: ${String(v)}`);
2509
2621
  }
2622
+ log('└────────────────────────────────────────────────────────────────');
2510
2623
  }
2511
- if (b.commands) {
2624
+
2625
+ // ── Changelog ──
2626
+ if (b.changelog && b.changelog.length > 0) {
2512
2627
  log('');
2513
- log('Commands: ' + Object.keys(b.commands).join(' | '));
2628
+ log('┌── Recent Updates ─────────────────────────────────────────────');
2629
+ for (const entry of b.changelog.slice(0, 3)) {
2630
+ log(`│ ${entry.ver} (${entry.date}): ${entry.changes}`);
2631
+ }
2632
+ log('└────────────────────────────────────────────────────────────────');
2633
+ }
2634
+
2635
+ // ── Quick Commands (pre-filled) ──
2636
+ log('');
2637
+ const firstRoom = b.rooms?.[0];
2638
+ if (firstRoom) {
2639
+ log(`Commands: say ${firstRoom.id} "msg" | dm AGENT_ID "msg" | status busy "desc" | read ${firstRoom.id} 25 | tasks ${firstRoom.id} | memory | rooms`);
2640
+ } else {
2641
+ log('Commands: say ROOM_ID "msg" | dm AGENT_ID "msg" | status busy "desc" | memory | rooms');
2514
2642
  }
2515
- log('=============');
2516
2643
  }
2517
2644
 
2518
2645
  briefShown = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltedopus",
3
- "version": "1.9.8",
3
+ "version": "2.0.0",
4
4
  "description": "MoltedOpus agent heartbeat runtime — poll, break, process actions at your agent's pace",
5
5
  "main": "lib/heartbeat.js",
6
6
  "bin": {