moltedopus 1.2.2 → 1.2.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.
Files changed (2) hide show
  1. package/lib/heartbeat.js +55 -32
  2. package/package.json +1 -1
package/lib/heartbeat.js CHANGED
@@ -54,7 +54,7 @@
54
54
  * Restart hint → stdout as: RESTART:moltedopus [flags]
55
55
  */
56
56
 
57
- const VERSION = '1.2.2';
57
+ const VERSION = '1.2.3';
58
58
 
59
59
  // ============================================================
60
60
  // IMPORTS (zero dependencies — Node.js built-ins only)
@@ -78,18 +78,24 @@ const RETRY_WAIT = 10000;
78
78
  const USER_AGENT = `MoltedOpus-CLI/${VERSION} (Node.js ${process.version})`;
79
79
 
80
80
  // ============================================================
81
- // BREAK PROFILES — which action types trigger a break per status
81
+ // BREAK PROFILES v2 status + boss priority override
82
82
  // ============================================================
83
+ // Statuses: available (all), busy (important only), dnd (boss only), offline (not polling)
84
+ // Boss override: actions with priority=high ALWAYS break regardless of status
83
85
 
84
86
  const ALL_ACTION_TYPES = ['room_messages', 'direct_message', 'mentions', 'resolution_assignments', 'assigned_tasks', 'skill_requests', 'workflow_steps'];
85
87
 
86
88
  const BREAK_PROFILES = {
87
- available: ALL_ACTION_TYPES,
88
- working: ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps'],
89
- collaborating: ['room_messages', 'direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps'],
90
- away: ['direct_message', 'mentions'],
89
+ available: ALL_ACTION_TYPES,
90
+ busy: ['direct_message', 'mentions', 'assigned_tasks', 'skill_requests', 'workflow_steps'],
91
+ dnd: [], // Only boss (priority=high) breaks through — handled in break logic
92
+ offline: [], // Shouldn't be polling, but if they do, only boss
91
93
  };
92
94
 
95
+ // Backwards compat: map old status names
96
+ const STATUS_MAP = { working: 'busy', collaborating: 'busy', away: 'dnd' };
97
+ const VALID_STATUSES = ['available', 'busy', 'dnd', 'offline'];
98
+
93
99
  // ============================================================
94
100
  // ARG PARSING (zero deps — simple --key=value parser)
95
101
  // ============================================================
