agent-comms 1.25.4 → 1.25.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.
@@ -15,7 +15,7 @@
15
15
  "url": "https://github.com/ExaDev/agent-comms.git"
16
16
  },
17
17
  "description": "Cross-harness LLM agent communication mesh — rooms, DMs, and presence",
18
- "version": "1.25.4"
18
+ "version": "1.25.6"
19
19
  }
20
20
  ]
21
21
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-comms",
3
3
  "description": "Cross-harness LLM agent communication mesh — rooms, DMs, and presence",
4
- "version": "1.25.4",
4
+ "version": "1.25.6",
5
5
  "author": {
6
6
  "name": "ExaDev"
7
7
  },
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github&logoColor=white)](https://github.com/ExaDev/agent-comms)
4
4
  [![npm](https://img.shields.io/badge/npm-CB3837?logo=npm&logoColor=white)](https://www.npmjs.com/package/agent-comms)
5
- [![version](https://img.shields.io/badge/version-1.25.4-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v1.25.4)
5
+ [![version](https://img.shields.io/badge/version-1.25.6-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v1.25.6)
6
6
  [![CI](https://img.shields.io/github/actions/workflow/status/ExaDev/agent-comms/ci.yml?branch=main)](https://github.com/ExaDev/agent-comms/actions)
7
7
 
8
8
  Cross-harness communication mesh for LLM agents: rooms, DMs, presence, and visibility over TCP with zero filesystem dependencies.
@@ -34,7 +34,7 @@ graph LR
34
34
  B_Bridge -- "channel notification" --> B_LLM
35
35
  ```
36
36
 
37
- All state is held in memory and synchronised between peers. Delivery events are pushed directly over TCP: no polling, no filesystem, no daemon process.
37
+ All state is held in memory and synchronised between peers. Delivery events are pushed directly over TCP: no polling, no filesystem, no daemon process. Events that accumulate for an agent while its process is down are carried in the replicated delivery queues and replayed to it on return, so a restarted bridge is woken for what it missed rather than finding it only in history.
38
38
 
39
39
  ### Coordinator pattern
40
40
 
@@ -55,6 +55,15 @@ export declare class MeshStore implements CommsStore {
55
55
  private handlePeerJoined;
56
56
  private handleIntroduction;
57
57
  private handlePeerConnected;
58
+ /**
59
+ * Merge a peer's state snapshot into the local state. Agents and rooms
60
+ * accept the incoming copy when it carries a revision at least as high as
61
+ * the local one, so a peer holding stale entities converges when it
62
+ * receives a fresher snapshot, while its own stale copies are rejected by
63
+ * peers that stayed current (#27). Message and DM histories are
64
+ * append-only: add unseen entries and union read receipts.
65
+ */
66
+ applyStateSync(state: SerialisedState): void;
58
67
  private handleDataMessage;
59
68
  private handleBecomeCoordinator;
60
69
  private handlePeerDisconnected;
@@ -80,9 +89,22 @@ export declare class MeshStore implements CommsStore {
80
89
  startDataServerOnly(): Promise<void>;
81
90
  /** Returns the TransportEvents object that bridges should pass to the transport constructor. */
82
91
  get events(): TransportEvents;
92
+ /** Append to a target agent's delivery queue, bounded oldest-first (#28). */
93
+ private queueDelivery;
94
+ /** Bump an entity's sync revision; call before broadcasting a local mutation. */
95
+ private bump;
83
96
  private applyPatch;
84
97
  private broadcastPatch;
85
98
  private deliverLocallyAndBroadcast;
99
+ /**
100
+ * Fire onDelivery for an event targeting this peer's own agent, deduped
101
+ * against events already delivered in this process. Used both for live
102
+ * deliveries and for replays of events that accumulated while this
103
+ * process was down (#28): the dedup set is per-process, so a replayed
104
+ * event this process never saw fires, and one it already handled does
105
+ * not.
106
+ */
107
+ private fireLocalDelivery;
86
108
  private deliverToRoom;
87
109
  private notifyRoomsOfStatus;
88
110
  private notifyRoomsOfNameChange;
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAe,cAAc,EAAY,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAEV,aAAa,EACb,eAAe,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EACV,aAAa,EAEb,aAAa,EAEb,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,gBAAgB,CAAC;AAanE,qBAAa,SAAU,YAAW,UAAU;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,eAAe,CAA6C;IACpE,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,qBAAqB,CAAuC;IAEpE,0DAA0D;IAC1D,IAAI,SAAS,IAAI,OAAO,CAIvB;IAGD,OAAO,CAAC,yBAAyB,CAG7B;IAEJ,UAAU,EACN,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACjE,SAAS,CAAC;IAEd,uEAAuE;IACvE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,IAAI,eAAe;IAS5B,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,iBAAiB,CAAqB;IAE9C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,UAAU,EAAE,iBAAiB,CAAC;gBAElB,eAAe,GAAE,MAAiC;IAiC9D,gFAAgF;IAChF,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IAQtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiE3B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,gBAAgB;YAKV,kBAAkB;YA0BlB,mBAAmB;YAmBnB,iBAAiB;YA6BjB,uBAAuB;IAcrC,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,uBAAuB;IAkC/B,2CAA2C;IACrC,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D,2CAA2C;IACrC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3E,8DAA8D;IAC9D,sBAAsB,IAAI;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;KACrB,EAAE;IAOH;;4EAEwE;IACxE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB1D;kFAC8E;IACxE,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAc1C,gGAAgG;IAChG,IAAI,MAAM,IAAI,eAAe,CA2B5B;YAMa,UAAU;YAsGV,cAAc;YAOd,0BAA0B;YAoD1B,aAAa;YAab,mBAAmB;YAgBnB,uBAAuB;YAoBvB,kBAAkB;IAkBhC,OAAO,CAAC,iBAAiB;YAoBX,QAAQ;IAkCtB,OAAO,CAAC,gBAAgB;IA2BlB,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAKhC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE,aAAa,CAAC,IAAI,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;QACvB,IAAI,EAAE,MAAM,EAAE,CAAC;KAChB,GAAG,OAAO,CAAC,aAAa,CAAC;IAoCpB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAKxD,WAAW,CACf,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CACZ,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,CACvE,GACA,OAAO,CAAC,aAAa,CAAC;IAsBnB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAUzD,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB1C,UAAU,CAAC,IAAI,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,QAAQ,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBX,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAK9C,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAW/C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoExD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCzD,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAwBV,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAyBV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAaV,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B3D,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,WAAW,CAAC;IAyCjB,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,EAAE,CAAC;IAWnB,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,SAAS,CAAC;IAkCf,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAqB9D,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,cAAc;YAOR,gBAAgB;IAsC9B,OAAO,CAAC,cAAc;IAatB,qEAAqE;IAC/D,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,6CAA6C;IAC7C,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,cAAc;IAQzC,WAAW,CACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC;IAYZ,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,aAAa,IAAI,YAAY,EAAE;IAI/B,oBAAoB,IAAI,gBAAgB,EAAE;IAqBpC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,QAAQ,IAAI,OAAO,EAAE;YAQP,qBAAqB;YAYrB,kBAAkB;YAalB,oBAAoB;YAqBpB,iBAAiB;YA6BjB,kBAAkB;IAmBhC,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,iCAAiC;IAgBnC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
1
+ {"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,32 @@ import { FederationManager } from "./federation.js";
24
24
  // ---------------------------------------------------------------------------
25
25
  const DEFAULT_COORDINATOR_PORT = 19876;
26
26
  const COORDINATOR_HOST = "127.0.0.1";
27
+ /**
28
+ * Bound on pending delivery events held per target agent. Events beyond the
29
+ * bound drop oldest-first: a long-offline agent's queue cannot grow without
30
+ * limit in memory or in synced snapshots (#28).
31
+ */
32
+ const MAX_QUEUED_DELIVERIES_PER_AGENT = 100;
33
+ /**
34
+ * Merge an incoming append-only message history into the local one: add
35
+ * entries the local list does not have and union read receipts on the ones
36
+ * it does. Local ordering is preserved; unseen entries are appended.
37
+ */
38
+ function mergeMessageHistories(local, incoming) {
39
+ const byId = new Map(local.map((m) => [m.id, m]));
40
+ for (const msg of incoming) {
41
+ const existing = byId.get(msg.id);
42
+ if (existing === undefined) {
43
+ local.push(msg);
44
+ byId.set(msg.id, msg);
45
+ continue;
46
+ }
47
+ for (const reader of msg.readBy) {
48
+ if (!existing.readBy.includes(reader))
49
+ existing.readBy.push(reader);
50
+ }
51
+ }
52
+ }
27
53
  // ---------------------------------------------------------------------------
28
54
  // MeshStore
29
55
  // ---------------------------------------------------------------------------
@@ -59,6 +85,7 @@ export class MeshStore {
59
85
  rooms: Object.fromEntries(this.rooms),
60
86
  messages: Object.fromEntries(this.messages),
61
87
  dms: Object.fromEntries(this.dms),
88
+ deliveryQueues: Object.fromEntries(this.deliveryQueues),
62
89
  };
63
90
  }
64
91
  lastLocalDeliveryKey;
@@ -175,44 +202,94 @@ export class MeshStore {
175
202
  async handlePeerConnected(handle, _info) {
176
203
  // If we have state and the peer doesn't, send state sync
177
204
  if (this.agents.size > 0) {
178
- const state = {
179
- agents: Object.fromEntries(this.agents),
180
- rooms: Object.fromEntries(this.rooms),
181
- messages: Object.fromEntries(this.messages),
182
- dms: Object.fromEntries(this.dms),
183
- };
205
+ const state = this.serialise();
184
206
  await this.transport.send(handle, {
185
207
  method: "state_sync",
186
208
  state,
187
209
  });
188
210
  }
189
211
  }
190
- async handleDataMessage(handle, msg) {
191
- if (msg.method === "state_sync") {
192
- // Merge don't replace so our own state isn't lost
193
- const incoming = {
194
- agents: new Map(Object.entries(msg.state.agents)),
195
- rooms: new Map(Object.entries(msg.state.rooms)),
196
- messages: new Map(Object.entries(msg.state.messages)),
197
- dms: new Map(Object.entries(msg.state.dms)),
198
- };
212
+ /**
213
+ * Merge a peer's state snapshot into the local state. Agents and rooms
214
+ * accept the incoming copy when it carries a revision at least as high as
215
+ * the local one, so a peer holding stale entities converges when it
216
+ * receives a fresher snapshot, while its own stale copies are rejected by
217
+ * peers that stayed current (#27). Message and DM histories are
218
+ * append-only: add unseen entries and union read receipts.
219
+ */
220
+ applyStateSync(state) {
221
+ const incoming = {
222
+ agents: new Map(Object.entries(state.agents)),
223
+ rooms: new Map(Object.entries(state.rooms)),
224
+ messages: new Map(Object.entries(state.messages)),
225
+ dms: new Map(Object.entries(state.dms)),
226
+ };
227
+ {
199
228
  for (const [id, agent] of incoming.agents) {
200
- if (!this.agents.has(id))
201
- this.agents.set(id, agent);
229
+ const existingVersion = this.agents.get(id)?.version;
230
+ if (existingVersion !== undefined && agent.version < existingVersion) {
231
+ continue;
232
+ }
233
+ if (existingVersion === agent.version) {
234
+ const existing = this.agents.get(id);
235
+ if (existing) {
236
+ for (const r of existing.subscribedRooms) {
237
+ if (!agent.subscribedRooms.includes(r))
238
+ agent.subscribedRooms.push(r);
239
+ }
240
+ }
241
+ }
242
+ this.agents.set(id, agent);
202
243
  }
203
244
  for (const [id, room] of incoming.rooms) {
204
- if (!this.rooms.has(id))
205
- this.rooms.set(id, room);
245
+ const existingVersion = this.rooms.get(id)?.version;
246
+ if (existingVersion !== undefined && room.version < existingVersion) {
247
+ continue;
248
+ }
249
+ if (existingVersion === room.version) {
250
+ const existing = this.rooms.get(id);
251
+ if (existing) {
252
+ for (const m of existing.members) {
253
+ if (!room.members.includes(m))
254
+ room.members.push(m);
255
+ }
256
+ }
257
+ }
258
+ this.rooms.set(id, room);
206
259
  }
207
260
  for (const [id, msgs] of incoming.messages) {
208
- if (!this.messages.has(id))
261
+ const existing = this.messages.get(id);
262
+ if (existing === undefined) {
209
263
  this.messages.set(id, msgs);
264
+ continue;
265
+ }
266
+ mergeMessageHistories(existing, msgs);
210
267
  }
211
268
  for (const [id, dmMsgs] of incoming.dms) {
212
- if (!this.dms.has(id))
269
+ const existing = this.dms.get(id);
270
+ if (existing === undefined) {
213
271
  this.dms.set(id, dmMsgs);
272
+ continue;
273
+ }
274
+ mergeMessageHistories(existing, dmMsgs);
275
+ }
276
+ for (const [agentId, events] of Object.entries(state.deliveryQueues)) {
277
+ const seen = new Set((this.deliveryQueues.get(agentId) ?? []).map((e) => JSON.stringify(e)));
278
+ for (const event of events) {
279
+ if (seen.has(JSON.stringify(event)))
280
+ continue;
281
+ this.queueDelivery(agentId, event);
282
+ // A returning peer replays its own pending queue: events pushed
283
+ // while its process was down fire onDelivery now (#28).
284
+ this.fireLocalDelivery(agentId, event);
285
+ }
214
286
  }
215
287
  }
288
+ }
289
+ async handleDataMessage(handle, msg) {
290
+ if (msg.method === "state_sync") {
291
+ this.applyStateSync(msg.state);
292
+ }
216
293
  else if (msg.method === "state_update") {
217
294
  await this.applyPatch(msg.patch);
218
295
  }
@@ -250,9 +327,7 @@ export class MeshStore {
250
327
  name: request.name,
251
328
  fingerprint: request.fingerprint,
252
329
  };
253
- const arr = this.deliveryQueues.get(this.peerId) ?? [];
254
- arr.push(event);
255
- this.deliveryQueues.set(this.peerId, arr);
330
+ this.queueDelivery(this.peerId, event);
256
331
  if (this.onDelivery) {
257
332
  void this.onDelivery(this.peerId, event);
258
333
  }
@@ -343,25 +418,42 @@ export class MeshStore {
343
418
  },
344
419
  };
345
420
  }
421
+ /** Append to a target agent's delivery queue, bounded oldest-first (#28). */
422
+ queueDelivery(agentId, event) {
423
+ const arr = this.deliveryQueues.get(agentId) ?? [];
424
+ arr.push(event);
425
+ if (arr.length > MAX_QUEUED_DELIVERIES_PER_AGENT) {
426
+ arr.splice(0, arr.length - MAX_QUEUED_DELIVERIES_PER_AGENT);
427
+ }
428
+ this.deliveryQueues.set(agentId, arr);
429
+ }
430
+ /** Bump an entity's sync revision; call before broadcasting a local mutation. */
431
+ bump(entity) {
432
+ entity.version += 1;
433
+ return entity;
434
+ }
346
435
  // -----------------------------------------------------------------------
347
436
  // State patch application
348
437
  // -----------------------------------------------------------------------
349
438
  async applyPatch(patch) {
350
439
  switch (patch.type) {
351
440
  case "agent_upsert": {
352
- // Merge subscribedRooms to avoid losing local room memberships.
353
441
  const existingAgent = this.agents.get(patch.agent.id);
354
- if (existingAgent) {
355
- const merged = patch.agent;
442
+ if (existingAgent !== undefined &&
443
+ patch.agent.version < existingAgent.version) {
444
+ // Stale copy from a peer that missed updates (#27).
445
+ break;
446
+ }
447
+ const merged = patch.agent;
448
+ if (existingAgent?.version === patch.agent.version) {
449
+ // Concurrent mutations from the same base: keep subscriptions
450
+ // gained locally. A strictly higher version replaces the record.
356
451
  for (const r of existingAgent.subscribedRooms) {
357
452
  if (!merged.subscribedRooms.includes(r))
358
453
  merged.subscribedRooms.push(r);
359
454
  }
360
- this.agents.set(merged.id, merged);
361
- }
362
- else {
363
- this.agents.set(patch.agent.id, patch.agent);
364
455
  }
456
+ this.agents.set(merged.id, merged);
365
457
  break;
366
458
  }
367
459
  case "agent_offline": {
@@ -373,20 +465,23 @@ export class MeshStore {
373
465
  break;
374
466
  }
375
467
  case "room_upsert": {
376
- // Merge members rather than overwriting — last-write-wins can lose
377
- // members added locally when a remote patch arrives with a stale list.
378
468
  const existing = this.rooms.get(patch.room.id);
379
- if (existing) {
380
- const merged = patch.room;
469
+ if (existing !== undefined && patch.room.version < existing.version) {
470
+ // Stale copy from a peer that missed updates (#27).
471
+ break;
472
+ }
473
+ const merged = patch.room;
474
+ if (existing?.version === patch.room.version) {
475
+ // Concurrent mutations from the same base: union members so
476
+ // simultaneous joins both survive. A strictly higher version
477
+ // replaces the record wholesale, which is what heals a leave or
478
+ // edit a lagging peer missed.
381
479
  for (const m of existing.members) {
382
480
  if (!merged.members.includes(m))
383
481
  merged.members.push(m);
384
482
  }
385
- this.rooms.set(merged.id, merged);
386
- }
387
- else {
388
- this.rooms.set(patch.room.id, patch.room);
389
483
  }
484
+ this.rooms.set(merged.id, merged);
390
485
  break;
391
486
  }
392
487
  case "room_delete":
@@ -405,9 +500,7 @@ export class MeshStore {
405
500
  break;
406
501
  }
407
502
  case "delivery": {
408
- const arr = this.deliveryQueues.get(patch.agentId) ?? [];
409
- arr.push(patch.event);
410
- this.deliveryQueues.set(patch.agentId, arr);
503
+ this.queueDelivery(patch.agentId, patch.event);
411
504
  if (patch.agentId === this.peerId && this.onDelivery) {
412
505
  // Deduplicate against local deliveries
413
506
  const eventKey = JSON.stringify(patch.event);
@@ -461,9 +554,7 @@ export class MeshStore {
461
554
  }
462
555
  async deliverLocallyAndBroadcast(agentId, event) {
463
556
  // Local delivery
464
- const arr = this.deliveryQueues.get(agentId) ?? [];
465
- arr.push(event);
466
- this.deliveryQueues.set(agentId, arr);
557
+ this.queueDelivery(agentId, event);
467
558
  // Auto-emit delivered status for messages
468
559
  if (event.type === "room_message") {
469
560
  await this.emitDeliveryStatus(event.message.id, agentId, "delivered", event.message.room);
@@ -471,40 +562,63 @@ export class MeshStore {
471
562
  else if (event.type === "dm") {
472
563
  await this.emitDeliveryStatus(event.message.id, agentId, "delivered");
473
564
  }
474
- if (agentId === this.peerId && this.onDelivery) {
475
- // Deduplicate: skip if this exact event was already delivered locally.
476
- // The mesh can echo delivery patches through multiple peer paths,
477
- // causing applyPatch to fire onDelivery for the same event.
478
- const eventKey = JSON.stringify(event);
479
- if (this.localDeliveryKeys.has(eventKey))
480
- return;
481
- this.localDeliveryKeys.add(eventKey);
482
- // Prevent unbounded growth — evict oldest when cap reached
483
- if (this.localDeliveryKeys.size > 50) {
484
- const oldest = this.localDeliveryKeys.values().next().value;
485
- if (oldest !== undefined)
486
- this.localDeliveryKeys.delete(oldest);
487
- }
488
- void this.onDelivery(agentId, event);
489
- // Auto-mark read — scheduled as a macrotask to yield to the event
490
- // loop (see matching comment in applyPatch for rationale).
491
- const timer = setTimeout(() => {
492
- if (this.isShutDown)
493
- return;
494
- if (event.type === "room_message") {
495
- void this.markRead(event.message.id, agentId, event.message.room);
496
- }
497
- else if (event.type === "dm") {
498
- void this.markRead(event.message.id, agentId);
499
- }
500
- }, 0);
501
- if (!this.isShutDown)
502
- this.pendingMarkReadTimers.push(timer);
503
- }
565
+ this.fireLocalDelivery(agentId, event);
504
566
  // Remote delivery
505
567
  const patch = { type: "delivery", agentId, event };
506
568
  await this.broadcastPatch(patch);
507
569
  }
570
+ /**
571
+ * Fire onDelivery for an event targeting this peer's own agent, deduped
572
+ * against events already delivered in this process. Used both for live
573
+ * deliveries and for replays of events that accumulated while this
574
+ * process was down (#28): the dedup set is per-process, so a replayed
575
+ * event this process never saw fires, and one it already handled does
576
+ * not.
577
+ */
578
+ fireLocalDelivery(agentId, event) {
579
+ if (agentId !== this.peerId || !this.onDelivery)
580
+ return;
581
+ // A room message or DM this agent has already read was already pushed
582
+ // and consumed: read receipts mutate the event between snapshots, so a
583
+ // plain structural key would miss and re-fire on the next sync (#28).
584
+ if ((event.type === "room_message" || event.type === "dm") &&
585
+ event.message.readBy.includes(agentId)) {
586
+ return;
587
+ }
588
+ const eventKey = JSON.stringify(event);
589
+ if (this.localDeliveryKeys.has(eventKey))
590
+ return;
591
+ this.localDeliveryKeys.add(eventKey);
592
+ // Prevent unbounded growth — evict oldest when cap reached
593
+ if (this.localDeliveryKeys.size > 50) {
594
+ const oldest = this.localDeliveryKeys.values().next().value;
595
+ if (oldest !== undefined)
596
+ this.localDeliveryKeys.delete(oldest);
597
+ }
598
+ void this.onDelivery(agentId, event);
599
+ // Delivered to this process, so no longer pending for it. Peers that
600
+ // never fired the event keep their copies, which is what a restart
601
+ // replays from (#28). Key-based match: replayed events are JSON clones.
602
+ const queued = this.deliveryQueues.get(agentId);
603
+ if (queued !== undefined) {
604
+ const idx = queued.findIndex((e) => JSON.stringify(e) === eventKey);
605
+ if (idx !== -1)
606
+ queued.splice(idx, 1);
607
+ }
608
+ // Auto-mark read — scheduled as a macrotask to yield to the event loop.
609
+ const timer = setTimeout(() => {
610
+ if (this.isShutDown)
611
+ return;
612
+ if (event.type === "room_message") {
613
+ void this.markRead(event.message.id, agentId, event.message.room);
614
+ }
615
+ else if (event.type === "dm") {
616
+ void this.markRead(event.message.id, agentId);
617
+ }
618
+ }, 0);
619
+ if (!this.isShutDown)
620
+ this.pendingMarkReadTimers.push(timer);
621
+ }
508
622
  async deliverToRoom(roomId, event, excludeAgent) {
509
623
  const room = this.rooms.get(roomId);
510
624
  if (!room)
@@ -649,6 +763,7 @@ export class MeshStore {
649
763
  const id = this.peerId;
650
764
  const agent = {
651
765
  id,
766
+ version: 1,
652
767
  name: opts.name,
653
768
  harness: opts.harness,
654
769
  cwd: opts.cwd,
@@ -679,6 +794,7 @@ export class MeshStore {
679
794
  const oldStatus = agent.status;
680
795
  const oldName = agent.name;
681
796
  Object.assign(agent, patch);
797
+ this.bump(agent);
682
798
  this.agents.set(id, agent);
683
799
  await this.broadcastPatch({ type: "agent_upsert", agent });
684
800
  if (patch.name !== undefined && patch.name !== oldName) {
@@ -709,6 +825,7 @@ export class MeshStore {
709
825
  // Other stores learn about it via the agent_offline mesh patch.
710
826
  const isOwner = id === this.peerId;
711
827
  agent.status = "offline";
828
+ this.bump(agent);
712
829
  this.agents.set(id, agent);
713
830
  if (isOwner) {
714
831
  await this.notifyRoomsOfStatus(id, "offline");
@@ -725,6 +842,7 @@ export class MeshStore {
725
842
  throw new CommsError(`Room ${id} already exists`, "ROOM_EXISTS");
726
843
  const room = {
727
844
  id,
845
+ version: 1,
728
846
  name: opts.name,
729
847
  type: opts.type,
730
848
  owner: opts.owner,
@@ -771,10 +889,12 @@ export class MeshStore {
771
889
  if (!room.members.includes(agentId))
772
890
  room.members.push(agentId);
773
891
  }
892
+ this.bump(room);
774
893
  this.rooms.set(roomId, room);
775
894
  const agent = this.agents.get(agentId);
776
895
  if (agent && !agent.subscribedRooms.includes(roomId)) {
777
896
  agent.subscribedRooms.push(roomId);
897
+ this.bump(agent);
778
898
  this.agents.set(agentId, agent);
779
899
  await this.broadcastPatch({ type: "agent_upsert", agent });
780
900
  }
@@ -814,6 +934,7 @@ export class MeshStore {
814
934
  if (!room)
815
935
  throw new CommsError(`Room ${roomId} not found`, "ROOM_NOT_FOUND");
816
936
  room.members = room.members.filter((id) => id !== agentId);
937
+ this.bump(room);
817
938
  this.rooms.set(roomId, room);
818
939
  const agent = this.agents.get(agentId);
819
940
  if (agent) {
@@ -844,6 +965,7 @@ export class MeshStore {
844
965
  if (!room.invited.includes(targetId) && !room.members.includes(targetId)) {
845
966
  room.invited.push(targetId);
846
967
  }
968
+ this.bump(room);
847
969
  this.rooms.set(roomId, room);
848
970
  await this.broadcastPatch({ type: "room_upsert", room });
849
971
  const inviter = this.agents.get(inviterId);
@@ -863,6 +985,7 @@ export class MeshStore {
863
985
  if (!room.invited.includes(agentId))
864
986
  throw new CommsError(`Agent ${agentId} was not invited to ${roomId}`, "NOT_INVITED");
865
987
  room.invited = room.invited.filter((id) => id !== agentId);
988
+ this.bump(room);
866
989
  this.rooms.set(roomId, room);
867
990
  await this.broadcastPatch({ type: "room_upsert", room });
868
991
  const decliner = this.agents.get(agentId);
@@ -882,6 +1005,7 @@ export class MeshStore {
882
1005
  throw new CommsError("Only the room owner can kick", "NOT_OWNER");
883
1006
  room.members = room.members.filter((id) => id !== targetId);
884
1007
  room.invited = room.invited.filter((id) => id !== targetId);
1008
+ this.bump(room);
885
1009
  this.rooms.set(roomId, room);
886
1010
  await this.broadcastPatch({ type: "room_upsert", room });
887
1011
  }
@@ -1048,6 +1172,7 @@ export class MeshStore {
1048
1172
  this.agents.delete(id);
1049
1173
  this.peerInfo.delete(id);
1050
1174
  this.identityCache.delete(id);
1175
+ this.deliveryQueues.delete(id);
1051
1176
  }
1052
1177
  }
1053
1178
  isProcessAlive(pid) {
@@ -1167,6 +1292,7 @@ export class MeshStore {
1167
1292
  const remoteId = `fed:${agentId}`;
1168
1293
  if (!room.members.includes(remoteId)) {
1169
1294
  room.members.push(remoteId);
1295
+ this.bump(room);
1170
1296
  this.rooms.set(roomId, room);
1171
1297
  await this.broadcastPatch({ type: "room_upsert", room });
1172
1298
  }
@@ -1183,6 +1309,7 @@ export class MeshStore {
1183
1309
  return;
1184
1310
  const remoteId = `fed:${agentId}`;
1185
1311
  room.members = room.members.filter((m) => m !== remoteId);
1312
+ this.bump(room);
1186
1313
  this.rooms.set(roomId, room);
1187
1314
  await this.broadcastPatch({ type: "room_upsert", room });
1188
1315
  await this.deliverToRoom(roomId, {