cross-agent-teams-mcp 0.5.1 → 0.5.3

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/cli.js CHANGED
@@ -195,10 +195,18 @@ function migrateMessagesNeedReplyColumn(db) {
195
195
  if (existing.has("need_reply")) return;
196
196
  db.exec(`ALTER TABLE messages ADD COLUMN need_reply INTEGER NOT NULL DEFAULT 1`);
197
197
  }
198
+ function migrateAgentsCursorWatermark(db) {
199
+ db.exec(
200
+ `UPDATE agents
201
+ SET last_processed_event_id = COALESCE((SELECT MAX(event_id) FROM events), 0)
202
+ WHERE last_processed_event_id = 0`
203
+ );
204
+ }
198
205
  function applySchema(db) {
199
206
  for (const sql of DDL) db.exec(sql);
200
207
  migrateAgentsDeliveryColumns(db);
201
208
  migrateMessagesNeedReplyColumn(db);
209
+ migrateAgentsCursorWatermark(db);
202
210
  }
203
211
 
204
212
  // src/daemon/auth.ts
@@ -425,9 +433,11 @@ var AgentsRepo = class {
425
433
  this.db.prepare(
426
434
  `INSERT INTO agents (
427
435
  agent_id, agent_type, agent_type_name, team, role, name, model, registered_at, last_seen_at,
428
- tmux_pane_id, claude_ui_pid, runtime_ui_pid, delivery_kind, delivery_payload
436
+ tmux_pane_id, claude_ui_pid, runtime_ui_pid, delivery_kind, delivery_payload,
437
+ last_processed_event_id
429
438
  )
430
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
439
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
440
+ COALESCE((SELECT MAX(event_id) FROM events), 0))
431
441
  ON CONFLICT (team, name) DO UPDATE SET
432
442
  agent_type = excluded.agent_type,
433
443
  agent_type_name = excluded.agent_type_name,
@@ -1364,27 +1374,40 @@ var GetInboxService = class {
1364
1374
  const caller = this.agents.findById(args.caller);
1365
1375
  if (!caller) return { messages: [], has_more: false, last_event_id: args.since_event_id ?? 0 };
1366
1376
  const callerTeam = caller.team;
1367
- const callerRole = this.db.prepare("SELECT role FROM agents WHERE agent_id=?").get(args.caller);
1377
+ const callerRoleRow = this.db.prepare("SELECT role, last_processed_event_id FROM agents WHERE agent_id=?").get(args.caller);
1378
+ const callerRole = callerRoleRow?.role;
1379
+ const storedCursor = callerRoleRow?.last_processed_event_id ?? 0;
1368
1380
  const limit = Math.min(args.limit ?? 50, 200);
1369
- const since = args.since_event_id ?? 0;
1370
- const rows = this.db.prepare(
1371
- `SELECT m.id, m.event_id, m.from_team, m.to_team, m.from_agent_id, m.to_agent_id, m.to_role, m.subject, m.body, m.need_reply, m.sent_at,
1372
- a.role as from_role
1373
- FROM messages m
1374
- LEFT JOIN agents a ON a.agent_id = m.from_agent_id
1375
- WHERE m.to_team = ?
1376
- AND m.event_id > ?
1377
- AND ( m.to_agent_id = ? OR (m.to_role IS NOT NULL AND m.to_role = ?) )
1378
- ORDER BY m.event_id ASC
1379
- LIMIT ?`
1380
- ).all(callerTeam, since, args.caller, callerRole?.role ?? "__none__", limit + 1);
1381
- const has_more = rows.length > limit;
1382
- const trimmed = (has_more ? rows.slice(0, limit) : rows).map((row) => ({
1383
- ...row,
1384
- need_reply: row.need_reply === 1
1385
- }));
1386
- const last_event_id = trimmed.length > 0 ? trimmed[trimmed.length - 1].event_id : since;
1387
- return { messages: trimmed, has_more, last_event_id };
1381
+ const implicit = args.since_event_id === void 0;
1382
+ const effectiveSince = implicit ? storedCursor : args.since_event_id;
1383
+ const tx = this.db.transaction(() => {
1384
+ const rows = this.db.prepare(
1385
+ `SELECT m.id, m.event_id, m.from_team, m.to_team, m.from_agent_id, m.to_agent_id, m.to_role, m.subject, m.body, m.need_reply, m.sent_at,
1386
+ a.role as from_role
1387
+ FROM messages m
1388
+ LEFT JOIN agents a ON a.agent_id = m.from_agent_id
1389
+ WHERE m.to_team = ?
1390
+ AND m.event_id > ?
1391
+ AND ( m.to_agent_id = ? OR (m.to_role IS NOT NULL AND m.to_role = ?) )
1392
+ ORDER BY m.event_id ASC
1393
+ LIMIT ?`
1394
+ ).all(callerTeam, effectiveSince, args.caller, callerRole ?? "__none__", limit + 1);
1395
+ const has_more = rows.length > limit;
1396
+ const trimmed = (has_more ? rows.slice(0, limit) : rows).map((row) => ({
1397
+ ...row,
1398
+ need_reply: row.need_reply === 1
1399
+ }));
1400
+ const last_event_id = trimmed.length > 0 ? trimmed[trimmed.length - 1].event_id : effectiveSince;
1401
+ if (implicit && last_event_id > storedCursor) {
1402
+ this.db.prepare(
1403
+ `UPDATE agents
1404
+ SET last_processed_event_id = ?
1405
+ WHERE agent_id = ? AND last_processed_event_id < ?`
1406
+ ).run(last_event_id, args.caller, last_event_id);
1407
+ }
1408
+ return { messages: trimmed, has_more, last_event_id };
1409
+ });
1410
+ return tx();
1388
1411
  }
1389
1412
  };
1390
1413
 
@@ -3701,7 +3724,13 @@ function registerBusinessTools(server, db, getCallerAgentId, fanout, onRegisterS
3701
3724
  "get_inbox",
3702
3725
  {
3703
3726
  title: "Get inbox",
3704
- description: "Return messages addressed to caller after since_event_id",
3727
+ description: [
3728
+ "Return messages addressed to the caller (by agent_id or matching role) within the caller team.",
3729
+ "Default behaviour (since_event_id omitted): the daemon reads the caller's server-side cursor (`agents.last_processed_event_id`), returns mail past it, and ADVANCES the cursor to the highest returned event_id in the same transaction. Subsequent default calls return only newer mail.",
3730
+ "Pagination via `limit` advances the cursor only to the last RETURNED event_id; the next default call resumes from there.",
3731
+ "Explicit `since_event_id` (any number, including 0) is read-only inspection: the daemon uses the supplied value as the lower bound and does NOT advance the stored cursor \u2014 useful for re-reading history or debugging without disturbing live read position.",
3732
+ "Retention: messages older than 30 days are deleted by the cleanup routine regardless of read state. Agents that go offline for more than 30 days forfeit any unread mail in that window."
3733
+ ].join(" "),
3705
3734
  inputSchema: {
3706
3735
  since_event_id: z3.number().int().optional(),
3707
3736
  limit: z3.number().int().optional()
@@ -4196,14 +4225,10 @@ function mountMcp(app, db, fanout, channelWakeFanout, opts = {}) {
4196
4225
  return reply;
4197
4226
  });
4198
4227
  function reapOrphanSessions(now, graceMs = 6e4) {
4199
- for (const [sid, session] of sessions) {
4228
+ for (const session of sessions.values()) {
4200
4229
  if (session.agentIdHolder.current !== void 0) continue;
4201
4230
  const ageMs = now - session.createdAt;
4202
4231
  if (ageMs < graceMs) continue;
4203
- try {
4204
- log(`mcp orphan session reap: sid=${sid} age_s=${Math.round(ageMs / 1e3)}`);
4205
- } catch {
4206
- }
4207
4232
  try {
4208
4233
  void session.transport.close();
4209
4234
  } catch {
@@ -4214,30 +4239,23 @@ function mountMcp(app, db, fanout, channelWakeFanout, opts = {}) {
4214
4239
  }
4215
4240
 
4216
4241
  // src/daemon/cleanup.ts
4217
- var DELETE_AGED_EVENTS_SQL = `
4218
- WITH online_cursor AS (
4219
- SELECT team AS to_team, MIN(last_processed_event_id) AS min_cursor
4220
- FROM agents
4221
- WHERE last_seen_at >= :cutoffOnline
4222
- GROUP BY team
4223
- )
4224
- DELETE FROM events
4225
- WHERE created_at < :ageCutoff
4226
- AND (
4227
- events.to_team NOT IN (SELECT to_team FROM online_cursor)
4228
- OR events.event_id < (
4229
- SELECT min_cursor FROM online_cursor WHERE online_cursor.to_team = events.to_team
4230
- )
4231
- )
4232
- `;
4233
4242
  function runCleanup(db, opts = {}) {
4234
4243
  const now = opts.now ?? /* @__PURE__ */ new Date();
4235
- const maxAgeDays = opts.maxAgeDays ?? 7;
4236
- const onlineWindowMs = opts.onlineWindowMs ?? 5 * 60 * 1e3;
4244
+ const maxAgeDays = opts.maxAgeDays ?? 30;
4237
4245
  const ageCutoff = new Date(now.getTime() - maxAgeDays * 86400 * 1e3).toISOString();
4238
- const cutoffOnline = new Date(now.getTime() - onlineWindowMs).toISOString();
4239
- const info = db.prepare(DELETE_AGED_EVENTS_SQL).run({ ageCutoff, cutoffOnline });
4240
- return { deleted: Number(info.changes) };
4246
+ const deleteStatus = db.prepare(
4247
+ `DELETE FROM message_delivery_status
4248
+ WHERE message_id IN (SELECT id FROM messages WHERE sent_at < ?)`
4249
+ );
4250
+ const deleteMessages = db.prepare(`DELETE FROM messages WHERE sent_at < ?`);
4251
+ const deleteEvents = db.prepare(`DELETE FROM events WHERE created_at < ?`);
4252
+ const tx = db.transaction(() => {
4253
+ const s = deleteStatus.run(ageCutoff);
4254
+ const m = deleteMessages.run(ageCutoff);
4255
+ const e = deleteEvents.run(ageCutoff);
4256
+ return Number(s.changes) + Number(m.changes) + Number(e.changes);
4257
+ });
4258
+ return { deleted: tx() };
4241
4259
  }
4242
4260
 
4243
4261
  // src/daemon/sse-fanout.ts