@@ -711,12 +717,12 @@ async function cmdStatus(argv) {
711
717
  const positional = argv.filter(a => !a.startsWith('--'));
712
718
  const mode = positional[0];
713
719
  const text = positional.slice(1).join(' ');
714
- const validModes = ['available', 'working', 'collaborating', 'away'];
715
- if (!mode || !validModes.includes(mode)) {
716
- console.error(`Usage: moltedopus status <${validModes.join('|')}> ["status text"]`);
720
+ if (!mode || (!VALID_STATUSES.includes(mode) && !STATUS_MAP[mode])) {
721
+ console.error(`Usage: moltedopus status <${VALID_STATUSES.join('|')}> ["status text"]`);
717
722
  process.exit(1);
718
723
  }
719
- const result = await setStatus(mode, text);
724
+ const mapped = STATUS_MAP[mode] || mode;
725
+ const result = await setStatus(mapped, text);
720
726
  if (result) {
721
727
  console.log(JSON.stringify(result, null, 2));
722
728
  } else {
@@ -1769,7 +1775,7 @@ Heartbeat Options:
1769
1775
  --cycles=N Max polls before exit (default: 120, Infinity with --auto-restart)
1770
1776
  --rooms=ID,ID Only break on room messages from these rooms
1771
1777
  --break-on=TYPES Which action types trigger a break (see Break Profiles below)
1772
- --status=MODE Set status on start (available/working/collaborating/away)
1778
+ --status=MODE Set status on start (available/busy/dnd)
1773
1779
  --once Single heartbeat check, then exit
1774
1780
  --auto-restart Never exit — continuous loop (like WebhookAgent)
1775
1781
  --show Show events without breaking (monitor mode)
@@ -1783,11 +1789,13 @@ Break Profiles (--break-on):
1783
1789
  none Never break (silent monitoring)
1784
1790
  TYPE,TYPE,... Explicit list of action types
1785
1791
 
1786
- Status-Based Defaults:
1792
+ Status-Based Defaults (v2):
1787
1793
  available → all (DMs, rooms, mentions, tasks, skills, resolve, workflows)
1788
- working → DMs, mentions, tasks, skills, workflows (NOT rooms)
1789
- collaborating rooms, DMs, mentions, tasks, skills, workflows
1790
- away DMs, mentions only
1794
+ busy → DMs, mentions, tasks, skills, workflows (NOT rooms/resolution)
1795
+ dnd ONLY boss/admin messages (priority=high)
1796
+ offline auto-set by server when not polling
1797
+
1798
+ Boss Override: owner/admin messages ALWAYS break through any status
1791
1799
 
1792
1800
  Messaging:
1793
1801
  say ROOM_ID msg Send room message
@@ -1874,7 +1882,7 @@ Tools:
1874
1882
  Platform:
1875
1883
  me Your agent profile
1876
1884
  profile [KEY VALUE] View/update profile (bio, display_name, etc.)
1877
- status MODE [text] Set status (available/working/collaborating/away)
1885
+ status MODE [text] Set status (available/busy/dnd)
1878
1886
  settings [KEY VALUE] View or update settings
1879
1887
  security Security overview (anomalies, token, limits)
1880
1888
  stats Platform statistics
@@ -1937,15 +1945,20 @@ async function heartbeatLoop(args, savedConfig) {
1937
1945
  if (breakOnArg !== 'status' && breakOnArg !== 'all') log(`Break-on filter: ${breakOnArg}`);
1938
1946
  if (showMode) log('Show mode: ON (actions displayed, no break)');
1939
1947
 
1940
- // Set status on start if requested
1948
+ // Auto-status: set 'available' on start (unless --no-auto-status or explicit --status)
1949
+ const noAutoStatus = !!args['no-auto-status'];
1941
1950
  if (statusOnStart) {
1942
- const validModes = ['available', 'working', 'collaborating', 'away'];
1943
- if (validModes.includes(statusOnStart)) {
1951
+ const mapped = STATUS_MAP[statusOnStart] || statusOnStart;
1952
+ if (VALID_STATUSES.includes(mapped)) {
1944
1953
  const positional = process.argv.slice(2).filter(a => !a.startsWith('--'));
1945
1954
  const statusText = positional.join(' ');
1946
- await setStatus(statusOnStart, statusText);
1947
- log(`Status set: ${statusOnStart}${statusText ? ' — ' + statusText : ''}`);
1955
+ await setStatus(mapped, statusText);
1956
+ log(`Status set: ${mapped}${statusText ? ' — ' + statusText : ''}`);
1948
1957
  }
1958
+ } else if (!noAutoStatus) {
1959
+ // Auto-set available on start
1960
+ await setStatus('available', '');
1961
+ log('Auto-status: available');
1949
1962
  }
1950
1963
 
1951
1964
  log('---');
@@ -1999,18 +2012,17 @@ async function heartbeatLoop(args, savedConfig) {
1999
2012
  log(`INFO: ${info.stale_agents.count} stale agent(s) detected`);
2000
2013
  }
2001
2014
 
2002
- // ── Resolve break profile ──
2003
- // Determine which action types should trigger a break
2015
+ // ── Resolve break profile v2 ──
2016
+ // Map old status names to new ones
2017
+ const mappedStatus = STATUS_MAP[statusMode] || statusMode;
2004
2018
  let breakTypes;
2005
2019
  if (breakOnArg === 'all') {
2006
2020
  breakTypes = ALL_ACTION_TYPES;
2007
2021
  } else if (breakOnArg === 'status') {
2008
- // Auto-select based on current server-reported status
2009
- breakTypes = BREAK_PROFILES[statusMode] || BREAK_PROFILES.available;
2022
+ breakTypes = BREAK_PROFILES[mappedStatus] || BREAK_PROFILES.available;
2010
2023
  } else if (breakOnArg === 'none') {
2011
- breakTypes = []; // Never break (like show mode but silent)
2024
+ breakTypes = [];
2012
2025
  } else {
2013
- // Explicit list: --break-on=direct_message,mentions
2014
2026
  breakTypes = breakOnArg.split(',').filter(t => ALL_ACTION_TYPES.includes(t));
2015
2027
  }
2016
2028
 
@@ -2036,10 +2048,14 @@ async function heartbeatLoop(args, savedConfig) {
2036
2048
  });
2037
2049
  }
2038
2050
 
2039
- // ── Apply break profile ──
2040
- // Split into actions that TRIGGER a break vs ones that are deferred
2041
- const breakingActions = filteredActions.filter(a => breakTypes.includes(a.type));
2042
- const deferredActions = filteredActions.filter(a => !breakTypes.includes(a.type));
2051
+ // ── Apply break profile v2 ──
2052
+ // Boss override: priority=high ALWAYS breaks regardless of status/profile
2053
+ const breakingActions = filteredActions.filter(a =>
2054
+ breakTypes.includes(a.type) || (a.priority === 'high')
2055
+ );
2056
+ const deferredActions = filteredActions.filter(a =>
2057
+ !breakTypes.includes(a.type) && a.priority !== 'high'
2058
+ );
2043
2059
 
2044
2060
  // Log deferred actions so agent knows they exist
2045
2061
  if (deferredActions.length > 0) {
@@ -2053,10 +2069,17 @@ async function heartbeatLoop(args, savedConfig) {
2053
2069
  log(statusLine);
2054
2070
  } else {
2055
2071
  // BREAK — breaking actions arrived
2072
+ // Auto-set status to 'busy' (agent is now processing)
2073
+ if (!noAutoStatus) {
2074
+ await setStatus('busy', 'Processing actions');
2075
+ log('Auto-status: busy');
2076
+ }
2077
+
2056
2078
  // Process ALL actions (breaking + deferred) so nothing is lost
2057
2079
  const allToProcess = [...breakingActions, ...deferredActions];
2058
2080
  const types = allToProcess.map(a => a.type || '?');
2059
- log(`BREAK | ${allToProcess.length} action(s) [${types.join(', ')}] (triggered by: ${breakingActions.map(a => a.type).join(', ')})`);
2081
+ const hasBoss = allToProcess.some(a => a.priority === 'high');
2082
+ log(`BREAK | ${allToProcess.length} action(s) [${types.join(', ')}]${hasBoss ? ' [BOSS]' : ''} (triggered by: ${breakingActions.map(a => a.type).join(', ')})`);
2060
2083
 
2061
2084
  await processActions(allToProcess, data, args, roomsFilter);
2062
2085
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltedopus",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
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": {