multi-agent-collaboration-mcp 0.12.0 → 0.13.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/dist/check.js CHANGED
@@ -8,12 +8,11 @@
8
8
  // Diagnostic one-shot. The background watcher is src/poller.ts and keeps one
9
9
  // connection instead of launching this process on every interval.
10
10
  //
11
- // The bare --agent baseline is the IDENTITY-level marker
12
- // (memberships.last_read_seq, the MAX across that identity's sessions), which
13
- // can hide a lagging private session's backlog. --session (the process nonce
14
- // the server bakes into poller_cmd) fixes that for both all-rooms and scoped
15
- // watches: each room baselines off that session's OWN private cursor where one
16
- // exists.
11
+ // Baselines are the persona's read markers (memberships.last_read_seq). This
12
+ // probe is deliberately NOT epoch-bound: it is a one-shot diagnostic answering
13
+ // "does this persona have unread work", a question that stays meaningful
14
+ // regardless of which runtime currently holds the persona. The watcher that
15
+ // speaks FOR a runtime is poller.ts, and that one is bound.
17
16
  import Database from "better-sqlite3";
18
17
  import { existsSync, writeFileSync } from "node:fs";
19
18
  import { homedir } from "node:os";
@@ -27,14 +26,12 @@ function fail(msg) {
27
26
  }
28
27
  const USAGE = `agent-chat-check: one-shot, read-only unread probe.
29
28
  Usage:
30
- check.js --agent <agent_id> [--session <nonce>] [--mentions-only] # all rooms
31
- check.js --room <id|name> --agent <agent_id> [--session <nonce>] [--since <seq>]
29
+ check.js --agent <agent_id> [--mentions-only] # all rooms
30
+ check.js --room <id|name> --agent <agent_id> [--since <seq>]
32
31
  Flags:
33
- --agent <id> identity to check; baselines are its read markers
32
+ --agent <id> persona to check; baselines are its read markers
34
33
  --room <id|name> scope to one room (default: every room the agent is in)
35
34
  --since <seq> explicit baseline instead of the read marker (needs --room)
36
- --session <nonce> baseline off this session's private cursor where it exists;
37
- all-rooms mode also mutes rooms that session left
38
35
  --mentions-only count only messages that mention --agent or reply to it
39
36
  --help, -h print this and exit 0
40
37
  Exit codes: 0 = updates exist (JSON status on stdout; rooms_with_updates names
@@ -42,10 +39,9 @@ up to 20 firing rooms on the all-rooms path and
42
39
  rooms_with_updates_truncated:true means more fired), 1 = nothing new, 2 = error.
43
40
  `;
