agent-coord-mcp 0.4.6 → 0.4.7
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/server.js +0 -0
- package/package.json +1 -1
- package/scripts/coord-chat.mjs +88 -33
package/dist/server.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/scripts/coord-chat.mjs
CHANGED
|
@@ -661,31 +661,64 @@ async function drainAndPrint() {
|
|
|
661
661
|
if (changed) writeJsonAtomic(CURSOR_FILE, cursor);
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
+
// Consecutive messages from the same sender within this window are visually
|
|
665
|
+
// grouped: the second one drops its header/blank line and just continues the
|
|
666
|
+
// gutter, Slack-style.
|
|
667
|
+
const GROUP_WINDOW = 2 * 60 * 1000;
|
|
668
|
+
let lastBlock = { who: null, ts: 0, kind: null };
|
|
669
|
+
|
|
670
|
+
// Matches "@<this agent>" not followed by a name char, so we can flag messages
|
|
671
|
+
// that ping the current user. ID may contain regex metachars — escape it.
|
|
672
|
+
const SELF_MENTION_RE = new RegExp(
|
|
673
|
+
"@" + ID.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?![A-Za-z0-9._-])",
|
|
674
|
+
);
|
|
675
|
+
const mentionsSelf = (text) => SELF_MENTION_RE.test(text ?? "");
|
|
676
|
+
|
|
677
|
+
// Recency at a glance: "now" / "5m" for fresh messages, falling back to a wall
|
|
678
|
+
// clock for anything over an hour (a stale "63m" reads worse than "08:34").
|
|
679
|
+
function relTime(ts) {
|
|
680
|
+
const mins = Math.floor((Date.now() - ts) / 60000);
|
|
681
|
+
if (mins < 1) return "now";
|
|
682
|
+
if (mins < 60) return `${mins}m`;
|
|
683
|
+
const d = new Date(ts);
|
|
684
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
685
|
+
}
|
|
686
|
+
|
|
664
687
|
function printMsg(kind, m, opts = {}) {
|
|
665
|
-
const d = new Date(m.ts);
|
|
666
|
-
const t = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
667
688
|
const who = m.from ?? "?";
|
|
668
689
|
const color = agentColor(who);
|
|
669
690
|
const gutter = color("▎");
|
|
670
|
-
const tag = kind === "DM" ? A.bold(A.cyan("DM")) : A.dim("room");
|
|
671
|
-
const nick = A.bold(color(who));
|
|
672
|
-
const meta = `${A.dim(t)} ${tag} ${nick}`;
|
|
673
691
|
const prefix = opts.history ? A.dim(" ") : "";
|
|
674
692
|
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
// colored gutter prepends every wrapped line — terminal auto-wrap
|
|
678
|
-
// would lose it.
|
|
693
|
+
// Body wraps manually under a continuous gutter — terminal auto-wrap would
|
|
694
|
+
// lose the colored gutter on continuation lines.
|
|
679
695
|
const prefixW = visibleLength(prefix);
|
|
680
|
-
const
|
|
681
|
-
const restChrome = prefixW + visibleLength(`▎ `);
|
|
682
|
-
const firstWidth = Math.max(20, COLS - firstChrome);
|
|
683
|
-
const restWidth = Math.max(20, COLS - restChrome);
|
|
684
|
-
|
|
696
|
+
const bodyWidth = Math.max(20, COLS - prefixW - visibleLength(`▎ `));
|
|
685
697
|
const text = (m.text ?? "").split("\n").map(formatBody).join("\n");
|
|
686
|
-
const lines =
|
|
687
|
-
|
|
688
|
-
|
|
698
|
+
const lines = wrapBody(text, bodyWidth);
|
|
699
|
+
|
|
700
|
+
// Group onto the previous block when it's the same live sender within the
|
|
701
|
+
// window — skip the blank line + header, just keep the gutter going.
|
|
702
|
+
const grouped = !opts.history
|
|
703
|
+
&& lastBlock.who === who && lastBlock.kind === kind
|
|
704
|
+
&& (m.ts - lastBlock.ts) < GROUP_WINDOW;
|
|
705
|
+
|
|
706
|
+
if (!grouped) {
|
|
707
|
+
// Header on its own line so the sender is a scannable anchor and the body
|
|
708
|
+
// always starts at a fixed column. "room" is the default and stays
|
|
709
|
+
// implied; only DMs get a badge. A ping to the current user brightens the
|
|
710
|
+
// gutter and adds a ► marker so it pops out of the firehose.
|
|
711
|
+
const pinged = mentionsSelf(m.text);
|
|
712
|
+
const badge = kind === "DM" ? A.bold(A.cyan("DM ")) : "";
|
|
713
|
+
const marker = pinged ? A.bold(A.yellow("► ")) : "";
|
|
714
|
+
const headGutter = pinged ? A.bold(color("▌")) : gutter;
|
|
715
|
+
const header = `${marker}${badge}${A.bold(color(who))} ${A.dim(`· ${relTime(m.ts)}`)}`;
|
|
716
|
+
say("");
|
|
717
|
+
say(`${prefix}${headGutter} ${header}`);
|
|
718
|
+
}
|
|
719
|
+
for (const line of lines) say(`${prefix}${gutter} ${line}`);
|
|
720
|
+
|
|
721
|
+
if (!opts.history) lastBlock = { who, ts: m.ts, kind };
|
|
689
722
|
}
|
|
690
723
|
|
|
691
724
|
function visibleLength(s) {
|
|
@@ -693,24 +726,31 @@ function visibleLength(s) {
|
|
|
693
726
|
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
694
727
|
}
|
|
695
728
|
|
|
696
|
-
|
|
697
|
-
|
|
729
|
+
// Wrap one message's text, preserving list/indent structure: a leading bullet
|
|
730
|
+
// ("- ", "* ", "1. ", "2) ") or whitespace indent is detected so wrapped
|
|
731
|
+
// continuation lines hang-indent under the text rather than re-flowing as flat
|
|
732
|
+
// prose.
|
|
733
|
+
function wrapBody(text, width) {
|
|
734
|
+
if (width <= 0) return [text];
|
|
698
735
|
const out = [];
|
|
699
|
-
for (const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
736
|
+
for (const raw of text.split("\n")) {
|
|
737
|
+
const mk = raw.match(/^(\s*(?:[-*•]\s+|\d+[.)]\s+)?)([\s\S]*)$/);
|
|
738
|
+
const lead = mk ? mk[1] : "";
|
|
739
|
+
const body = mk ? mk[2] : raw;
|
|
740
|
+
const indent = " ".repeat(visibleLength(lead));
|
|
741
|
+
const words = body.length ? body.split(/\s+/) : [];
|
|
742
|
+
if (!words.length) { out.push(lead.trimEnd()); continue; }
|
|
743
|
+
let line = lead + words[0];
|
|
744
|
+
for (let i = 1; i < words.length; i++) {
|
|
745
|
+
const proposed = line + " " + words[i];
|
|
746
|
+
if (visibleLength(proposed) > width) {
|
|
706
747
|
out.push(line);
|
|
707
|
-
line =
|
|
708
|
-
currentWidth = restWidth;
|
|
748
|
+
line = indent + words[i];
|
|
709
749
|
} else {
|
|
710
750
|
line = proposed;
|
|
711
751
|
}
|
|
712
752
|
}
|
|
713
|
-
|
|
753
|
+
out.push(line);
|
|
714
754
|
}
|
|
715
755
|
return out;
|
|
716
756
|
}
|
|
@@ -736,16 +776,31 @@ function formatBody(text) {
|
|
|
736
776
|
// non-asterisk neighbors)
|
|
737
777
|
s = s.replace(/(?<![*\w])\*([^*\n]+)\*(?![*\w])/g, (_, t) => `\x1b[3m${t}\x1b[0m`);
|
|
738
778
|
s = s.replace(/(?<![_\w])_([^_\n]+)_(?![_\w])/g, (_, t) => `\x1b[3m${t}\x1b[0m`);
|
|
739
|
-
// [text](url) — show text underlined with a dim trailing (url)
|
|
779
|
+
// [text](url) — show text underlined with a dim, shortened trailing (url)
|
|
740
780
|
s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_, t, u) =>
|
|
741
|
-
`\x1b[4m${t}\x1b[0m${A.dim(` (${u})`)}`,
|
|
781
|
+
`\x1b[4m${t}\x1b[0m${A.dim(` (${shortenUrl(u)})`)}`,
|
|
742
782
|
);
|
|
743
|
-
// Bare URLs — underline only the URL itself
|
|
744
|
-
s = s.replace(/\bhttps?:\/\/[^\s)]+/g, (u) => `\x1b[4m${u}\x1b[0m`);
|
|
783
|
+
// Bare URLs — underline only the URL itself, shortened if long
|
|
784
|
+
s = s.replace(/\bhttps?:\/\/[^\s)]+/g, (u) => `\x1b[4m${shortenUrl(u)}\x1b[0m`);
|
|
745
785
|
return s;
|
|
746
786
|
}).join("");
|
|
747
787
|
}
|
|
748
788
|
|
|
789
|
+
// Long URLs eat a whole wrapped line. Collapse to "host/…/last-segment" so the
|
|
790
|
+
// link stays recognizable without dominating the message. Short URLs are left
|
|
791
|
+
// intact (and remain copy-pasteable).
|
|
792
|
+
function shortenUrl(u) {
|
|
793
|
+
if (u.length <= 48) return u;
|
|
794
|
+
try {
|
|
795
|
+
const { host, pathname } = new URL(u);
|
|
796
|
+
const tail = pathname.split("/").filter(Boolean).pop() ?? "";
|
|
797
|
+
const short = tail ? `${host}/…/${tail}` : host;
|
|
798
|
+
return short.length < u.length ? short : u.slice(0, 45) + "…";
|
|
799
|
+
} catch {
|
|
800
|
+
return u.slice(0, 45) + "…";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
749
804
|
async function printAgents() {
|
|
750
805
|
const reg = readJsonSafe(AGENTS_FILE, {});
|
|
751
806
|
const now = Date.now();
|