agent-coord-mcp 0.13.0 → 0.14.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.
package/README.md CHANGED
@@ -293,7 +293,7 @@ Either way, `list_agents` will show the agent with `transport: "tmux-push"` so p
293
293
 
294
294
  Under the hood: [`hooks/tmux-pusher.mjs`](./hooks/tmux-pusher.mjs) is the daemon. It watches `~/agent-coord/inbox/<id>.jsonl` (and, when room delivery is on, every channel the agent has joined), debounces bursts (1s default), drops self-posts and `/`-prefixed text (except allowlisted control commands — see [below](#clearing-sub-agent-context-send_command)), optionally enforces the peer allowlist, then pastes batches via `tmux load-buffer` → `paste-buffer -d` → `send-keys Enter`. Single-flight so two batches never overlap.
295
295
 
296
- **Delivery tiers.** Not every message deserves an agent turn. Only push-now traffic wakes the pane: `BLOCKER:`, `DAVID_DECISION:`, literal `GO:` work orders, `SCOPE:`/`SCOPE CHANGE:` from a trusted sender (coordinator/gate ids resolved from the registry), `DONE:` when this agent is a gate runner (QA/coordinator — from its registry role, re-resolved every 30s, overridable with `AGENT_COORD_GATE_RUNNER=1|0`), control commands, and server-flagged messages like the post-`/clear` identity reminder. Prefixes are literal and case-sensitive. Everything else (`FYI:`, `AGENT_ACTION:`, `RISK:`, chatter) queues silently and rides the next urgent push as one coalesced `[agent-coord] digest` block. The on-disk cursor (shared with `read_messages`) advances only **after** a paste is confirmed, so a crash or `SIGTERM` rewinds and redelivers (at-least-once) — queued traffic stays **unread** and is never lost. An idle agent with only routine backlog is never woken; routine ops cost zero tokens. `AGENT_COORD_TIERS=0` restores legacy push-everything. The classifier and queue are pure ([`hooks/tier.mjs`](./hooks/tier.mjs)). Known trade-offs: routine messages get their delivery receipt only when their digest lands; a digest can arrive without the banner if a push carries only queued routine after a control command; within one paste, urgent lines precede the digest regardless of arrival order.
296
+ **Delivery tiers.** Not every message deserves an agent turn. Only push-now traffic wakes the pane: `BLOCKER:`, `DAVID_DECISION:`, literal `GO:` work orders, `SCOPE:`/`SCOPE CHANGE:` from a trusted sender (coordinator/gate ids resolved from the registry), `DONE:` when this agent is a gate runner (QA/coordinator — from its registry role, re-resolved every 30s, overridable with `AGENT_COORD_GATE_RUNNER=1|0`), control commands, and server-flagged messages like the post-`/clear` identity reminder. Prefixes are literal and case-sensitive. Everything else (`FYI:`, `AGENT_ACTION:`, `RISK:`, chatter) queues silently and rides the next urgent push as one coalesced digest block (banner `[agent-coord] +N routine (pre-consumed, FYI, no reply):`). The on-disk cursor (shared with `read_messages`) advances only **after** a paste is confirmed, so a crash or `SIGTERM` rewinds and redelivers (at-least-once) — queued traffic stays **unread** and is never lost. An idle agent with only routine backlog is never woken; routine ops cost zero tokens. `AGENT_COORD_TIERS=0` restores legacy push-everything. The classifier and queue are pure ([`hooks/tier.mjs`](./hooks/tier.mjs)). Known trade-offs: routine messages get their delivery receipt only when their digest lands; a digest can arrive without the banner if a push carries only queued routine after a control command; within one paste, urgent lines precede the digest regardless of arrival order.
297
297
 
298
298
  **Caveats — read these.**
299
299
 
package/dist/server.js CHANGED
File without changes
package/hooks/tier.mjs CHANGED
@@ -68,25 +68,33 @@ export class TierQueue {
68
68
  }
69
69
  }
70
70
 
71
+ // The per-message PARSE CONTRACT line. Agent harnesses read from/room/text
72
+ // back out of this, so its shape is load-bearing and MUST stay byte-identical
73
+ // to coord-pusher.mjs's `injectLine`. Compact form (v0.14.0, salvaged from
74
+ // v0.8.10): ` [<kind> <HH:MM> <from>] <text>` where kind drops the leading
75
+ // "room " ("room #general" → "#general"), the timestamp is HH:MM UTC, and the
76
+ // "from=" label is dropped (bare id). kind/time/from never contain spaces
77
+ // (ids are sanitized), so a parser splits on the first "] " unambiguously.
78
+ export function injectLine(m) {
79
+ const tag = String(m.kind ?? "").replace(/^room /, "");
80
+ const d = new Date(m.ts ?? 0);
81
+ const hhmm = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`;
82
+ return ` [${tag} ${hhmm} ${m.from}] ${m.text ?? ""}`;
83
+ }
84
+
71
85
  // Render one delivery: urgent verbatim under the banner, then at most ONE
72
86
  // coalesced digest block carrying the queued routine messages.
73
87
  export function formatBatch(batch) {
74
- const line = (m) =>
75
- ` [${m.kind} ${new Date(m.ts ?? 0).toISOString()} from=${m.from}] ${m.text ?? ""}`;
76
88
  const urgent = batch.filter((m) => m.tier !== "routine");
77
89
  const routine = batch.filter((m) => m.tier === "routine");
78
90
  const lines = [];
79
91
  if (urgent.length > 0) {
80
- lines.push(
81
- "[agent-coord] incoming peer messages already consumed from your inbox, do not call read_messages for them:",
82
- );
83
- for (const m of urgent) lines.push(line(m));
92
+ lines.push("[agent-coord] msgs (pre-consumed, don't re-read):");
93
+ for (const m of urgent) lines.push(injectLine(m));
84
94
  }
85
95
  if (routine.length > 0) {
86
- lines.push(
87
- `[agent-coord] digest ${routine.length} routine message${routine.length === 1 ? "" : "s"} coalesced with this push (also consumed; FYI only, no reply expected):`,
88
- );
89
- for (const m of routine) lines.push(line(m));
96
+ lines.push(`[agent-coord] +${routine.length} routine (pre-consumed, FYI, no reply):`);
97
+ for (const m of routine) lines.push(injectLine(m));
90
98
  }
91
99
  return lines.join("\n");
92
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-coord-mcp",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "File-backed MCP server for coordinating multiple AI coding agents (Claude Code, Cursor, Cline, etc.). Local stdio or networked over Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -168,13 +168,23 @@ async function flush() {
168
168
  }
169
169
  }
170
170
 
171
+ // The per-message PARSE CONTRACT line — MUST stay byte-identical to
172
+ // hooks/tier.mjs's injectLine (agent harnesses parse from/room/text out of
173
+ // it). Compact form (v0.14.0): ` [<kind> <HH:MM> <from>] <text>`, kind drops
174
+ // the leading "room ", timestamp is HH:MM UTC, no "from=" label. This pusher
175
+ // is standalone (may deploy without hooks/), so the helper is duplicated
176
+ // rather than imported; test/tier.test.mjs locks both to the same shape.
177
+ function injectLine(m) {
178
+ const tag = String(m.kind ?? "").replace(/^room /, "");
179
+ const d = new Date(m.ts ?? 0);
180
+ const hhmm = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`;
181
+ return ` [${tag} ${hhmm} ${m.from}] ${m.text ?? ""}`;
182
+ }
183
+
171
184
  function formatBatch(batch) {
172
- const lines = [
173
- "[agent-coord] incoming peer messages — already consumed from your inbox, do not call read_messages for them:",
174
- ];
185
+ const lines = ["[agent-coord] msgs (pre-consumed, don't re-read):"];
175
186
  for (const m of batch) {
176
- const ts = new Date(m.ts ?? Date.now()).toISOString();
177
- lines.push(` [${m.kind} ${ts} from=${m.from}] ${m.text ?? ""}`);
187
+ lines.push(injectLine(m));
178
188
  }
179
189
  return lines.join("\n");
180
190
  }