44
41
  function parseArgs(argv) {
45
- const out = { mentionsOnly: false, wakeOnly: false };
42
+ const out = { mentionsOnly: false };
46
43
  for (let i = 0; i < argv.length; i++) {
47
- // Accept both `--flag value` and `--flag=value` (the wrapper script
48
- // accepts the = form for its own flags, so the probe must not reject it).
44
+ // Accept both `--flag value` and `--flag=value`.
49
45
  let a = argv[i];
50
46
  let inline;
51
47
  if (a.startsWith("--")) {
@@ -72,14 +68,6 @@ function parseArgs(argv) {
72
68
  fail("--mentions-only takes no value");
73
69
  out.mentionsOnly = true;
74
70
  }
75
- else if (a === "--wake-only") {
76
- // Internal poller mode: on a quiet cycle, answer via indexed EXISTS and
77
- // skip exact counts that no caller will see. Kept out of --help because
78
- // direct one-shot users should retain the full status object.
79
- if (inline !== undefined)
80
- fail("--wake-only takes no value");
81
- out.wakeOnly = true;
82
- }
83
71
  else if (a === "--room") {
84
72
  out.room = take(a);
85
73
  }
@@ -102,19 +90,6 @@ function parseArgs(argv) {
102
90
  else if (a === "--db") {
103
91
  out.db = take(a);
104
92
  }
105
- else if (a === "--session") {
106
- // A process nonce that makes the watch session-aware. In all-rooms mode,
107
- // rooms the owning session soft-left (a session_presence row with
108
- // left_at set) are excluded, matching my_mentions, and each room
109
- // baselines off that session's OWN private cursor where one exists
110
- // (session_markers is keyed by the same nonce). Without the cursor
111
- // baseline a private session whose twin read ahead was never woken: the
112
- // identity marker is the MAX across sessions, so its own unread was
113
- // invisible here while its catch_up still had messages. Scoped mode uses
114
- // the same cursor baseline but deliberately remains readable after a
115
- // soft leave, matching catch_up({room}) and poller.ts.
116
- out.session = take(a);
117
- }
118
93
  else if (a === "--help" || a === "-h") {
119
94
  writeFileSync(1, USAGE);
120
95
  process.exit(0);
@@ -174,77 +149,32 @@ try {
174
149
  fail("--agent is required when watching all rooms");
175
150
  const counts = db
176
151
  .transaction(() => {
177
- // Session-aware, only when --session is given: exclude rooms this
178
- // session soft-left (parity with my_mentions), and baseline each room
179
- // off this session's OWN private cursor where one exists (COALESCE to
180
- // the identity marker; parity with the session's catch_up).
181
- const sess = args.session;
182
- const smJoin = sess
183
- ? ` LEFT JOIN session_markers sm ON sm.room_id = mb.room_id
184
- AND sm.agent_id = mb.agent_id AND sm.session_id = ?`
185
- : "";
186
- const baseline = sess
187
- ? "COALESCE(sm.last_read_seq, mb.last_read_seq)"
188
- : "mb.last_read_seq";
189
- const sessClause = sess
190
- ? ` AND NOT EXISTS (SELECT 1 FROM session_presence sp
191
- WHERE sp.room_id = mb.room_id AND sp.agent_id = mb.agent_id
192
- AND sp.session_id = ? AND sp.left_at IS NOT NULL)`
193
- : "";
194
- // Rooms count stays IDENTITY-level: it only distinguishes a doomed
195
- // watch (identity in no room -> fail) from a live one. A session that
196
- // left all its rooms while a twin keeps the identity present is NOT
197
- // doomed -- its session-filtered unread below is simply 0 (exit 1, no
198
- // updates), never an error.
152
+ // Rooms count distinguishes a doomed watch (persona in no room -> fail)
153
+ // from a live one.
199
154
  const { n: rooms } = db
200
155
  .prepare("SELECT COUNT(*) AS n FROM memberships WHERE agent_id = ? AND left_at IS NULL")
201
156
  .get(agent);
202
157
  if (rooms === 0)
203
158
  return null;
204
- if (args.wakeOnly) {
205
- const candidateClause = args.mentionsOnly
206
- ? ` AND (g.mentions IS NOT NULL OR g.reply_to_agent IS NOT NULL)
207
- AND ${directedAt("g")}`
208
- : "";
209
- const hit = db
210
- .prepare(`SELECT 1 FROM messages g
211
- JOIN memberships mb ON mb.room_id = g.room_id
212
- AND mb.agent_id = ? AND mb.left_at IS NULL${smJoin}
213
- WHERE g.seq > ${baseline} AND g.agent_id != ?
214
- ${candidateClause}${sessClause}
215
- LIMIT 1`)
216
- .get(...(sess
217
- ? args.mentionsOnly
218
- ? [agent, sess, agent, agent, agent, sess]
219
- : [agent, sess, agent, sess]
220
- : args.mentionsOnly
221
- ? [agent, agent, agent, agent]
222
- : [agent, agent]));
223
- if (!hit)
224
- return { rooms, wakeOnlyQuiet: true };
225
- }
226
159
  const { c: unread } = db
227
160
  .prepare(`SELECT COUNT(*) AS c FROM messages g
228
161
  JOIN memberships mb ON mb.room_id = g.room_id
229
- AND mb.agent_id = ? AND mb.left_at IS NULL${smJoin}
230
- WHERE g.seq > ${baseline} AND g.agent_id != ?${sessClause}`)
231
- .get(...(sess ? [agent, sess, agent, sess] : [agent, agent]));
162
+ AND mb.agent_id = ? AND mb.left_at IS NULL
163
+ WHERE g.seq > mb.last_read_seq AND g.agent_id != ?`)
164
+ .get(agent, agent);
232
165
  const { c: unreadMentions } = db
233
166
  .prepare(`SELECT COUNT(*) AS c FROM messages g
234
167
  JOIN memberships mb ON mb.room_id = g.room_id
235
- AND mb.agent_id = ? AND mb.left_at IS NULL${smJoin}
236
- WHERE g.seq > ${baseline} AND g.agent_id != ?
168
+ AND mb.agent_id = ? AND mb.left_at IS NULL
169
+ WHERE g.seq > mb.last_read_seq AND g.agent_id != ?
237
170
  AND (g.mentions IS NOT NULL OR g.reply_to_agent IS NOT NULL)
238
- AND ${directedAt("g")}${sessClause}`)
239
- .get(...(sess
240
- ? [agent, sess, agent, agent, agent, sess]
241
- : [agent, agent, agent, agent]));
171
+ AND ${directedAt("g")}`)
172
+ .get(agent, agent, agent, agent);
242
173
  // Which rooms fired: same baseline/muting as the counts, read in the
243
174
  // same snapshot. Gated behind an actual wake (quiet interval polls,
244
175
  // the overwhelmingly common case, must not pay for the GROUP BY).
245
176
  // Placeholders in SQL text order: the directedAt pair (SELECT), the
246
- // membership join, the session key (if any), the author exclusion,
247
- // the presence key (if any).
177
+ // membership join, then the author exclusion.
248
178
  let roomsWithUpdates = null;
249
179
  let roomsWithUpdatesTruncated = false;
250
180
  if (args.mentionsOnly ? unreadMentions > 0 : unread > 0) {
@@ -259,16 +189,14 @@ try {
259
189
  SUM(CASE WHEN ${directedAt("g")} THEN 1 ELSE 0 END) AS directed
260
190
  FROM messages g
261
191
  JOIN memberships mb ON mb.room_id = g.room_id
262
- AND mb.agent_id = ? AND mb.left_at IS NULL${smJoin}
192
+ AND mb.agent_id = ? AND mb.left_at IS NULL
263
193
  JOIN rooms r ON r.id = g.room_id
264
- WHERE g.seq > ${baseline} AND g.agent_id != ?${candidateClause}${sessClause}
194
+ WHERE g.seq > mb.last_read_seq AND g.agent_id != ?${candidateClause}
265
195
  GROUP BY g.room_id, r.name
266
196
  ${mentionsHaving}
267
197
  ORDER BY directed DESC, unread DESC, g.room_id ASC
268
198
  LIMIT 21`)
269
- .all(...(sess
270
- ? [agent, agent, agent, sess, agent, sess]
271
- : [agent, agent, agent, agent]));
199
+ .all(agent, agent, agent, agent);
272
200
  // The summary drives which rooms callers read next. Silently
273
201
  // dropping its tail made firing rooms look quiet; in mentions-only
274
202
  // mode, broadcast-only groups did not fire and must not appear.
@@ -288,18 +216,6 @@ try {
288
216
  .deferred();
289
217
  if (counts === null)
290
218
  fail(`agent "${agent}" is not a member of any room`);
291
- if ("wakeOnlyQuiet" in counts && counts.wakeOnlyQuiet) {
292
- db.close();
293
- writeFileSync(1, JSON.stringify({
294
- agent,
295
- rooms: counts.rooms,
296
- ...(args.mentionsOnly ? { unread_count_skipped: true } : { unread: 0 }),
297
- unread_mentions: 0,
298
- mentions_only: args.mentionsOnly,
299
- has_updates: false,
300
- }) + "\n");
301
- process.exit(1);
302
- }
303
219
  const { rooms, unread, unreadMentions, roomsWithUpdates, roomsWithUpdatesTruncated, } = counts;
304
220
  db.close();
305
221
  const hasUpdates = args.mentionsOnly ? unreadMentions > 0 : unread > 0;
@@ -343,32 +259,14 @@ try {
343
259
  }
344
260
  else {
345
261
  const m = db
346
- .prepare(`SELECT COALESCE(sm.last_read_seq, mb.last_read_seq) AS last_read_seq
262
+ .prepare(`SELECT mb.last_read_seq AS last_read_seq
347
263
  FROM memberships mb
348
- LEFT JOIN session_markers sm ON sm.room_id = mb.room_id
349
- AND sm.agent_id = mb.agent_id AND sm.session_id = ?
350
264
  WHERE mb.room_id = ? AND mb.agent_id = ?`)
351
- .get(args.session ?? "", roomId, args.agent);
265
+ .get(roomId, args.agent);
352
266
  if (!m)
353
267
  return null;
354
268
  baseline = m.last_read_seq;
355
269
  }
356
- if (args.wakeOnly) {
357
- const authorClause = args.agent ? " AND agent_id != ?" : "";
358
- const directedClause = args.mentionsOnly
359
- ? ` AND (mentions IS NOT NULL OR reply_to_agent IS NOT NULL)
360
- AND ${directedAt("messages")}`
361
- : "";
362
- const hit = db
363
- .prepare(`SELECT 1 FROM messages
364
- WHERE room_id = ? AND seq > ?${authorClause}${directedClause}
365
- LIMIT 1`)
366
- .get(roomId, baseline, ...(args.agent ? [args.agent] : []), ...(args.mentionsOnly && args.agent
367
- ? [args.agent, args.agent]
368
- : []));
369
- if (!hit)
370
- return { baseline, wakeOnlyQuiet: true };
371
- }
372
270
  // Exclude the agent's own messages: posting should not make you "have updates".
373
271
  const unread = (args.agent
374
272
  ? db
@@ -395,19 +293,6 @@ try {
395
293
  if (snap === null) {
396
294
  fail(`agent "${args.agent}" is not a member of room ${roomId}; join first or pass --since`);
397
295
  }
398
- if ("wakeOnlyQuiet" in snap && snap.wakeOnlyQuiet) {
399
- db.close();
400
- writeFileSync(1, JSON.stringify({
401
- room_id: roomId,
402
- agent: args.agent ?? null,
403
- baseline_seq: snap.baseline,
404
- ...(args.mentionsOnly ? { unread_count_skipped: true } : { unread: 0 }),
405
- unread_mentions: 0,
406
- mentions_only: args.mentionsOnly,
407
- has_updates: false,
408
- }) + "\n");
409
- process.exit(1);
410
- }
411
296
  const { baseline, unread, unreadMentions, latest } = snap;
412
297
  db.close();
413
298
  const hasUpdates = args.mentionsOnly ? unreadMentions > 0 : unread > 0;