rogerthat 1.24.4 → 1.24.6
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/dist/listen-here.js +81 -4
- package/package.json +1 -1
package/dist/listen-here.js
CHANGED
|
@@ -67,6 +67,11 @@ options:
|
|
|
67
67
|
Filtered messages do NOT write to --inbox, fire --on-message,
|
|
68
68
|
or print to stdout. Use for park-style channels: stay
|
|
69
69
|
wake-able only on real signals, let chatter accumulate silently.
|
|
70
|
+
--heartbeat <secs> write a "♥ alive" line to --inbox every <secs> seconds so an
|
|
71
|
+
operator/Monitor can confirm the relay is still up WITHOUT
|
|
72
|
+
consuming a real message. Off by default. Needs --inbox and
|
|
73
|
+
--format text. (Non-destructive alternative: poll /roster —
|
|
74
|
+
your callsign present = listener alive.)
|
|
70
75
|
--quiet suppress the default stdout dump of each message
|
|
71
76
|
|
|
72
77
|
if neither --on-message nor --inbox is given, messages print to stdout (one
|
|
@@ -116,6 +121,7 @@ function parseFlags(argv) {
|
|
|
116
121
|
inbox: { type: "string" },
|
|
117
122
|
format: { type: "string" },
|
|
118
123
|
"min-priority": { type: "string" },
|
|
124
|
+
heartbeat: { type: "string" },
|
|
119
125
|
quiet: { type: "boolean" },
|
|
120
126
|
help: { type: "boolean", short: "h" },
|
|
121
127
|
},
|
|
@@ -161,6 +167,13 @@ function parseFlags(argv) {
|
|
|
161
167
|
}
|
|
162
168
|
minPriority = v;
|
|
163
169
|
}
|
|
170
|
+
let heartbeat;
|
|
171
|
+
if (parsed.values.heartbeat !== undefined) {
|
|
172
|
+
const n = Number(parsed.values.heartbeat);
|
|
173
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
174
|
+
return { error: "--heartbeat must be a positive number of seconds" };
|
|
175
|
+
heartbeat = n;
|
|
176
|
+
}
|
|
164
177
|
return {
|
|
165
178
|
channel,
|
|
166
179
|
token,
|
|
@@ -174,6 +187,7 @@ function parseFlags(argv) {
|
|
|
174
187
|
format,
|
|
175
188
|
quiet: parsed.values.quiet === true,
|
|
176
189
|
minPriority,
|
|
190
|
+
heartbeat,
|
|
177
191
|
};
|
|
178
192
|
}
|
|
179
193
|
/** Sanitize a filename for use on disk: strip path separators, NUL, leading
|
|
@@ -299,6 +313,30 @@ function formatLine(args, msg, savedPaths) {
|
|
|
299
313
|
}
|
|
300
314
|
return JSON.stringify(msg);
|
|
301
315
|
}
|
|
316
|
+
/** Append a raw status line to the inbox file (if --inbox is set). Used for the
|
|
317
|
+
* relay's own health signals — connect / reconnect notices — so an agent's
|
|
318
|
+
* Monitor (a `tail -F` of the inbox) sees a confirmation that the listener is
|
|
319
|
+
* actually attached, instead of having to inspect `ps`/`ss` to tell a healthy
|
|
320
|
+
* relay from a crashed one. Written even under --quiet: quiet silences stdout,
|
|
321
|
+
* not the inbox's own heartbeat. The `⟲` marker keeps it greppable/skippable.
|
|
322
|
+
* Best-effort — a failed append must never take the relay down. */
|
|
323
|
+
function writeInboxStatus(args, text) {
|
|
324
|
+
if (!args.inbox)
|
|
325
|
+
return;
|
|
326
|
+
// Only in text mode. JSONL consumers parse each line (or the whole file) as
|
|
327
|
+
// JSON, so a bare `⟲ …` line would break them — they get message objects only.
|
|
328
|
+
if (args.format !== "text")
|
|
329
|
+
return;
|
|
330
|
+
try {
|
|
331
|
+
const dir = dirname(args.inbox);
|
|
332
|
+
if (dir && !existsSync(dir))
|
|
333
|
+
mkdirSync(dir, { recursive: true });
|
|
334
|
+
appendFileSync(args.inbox, `⟲ [listen-here] ${text}\n`, { mode: 0o600 });
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
/* never let a health-line write kill the relay */
|
|
338
|
+
}
|
|
339
|
+
}
|
|
302
340
|
async function dispatch(args, msg) {
|
|
303
341
|
// --min-priority filter: drop messages below the threshold entirely (no
|
|
304
342
|
// inbox write, no hook spawn, no stdout). Missing priority counts as
|
|
@@ -404,6 +442,11 @@ async function runOneConnection(args, sinceCursor, abortSignal) {
|
|
|
404
442
|
res.resume();
|
|
405
443
|
return { lastId: sinceCursor, reason: "ended", statusError: status };
|
|
406
444
|
}
|
|
445
|
+
// SSE handshake accepted → the relay is genuinely attached and will receive
|
|
446
|
+
// phone messages from here on. Announce it in the inbox so the agent's Monitor
|
|
447
|
+
// confirms "listening" without a manual selftest, and the operator's phone
|
|
448
|
+
// sees a peer come online. (See writeInboxStatus.)
|
|
449
|
+
writeInboxStatus(args, `● connected — listening on ${args.channel} as session ${(args.session ?? "").slice(0, 8)}…`);
|
|
407
450
|
let lastId = sinceCursor;
|
|
408
451
|
// If the operator aborts mid-stream, destroy() the response to unblock the
|
|
409
452
|
// for-await on it.
|
|
@@ -525,6 +568,17 @@ export async function runListenHere(argv) {
|
|
|
525
568
|
};
|
|
526
569
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
527
570
|
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
571
|
+
// Opt-in liveness heartbeat: write a `♥ alive` line to the inbox every N
|
|
572
|
+
// seconds so an operator/Monitor can confirm the relay is up WITHOUT consuming
|
|
573
|
+
// a real message. .unref() so it never keeps the process alive on its own;
|
|
574
|
+
// cleared on shutdown via the abort signal.
|
|
575
|
+
if (args.heartbeat) {
|
|
576
|
+
const hb = setInterval(() => {
|
|
577
|
+
writeInboxStatus(args, `♥ alive — session ${args.session.slice(0, 8)}… (still listening)`);
|
|
578
|
+
}, args.heartbeat * 1000);
|
|
579
|
+
hb.unref?.();
|
|
580
|
+
ac.signal.addEventListener("abort", () => clearInterval(hb));
|
|
581
|
+
}
|
|
528
582
|
if (!args.quiet) {
|
|
529
583
|
console.error(`[listen-here] connecting to ${args.origin}/api/channels/${args.channel}/stream`);
|
|
530
584
|
}
|
|
@@ -547,12 +601,35 @@ export async function runListenHere(argv) {
|
|
|
547
601
|
return 0;
|
|
548
602
|
if (result.statusError !== undefined) {
|
|
549
603
|
const status = result.statusError;
|
|
550
|
-
// 4xx (except 408/429)
|
|
604
|
+
// 4xx (except 408/429) usually mean our SESSION is gone, not that our
|
|
605
|
+
// creds are bad: a server restart wipes in-memory sessions AND tombstones,
|
|
606
|
+
// so the stream returns 400 not_joined (or 410) for the session we held.
|
|
607
|
+
// Our join creds are durable, so the robust move is to mint a FRESH
|
|
608
|
+
// session and resume — a restart should be transparent, not fatal. Only
|
|
609
|
+
// bail if we have no identity to re-join with, or the re-join itself fails
|
|
610
|
+
// (that's a genuinely bad token / identity_key).
|
|
551
611
|
if (status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
552
|
-
|
|
553
|
-
|
|
612
|
+
// Re-join needs the identity_key (joinForSession auto-joins with it). If
|
|
613
|
+
// the listener was started with an explicit --session and no
|
|
614
|
+
// --identity-key, we have nothing to re-join with → bail as before.
|
|
615
|
+
if (!args.identityKey) {
|
|
616
|
+
console.error(`[listen-here] server returned ${status} and no --identity-key to re-join with; exiting`);
|
|
617
|
+
return 1;
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
args.session = await joinForSession(args);
|
|
621
|
+
if (!args.quiet) {
|
|
622
|
+
console.error(`[listen-here] re-joined after ${status} as session ${args.session.slice(0, 8)}… — resuming`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
console.error(`[listen-here] re-join after ${status} failed (${err.message}); exiting`);
|
|
627
|
+
return 1;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.error(`[listen-here] server returned ${status} — will retry`);
|
|
554
632
|
}
|
|
555
|
-
console.error(`[listen-here] server returned ${status} — will retry`);
|
|
556
633
|
}
|
|
557
634
|
// Connection closed cleanly or with retryable error. Backoff then reconnect.
|
|
558
635
|
if (ac.signal.aborted)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rogerthat",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.6",
|
|
4
4
|
"mcpName": "io.github.opcastil11/rogerthat",
|
|
5
5
|
"description": "Real-time chat for AI agents. A walkie-talkie hub that lets two or more agents — Claude Code, Cursor, Cline, Claude Desktop, Codex — on different machines send messages to each other over MCP or plain REST. Hosted at rogerthat.chat or self-hosted with `npx rogerthat`.",
|
|
6
6
|
"keywords": [
|