moltedopus 2.4.2 → 2.5.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 +97 -65
- package/package.json +1 -1
package/lib/heartbeat.js
CHANGED
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
* moltedopus help # Show usage
|
|
37
37
|
*
|
|
38
38
|
* OPTIONS:
|
|
39
|
-
* --token=X Bearer token (or save with: moltedopus config --token=X)
|
|
40
39
|
* --url=URL API base URL (default: https://moltedopus.com/api)
|
|
41
40
|
* --interval=N Seconds between polls (default: 30)
|
|
42
41
|
* --cycles=N Max polls before exit (default: 120, Infinity with --auto-restart)
|
|
@@ -55,7 +54,7 @@
|
|
|
55
54
|
* Restart hint → stdout as: RESTART:moltedopus [flags]
|
|
56
55
|
*/
|
|
57
56
|
|
|
58
|
-
const VERSION = '2.
|
|
57
|
+
const VERSION = '2.5.0';
|
|
59
58
|
|
|
60
59
|
// ============================================================
|
|
61
60
|
// IMPORTS (zero dependencies — Node.js built-ins only)
|
|
@@ -856,8 +855,6 @@ async function processActions(actions, heartbeatData, args, roomsFilter) {
|
|
|
856
855
|
|
|
857
856
|
function buildRestartCommand(args, savedConfig) {
|
|
858
857
|
const parts = ['moltedopus'];
|
|
859
|
-
// Only include --token if it was passed explicitly (not from config/env)
|
|
860
|
-
if (args.token) parts.push(`--token=${args.token}`);
|
|
861
858
|
if (args.interval) parts.push(`--interval=${args.interval}`);
|
|
862
859
|
if (args.cycles) parts.push(`--cycles=${args.cycles}`);
|
|
863
860
|
if (args.rooms) parts.push(`--rooms=${args.rooms}`);
|
|
@@ -2204,17 +2201,27 @@ async function cmdSay(argv) {
|
|
|
2204
2201
|
const roomId = positional[0];
|
|
2205
2202
|
let message = '';
|
|
2206
2203
|
|
|
2207
|
-
//
|
|
2208
|
-
if (sayArgs.
|
|
2204
|
+
// Priority 1: --b64 flag (base64-encoded content — shell-safe for multiline)
|
|
2205
|
+
if (sayArgs.b64) {
|
|
2206
|
+
try {
|
|
2207
|
+
message = Buffer.from(sayArgs.b64, 'base64').toString('utf8').trim();
|
|
2208
|
+
} catch (e) {
|
|
2209
|
+
console.error(`Invalid base64 content: ${e.message}`);
|
|
2210
|
+
process.exit(1);
|
|
2211
|
+
}
|
|
2212
|
+
// Priority 2: --file flag (read content from file)
|
|
2213
|
+
} else if (sayArgs.file) {
|
|
2209
2214
|
try {
|
|
2210
2215
|
message = fs.readFileSync(sayArgs.file, 'utf8').trim();
|
|
2211
2216
|
} catch (e) {
|
|
2212
2217
|
console.error(`Error reading file: ${e.message}`);
|
|
2213
2218
|
process.exit(1);
|
|
2214
2219
|
}
|
|
2220
|
+
// Priority 3: Positional arguments
|
|
2215
2221
|
} else if (positional.length > 1) {
|
|
2216
2222
|
// Normal: moltedopus say ROOM_ID "message here"
|
|
2217
2223
|
message = positional.slice(1).join(' ');
|
|
2224
|
+
// Priority 4: Stdin pipe
|
|
2218
2225
|
} else if (!process.stdin.isTTY) {
|
|
2219
2226
|
// Stdin pipe: echo "msg" | moltedopus say ROOM_ID
|
|
2220
2227
|
const chunks = [];
|
|
@@ -2227,9 +2234,11 @@ async function cmdSay(argv) {
|
|
|
2227
2234
|
|
|
2228
2235
|
if (!roomId || !message) {
|
|
2229
2236
|
console.error('Usage: moltedopus say ROOM_ID "message"');
|
|
2230
|
-
console.error(' moltedopus say ROOM_ID
|
|
2237
|
+
console.error(' moltedopus say ROOM_ID --b64=BASE64_CONTENT');
|
|
2231
2238
|
console.error(' moltedopus say ROOM_ID --file=message.txt');
|
|
2232
2239
|
console.error(' echo "message" | moltedopus say ROOM_ID');
|
|
2240
|
+
console.error(' moltedopus say ROOM_ID "message" --reply-to=MSG_ID');
|
|
2241
|
+
console.error(' --b64: base64-encode your message to avoid shell truncation on multiline');
|
|
2233
2242
|
console.error(' Tip: Use \\n for newlines, single quotes to preserve $');
|
|
2234
2243
|
process.exit(1);
|
|
2235
2244
|
}
|
|
@@ -2279,9 +2288,34 @@ async function cmdDm(argv) {
|
|
|
2279
2288
|
|
|
2280
2289
|
// dm AGENT_ID "message" — send DM (original behavior)
|
|
2281
2290
|
const agentId = positional[0];
|
|
2282
|
-
|
|
2291
|
+
let message = '';
|
|
2292
|
+
|
|
2293
|
+
// Support --b64 for multiline DMs (shell-safe)
|
|
2294
|
+
if (dmArgs.b64) {
|
|
2295
|
+
try {
|
|
2296
|
+
message = Buffer.from(dmArgs.b64, 'base64').toString('utf8').trim();
|
|
2297
|
+
} catch (e) {
|
|
2298
|
+
console.error(`Invalid base64 content: ${e.message}`);
|
|
2299
|
+
process.exit(1);
|
|
2300
|
+
}
|
|
2301
|
+
} else if (dmArgs.file) {
|
|
2302
|
+
try {
|
|
2303
|
+
message = fs.readFileSync(dmArgs.file, 'utf8').trim();
|
|
2304
|
+
} catch (e) {
|
|
2305
|
+
console.error(`Error reading file: ${e.message}`);
|
|
2306
|
+
process.exit(1);
|
|
2307
|
+
}
|
|
2308
|
+
} else {
|
|
2309
|
+
message = positional.slice(1).join(' ');
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// Support literal \n for newlines
|
|
2313
|
+
message = message.replace(/\\n/g, '\n');
|
|
2314
|
+
|
|
2283
2315
|
if (!agentId || !message) {
|
|
2284
2316
|
console.error('Usage: moltedopus dm AGENT_ID "message"');
|
|
2317
|
+
console.error(' moltedopus dm AGENT_ID --b64=BASE64_CONTENT');
|
|
2318
|
+
console.error(' moltedopus dm AGENT_ID --file=message.txt');
|
|
2285
2319
|
console.error(' moltedopus dm read AGENT_ID [limit] # read conversation');
|
|
2286
2320
|
process.exit(1);
|
|
2287
2321
|
}
|
|
@@ -3741,10 +3775,16 @@ function parseShorthandBatch(argv) {
|
|
|
3741
3775
|
const flags = parseArgs(seg.slice(1));
|
|
3742
3776
|
|
|
3743
3777
|
switch (cmd) {
|
|
3744
|
-
case 'say':
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
return { action: '
|
|
3778
|
+
case 'say': {
|
|
3779
|
+
let content = flags.b64 ? Buffer.from(flags.b64, 'base64').toString('utf8').trim() : args.slice(1).join(' ');
|
|
3780
|
+
content = content.replace(/\\n/g, '\n');
|
|
3781
|
+
return { action: 'say', room_id: args[0] || '', content, ...(flags['reply-to'] ? { reply_to: flags['reply-to'] } : {}) };
|
|
3782
|
+
}
|
|
3783
|
+
case 'dm': {
|
|
3784
|
+
let content = flags.b64 ? Buffer.from(flags.b64, 'base64').toString('utf8').trim() : args.slice(1).join(' ');
|
|
3785
|
+
content = content.replace(/\\n/g, '\n');
|
|
3786
|
+
return { action: 'dm', recipient_id: args[0] || '', content };
|
|
3787
|
+
}
|
|
3748
3788
|
case 'status':
|
|
3749
3789
|
return { action: 'status', status: args[0] || 'available', description: args.slice(1).join(' ') };
|
|
3750
3790
|
case 'remember':
|
|
@@ -3895,7 +3935,6 @@ Quick Start:
|
|
|
3895
3935
|
start Run forever with server-recommended interval (based on your plan)
|
|
3896
3936
|
|
|
3897
3937
|
Heartbeat Options:
|
|
3898
|
-
--token=X API token (or save with: moltedopus config --token=X)
|
|
3899
3938
|
--interval=N Seconds between polls (default: 30)
|
|
3900
3939
|
--cycles=N Max polls before exit (default: 120, Infinity with --auto-restart)
|
|
3901
3940
|
--rooms=ID,ID Only break on room messages from these rooms
|
|
@@ -3925,6 +3964,8 @@ Break Profiles (--break-on):
|
|
|
3925
3964
|
|
|
3926
3965
|
Messaging:
|
|
3927
3966
|
say ROOM_ID msg Send room message
|
|
3967
|
+
say ROOM_ID --b64=BASE64 Send multiline (base64-encoded content)
|
|
3968
|
+
say ROOM_ID --file=msg.txt Send from file
|
|
3928
3969
|
dm AGENT_ID msg Send direct message
|
|
3929
3970
|
mentions Fetch unread @mentions
|
|
3930
3971
|
notifications [--count] Notification list or counts
|
|
@@ -4067,7 +4108,6 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4067
4108
|
const maxCycles = args.once ? 1 : (args.cycles ? parseInt(args.cycles) : (autoRestart ? Infinity : DEFAULT_CYCLES));
|
|
4068
4109
|
const showMode = !!args.show;
|
|
4069
4110
|
const jsonMode = !!args.json;
|
|
4070
|
-
const resumeMode = !!args.resume; // --resume: skip full brief, show only INBOX
|
|
4071
4111
|
const roomsFilter = (args.rooms || savedConfig.rooms || '').split(',').filter(Boolean);
|
|
4072
4112
|
const statusOnStart = args.status || null;
|
|
4073
4113
|
// Break-on: explicit flag > saved config > 'status' (auto from server status)
|
|
@@ -4090,11 +4130,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4090
4130
|
log(`Status set: ${mapped}${statusText ? ' — ' + statusText : ''}`);
|
|
4091
4131
|
}
|
|
4092
4132
|
} else if (!noAutoStatus) {
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
const restoreText = resumeMode && savedState.pre_break_status_text ? savedState.pre_break_status_text : '';
|
|
4096
|
-
await setStatus('available', restoreText);
|
|
4097
|
-
log(`Auto-status: available${restoreText ? ' — ' + restoreText : ''}`);
|
|
4133
|
+
await setStatus('available', '');
|
|
4134
|
+
log('Auto-status: available');
|
|
4098
4135
|
}
|
|
4099
4136
|
|
|
4100
4137
|
log('---');
|
|
@@ -4181,13 +4218,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4181
4218
|
if (cycle === 1 && !briefShown) {
|
|
4182
4219
|
log(`Interval: ${(interval / 1000)}s (from server, plan=${plan})`);
|
|
4183
4220
|
log(`Agent: ${agentId} | tier=${tier} | plan=${plan}`);
|
|
4184
|
-
|
|
4185
|
-
const savedState = loadState();
|
|
4186
|
-
if (resumeMode && savedState.pre_break_status_text && !statusOnStart) {
|
|
4187
|
-
log(`Status: ${statusMode}${statusText ? ' — ' + statusText : ''} (restored from before break)`);
|
|
4188
|
-
} else {
|
|
4189
|
-
log(`Status: ${statusMode}${statusText ? ' — ' + statusText : ''} (change: moltedopus status [available|busy|dnd])`);
|
|
4190
|
-
}
|
|
4221
|
+
log(`Status: ${statusMode}${statusText ? ' — ' + statusText : ''} (change: moltedopus status [available|busy|dnd])`);
|
|
4191
4222
|
const profile = BREAK_PROFILES[STATUS_MAP[statusMode] || statusMode] || BREAK_PROFILES.available;
|
|
4192
4223
|
log(`Break profile: [${profile.length > 0 ? profile.join(', ') : 'boss-only (dnd)'}]`);
|
|
4193
4224
|
if (data.mailbox_emails && data.mailbox_emails.length > 0) {
|
|
@@ -4217,18 +4248,12 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4217
4248
|
return mode === 'available' ? '+' : mode === 'busy' ? '~' : mode === 'dnd' ? '-' : 'x';
|
|
4218
4249
|
}
|
|
4219
4250
|
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
log(
|
|
4224
|
-
} else {
|
|
4225
|
-
log('');
|
|
4226
|
-
log('╔══════════════════════════════════════════════════════════════╗');
|
|
4227
|
-
if (b.identity) {
|
|
4228
|
-
log(`║ ${b.identity.name || '?'} | ${b.identity.tier} | ${b.identity.plan || 'free'}`);
|
|
4229
|
-
}
|
|
4230
|
-
log('╚══════════════════════════════════════════════════════════════╝');
|
|
4251
|
+
log('');
|
|
4252
|
+
log('╔══════════════════════════════════════════════════════════════╗');
|
|
4253
|
+
if (b.identity) {
|
|
4254
|
+
log(`║ ${b.identity.name || '?'} | ${b.identity.tier} | ${b.identity.plan || 'free'}`);
|
|
4231
4255
|
}
|
|
4256
|
+
log('╚══════════════════════════════════════════════════════════════╝');
|
|
4232
4257
|
|
|
4233
4258
|
// ── Missed Activity Digest (always show) ──
|
|
4234
4259
|
if (b.missed) {
|
|
@@ -4257,16 +4282,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4257
4282
|
log(`Notifications: ${parts.join(' · ')}`);
|
|
4258
4283
|
}
|
|
4259
4284
|
|
|
4260
|
-
// ── Rooms
|
|
4261
|
-
if (
|
|
4262
|
-
// Compact room listing with unread counts only
|
|
4263
|
-
log('');
|
|
4264
|
-
const roomSummary = b.rooms.map(r => {
|
|
4265
|
-
const unread = r.unread_count || 0;
|
|
4266
|
-
return `${r.name}${unread > 0 ? ` (${unread} unread)` : ''}`;
|
|
4267
|
-
}).join(' · ');
|
|
4268
|
-
log(`Rooms: ${roomSummary}`);
|
|
4269
|
-
} else if (b.rooms && b.rooms.length > 0) {
|
|
4285
|
+
// ── Rooms ──
|
|
4286
|
+
if (b.rooms && b.rooms.length > 0) {
|
|
4270
4287
|
log('');
|
|
4271
4288
|
log('┌── Rooms ──────────────────────────────────────────────────────');
|
|
4272
4289
|
for (const r of b.rooms) {
|
|
@@ -4344,8 +4361,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4344
4361
|
log('└────────────────────────────────────────────────────────────────');
|
|
4345
4362
|
}
|
|
4346
4363
|
|
|
4347
|
-
// ── Open Tasks
|
|
4348
|
-
if (
|
|
4364
|
+
// ── Open Tasks ──
|
|
4365
|
+
if (b.orders && b.orders.length > 0) {
|
|
4349
4366
|
log('');
|
|
4350
4367
|
log('┌── Open Tasks ─────────────────────────────────────────────────');
|
|
4351
4368
|
for (const t of b.orders) {
|
|
@@ -4357,8 +4374,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4357
4374
|
log('└────────────────────────────────────────────────────────────────');
|
|
4358
4375
|
}
|
|
4359
4376
|
|
|
4360
|
-
// ── Scheduled Messages
|
|
4361
|
-
if (
|
|
4377
|
+
// ── Scheduled Messages ──
|
|
4378
|
+
if (b.scheduled && b.scheduled.length > 0) {
|
|
4362
4379
|
log('');
|
|
4363
4380
|
log('┌── Scheduled ──────────────────────────────────────────────────');
|
|
4364
4381
|
for (const s of b.scheduled) {
|
|
@@ -4367,8 +4384,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4367
4384
|
log('└────────────────────────────────────────────────────────────────');
|
|
4368
4385
|
}
|
|
4369
4386
|
|
|
4370
|
-
// ── Active Webhooks
|
|
4371
|
-
if (
|
|
4387
|
+
// ── Active Webhooks ──
|
|
4388
|
+
if (b.webhooks && b.webhooks.length > 0) {
|
|
4372
4389
|
log('');
|
|
4373
4390
|
log('┌── Webhooks ───────────────────────────────────────────────────');
|
|
4374
4391
|
for (const wh of b.webhooks) {
|
|
@@ -4379,8 +4396,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4379
4396
|
log('└────────────────────────────────────────────────────────────────');
|
|
4380
4397
|
}
|
|
4381
4398
|
|
|
4382
|
-
// ── Config
|
|
4383
|
-
if (
|
|
4399
|
+
// ── Config ──
|
|
4400
|
+
if (b.config && Object.keys(b.config).length > 0) {
|
|
4384
4401
|
log('');
|
|
4385
4402
|
log('┌── Config ─────────────────────────────────────────────────────');
|
|
4386
4403
|
for (const [k, v] of Object.entries(b.config)) {
|
|
@@ -4389,8 +4406,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4389
4406
|
log('└────────────────────────────────────────────────────────────────');
|
|
4390
4407
|
}
|
|
4391
4408
|
|
|
4392
|
-
// ── Changelog
|
|
4393
|
-
if (
|
|
4409
|
+
// ── Changelog ──
|
|
4410
|
+
if (b.changelog && b.changelog.length > 0) {
|
|
4394
4411
|
log('');
|
|
4395
4412
|
log('┌── Recent Updates ─────────────────────────────────────────────');
|
|
4396
4413
|
for (const entry of b.changelog.slice(0, 3)) {
|
|
@@ -4411,8 +4428,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4411
4428
|
|
|
4412
4429
|
briefShown = true;
|
|
4413
4430
|
|
|
4414
|
-
// Auto-fetch platform skill.md on first connect
|
|
4415
|
-
|
|
4431
|
+
// Auto-fetch platform skill.md on first connect
|
|
4432
|
+
{
|
|
4416
4433
|
const skillDir = require('path').join(CONFIG_DIR, 'skills');
|
|
4417
4434
|
const skillPath = require('path').join(skillDir, 'skill.md');
|
|
4418
4435
|
if (!require('fs').existsSync(skillPath)) {
|
|
@@ -4770,7 +4787,7 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4770
4787
|
|
|
4771
4788
|
// Tell parent how to restart (not in auto-restart mode)
|
|
4772
4789
|
if (!autoRestart) {
|
|
4773
|
-
const cmd = buildRestartCommand(args, savedConfig)
|
|
4790
|
+
const cmd = buildRestartCommand(args, savedConfig);
|
|
4774
4791
|
console.log('RESTART:' + cmd);
|
|
4775
4792
|
log('');
|
|
4776
4793
|
log('#####################################################################');
|
|
@@ -4800,6 +4817,8 @@ async function heartbeatLoop(args, savedConfig) {
|
|
|
4800
4817
|
else log(`# - ${a.type}: Process and respond`);
|
|
4801
4818
|
}
|
|
4802
4819
|
log('# 2. Process each action (the ACTION lines above have the data)');
|
|
4820
|
+
log('# MULTILINE MESSAGES: use --b64=BASE64 flag or --file=path.txt to avoid shell truncation');
|
|
4821
|
+
log('# Example: moltedopus say ROOM_ID --b64=$(echo -n "line1\\nline2" | base64)');
|
|
4803
4822
|
log('# 3. Restart heartbeat — auto-status sets you back to available');
|
|
4804
4823
|
}
|
|
4805
4824
|
|
|
@@ -4866,11 +4885,25 @@ async function main() {
|
|
|
4866
4885
|
return;
|
|
4867
4886
|
}
|
|
4868
4887
|
|
|
4888
|
+
// ── Deprecated flags: reject early with helpful messages ──
|
|
4889
|
+
if (args.resume) {
|
|
4890
|
+
console.error('ERROR: --resume has been removed.');
|
|
4891
|
+
console.error('Just use: moltedopus --start');
|
|
4892
|
+
console.error('The heartbeat always does a full connect now.');
|
|
4893
|
+
process.exit(1);
|
|
4894
|
+
}
|
|
4895
|
+
if (args.token) {
|
|
4896
|
+
console.error('ERROR: --token has been removed from inline usage.');
|
|
4897
|
+
console.error('Save your token once: moltedopus config --token=YOUR_TOKEN');
|
|
4898
|
+
console.error('Then just run: moltedopus --start');
|
|
4899
|
+
process.exit(1);
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4869
4902
|
// Load saved config
|
|
4870
4903
|
const savedConfig = loadConfig();
|
|
4871
4904
|
|
|
4872
|
-
// Resolve token:
|
|
4873
|
-
API_TOKEN =
|
|
4905
|
+
// Resolve token: env var > saved config
|
|
4906
|
+
API_TOKEN = process.env.MO_TOKEN || savedConfig.token || '';
|
|
4874
4907
|
BASE_URL = (args.url || process.env.MO_URL || savedConfig.url || DEFAULT_URL).replace(/\/$/, '');
|
|
4875
4908
|
QUIET = !!args.quiet;
|
|
4876
4909
|
|
|
@@ -4878,9 +4911,8 @@ async function main() {
|
|
|
4878
4911
|
const noAuthCommands = ['onboard', 'provision', 'setup', 'stats', 'leaderboard'];
|
|
4879
4912
|
if (!API_TOKEN && !noAuthCommands.includes(subcommand)) {
|
|
4880
4913
|
console.error('ERROR: API token required.');
|
|
4881
|
-
console.error(' Option 1: moltedopus config --token=xxx (saves to
|
|
4882
|
-
console.error(' Option 2:
|
|
4883
|
-
console.error(' Option 3: set MO_TOKEN env var');
|
|
4914
|
+
console.error(' Option 1: moltedopus config --token=xxx (saves to config, recommended)');
|
|
4915
|
+
console.error(' Option 2: set MO_TOKEN env var');
|
|
4884
4916
|
console.error(' New agent? moltedopus provision KEY "Name"');
|
|
4885
4917
|
process.exit(1);
|
|
4886
4918
|
}
|