agent-comms 2.21.12 → 2.21.14

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": "2.21.12"
18
+ "version": "2.21.14"
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": "2.21.12",
4
+ "version": "2.21.14",
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-2.21.12-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.21.12)
5
+ [![version](https://img.shields.io/badge/version-2.21.14-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.21.14)
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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=federation-bridge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-bridge.test.d.ts","sourceRoot":"","sources":["../../src/test/federation-bridge.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Direct, DI-based unit tests for FederationBridge -- it was previously exercised only indirectly through end-to-end federation.integration.test.ts scenarios, leaving many individual branches, the fed:-prefix startsWith-vs-endsWith distinction, and getVisibleAgents/getFederatedRoomMemberships' filtering unobserved. FederationBridgeDeps is a narrow, injectable surface built exactly for this: a fake deps object with vi.fn() collaborators lets every branch be asserted on directly.
3
+ *
4
+ * Three mutants Stryker raises are true equivalents, not gaps -- documented here rather than chased with a contrived test, matching stale-agent-checker.test.ts's and agent-registry.test.ts's own precedent for the identical pattern: `onAgentGone`'s `this.deps.agents.set(localId, agent)`, and `onRoomJoin`/`onRoomLeave`'s two `this.deps.rooms.set(roomId, room)` calls, each re-set the same key to the exact same object reference the map's own `.get()` already returned. `bump`/`recordMemberOp`/`refreshMembership` all mutate that object in place (bump does `entity.version += 1`; recordMemberOp writes into `room.memberJoins`/`memberLeaves`; refreshMembership reassigns `room.members`/`invited`), so the map already holds the fully up-to-date object before the redundant `.set()` call -- no test can observe removing it.
5
+ */
6
+ import { describe, expect, it, vi } from "vitest";
7
+ import { FederationBridge, } from "../core/federation-bridge.js";
8
+ function agent(overrides = {}) {
9
+ return {
10
+ id: "local-agent",
11
+ version: 1,
12
+ name: "agent-name",
13
+ harness: "pi",
14
+ cwd: "/tmp",
15
+ pid: 111,
16
+ startedAt: "2026-01-01T00:00:00.000Z",
17
+ visibility: "visible",
18
+ status: "active",
19
+ tags: [],
20
+ subscribedRooms: [],
21
+ ...overrides,
22
+ };
23
+ }
24
+ function room(overrides = {}) {
25
+ return {
26
+ id: "room-1",
27
+ version: 1,
28
+ name: "room-name",
29
+ type: "public",
30
+ owner: "owner-device",
31
+ createdAt: "2026-01-01T00:00:00.000Z",
32
+ description: "",
33
+ members: [],
34
+ invited: [],
35
+ memberJoins: {},
36
+ memberLeaves: {},
37
+ invitedJoins: {},
38
+ invitedLeaves: {},
39
+ ...overrides,
40
+ };
41
+ }
42
+ function message(overrides = {}) {
43
+ return {
44
+ id: "msg-1",
45
+ from: "sender",
46
+ room: "room-1",
47
+ content: "hi",
48
+ timestamp: "2026-01-01T00:00:00.000Z",
49
+ readBy: [],
50
+ ...overrides,
51
+ };
52
+ }
53
+ function makeHarness() {
54
+ const bump = vi.fn();
55
+ const recordMemberOp = vi.fn();
56
+ const refreshMembership = vi.fn();
57
+ const broadcastPatch = vi.fn().mockResolvedValue(undefined);
58
+ const deliverToRoom = vi.fn().mockResolvedValue(undefined);
59
+ const deliverLocallyAndBroadcast = vi.fn().mockResolvedValue(undefined);
60
+ const deps = {
61
+ agents: new Map(),
62
+ rooms: new Map(),
63
+ messages: new Map(),
64
+ deliveryEngine: {
65
+ bump,
66
+ recordMemberOp,
67
+ refreshMembership,
68
+ broadcastPatch,
69
+ deliverToRoom,
70
+ deliverLocallyAndBroadcast,
71
+ },
72
+ };
73
+ return {
74
+ deps,
75
+ bridge: new FederationBridge(deps),
76
+ bump,
77
+ recordMemberOp,
78
+ refreshMembership,
79
+ broadcastPatch,
80
+ deliverToRoom,
81
+ deliverLocallyAndBroadcast,
82
+ };
83
+ }
84
+ describe("FederationBridge — onAgentVisible", () => {
85
+ it("stores the remote agent under a fed:<id>@<harness> key, tagged federated, and broadcasts it", async () => {
86
+ const h = makeHarness();
87
+ const remote = agent({ id: "remote-id", harness: "codex", tags: ["x"] });
88
+ await h.bridge.onAgentVisible(remote);
89
+ const stored = h.deps.agents.get("fed:remote-id@codex");
90
+ expect(stored).toBeDefined();
91
+ expect(stored?.id).toBe("fed:remote-id@codex");
92
+ expect(stored?.tags).toEqual(["x", "federated"]);
93
+ expect(h.broadcastPatch).toHaveBeenCalledWith({
94
+ type: "agent_upsert",
95
+ agent: stored,
96
+ });
97
+ });
98
+ });
99
+ describe("FederationBridge — onAgentGone", () => {
100
+ it("marks the matching fed:-prefixed local agent offline and broadcasts it", async () => {
101
+ const h = makeHarness();
102
+ h.deps.agents.set("fed:remote-id@codex", agent({ id: "fed:remote-id@codex", status: "active" }));
103
+ await h.bridge.onAgentGone("remote-id");
104
+ expect(h.deps.agents.get("fed:remote-id@codex")?.status).toBe("offline");
105
+ expect(h.broadcastPatch).toHaveBeenCalledWith({
106
+ type: "agent_offline",
107
+ agentId: "fed:remote-id@codex",
108
+ });
109
+ });
110
+ it("does nothing when no local agent matches the fed:<id>@ prefix", async () => {
111
+ const h = makeHarness();
112
+ await h.bridge.onAgentGone("no-such-remote");
113
+ expect(h.broadcastPatch).not.toHaveBeenCalled();
114
+ });
115
+ it("requires the prefix to actually match at the start, not merely appear at the end", async () => {
116
+ const h = makeHarness();
117
+ // Ends with "fed:remote-id@" but does not start with it -- must not match.
118
+ h.deps.agents.set("xfed:remote-id@", agent({ id: "xfed:remote-id@", status: "active" }));
119
+ await h.bridge.onAgentGone("remote-id");
120
+ expect(h.deps.agents.get("xfed:remote-id@")?.status).toBe("active");
121
+ expect(h.broadcastPatch).not.toHaveBeenCalled();
122
+ });
123
+ });
124
+ describe("FederationBridge — onRoomMessage", () => {
125
+ it("stores the message and delivers it to every local room member", async () => {
126
+ const h = makeHarness();
127
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: true, members: ["a", "b"] }));
128
+ const msg = message();
129
+ await h.bridge.onRoomMessage("room-1", msg);
130
+ expect(h.deps.messages.get("room-1")).toEqual([msg]);
131
+ expect(h.deliverLocallyAndBroadcast).toHaveBeenCalledWith("a", {
132
+ type: "room_message",
133
+ message: msg,
134
+ });
135
+ expect(h.deliverLocallyAndBroadcast).toHaveBeenCalledWith("b", {
136
+ type: "room_message",
137
+ message: msg,
138
+ });
139
+ });
140
+ it("does nothing for a room that isn't federated", async () => {
141
+ const h = makeHarness();
142
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: false }));
143
+ await h.bridge.onRoomMessage("room-1", message());
144
+ expect(h.deps.messages.get("room-1")).toBeUndefined();
145
+ expect(h.deliverLocallyAndBroadcast).not.toHaveBeenCalled();
146
+ });
147
+ });
148
+ describe("FederationBridge — onRoomJoin", () => {
149
+ it("adds a fed:-prefixed shadow member, records the join, and broadcasts the room when the member is new", async () => {
150
+ const h = makeHarness();
151
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: true, members: [] }));
152
+ await h.bridge.onRoomJoin("room-1", "remote-agent", "Remote Name");
153
+ expect(h.bump).toHaveBeenCalledTimes(1);
154
+ expect(h.recordMemberOp).toHaveBeenCalledWith(expect.anything(), "member", "join", "fed:remote-agent");
155
+ expect(h.refreshMembership).toHaveBeenCalledTimes(1);
156
+ expect(h.broadcastPatch).toHaveBeenCalledWith({
157
+ type: "room_upsert",
158
+ room: expect.anything(),
159
+ });
160
+ });
161
+ it("skips the join-recording step entirely when the shadow member is already present", async () => {
162
+ const h = makeHarness();
163
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: true, members: ["fed:remote-agent"] }));
164
+ await h.bridge.onRoomJoin("room-1", "remote-agent", "Remote Name");
165
+ expect(h.bump).not.toHaveBeenCalled();
166
+ expect(h.recordMemberOp).not.toHaveBeenCalled();
167
+ expect(h.broadcastPatch).not.toHaveBeenCalled();
168
+ });
169
+ it("always notifies local members of the join, even when the shadow member already existed", async () => {
170
+ const h = makeHarness();
171
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: true, members: ["fed:remote-agent"] }));
172
+ await h.bridge.onRoomJoin("room-1", "remote-agent", "Remote Name");
173
+ expect(h.deliverToRoom).toHaveBeenCalledWith("room-1", { type: "member_joined", room: "room-1", agent: "fed:remote-agent" }, "fed:remote-agent");
174
+ });
175
+ it("does nothing at all for a room that isn't federated", async () => {
176
+ const h = makeHarness();
177
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: false }));
178
+ await h.bridge.onRoomJoin("room-1", "remote-agent", "Remote Name");
179
+ expect(h.bump).not.toHaveBeenCalled();
180
+ expect(h.deliverToRoom).not.toHaveBeenCalled();
181
+ });
182
+ });
183
+ describe("FederationBridge — onRoomLeave", () => {
184
+ it("records the leave, bumps and broadcasts the room, and notifies local members", async () => {
185
+ const h = makeHarness();
186
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: true, members: ["fed:remote-agent"] }));
187
+ await h.bridge.onRoomLeave("room-1", "remote-agent");
188
+ expect(h.bump).toHaveBeenCalledTimes(1);
189
+ expect(h.recordMemberOp).toHaveBeenCalledWith(expect.anything(), "member", "leave", "fed:remote-agent");
190
+ expect(h.refreshMembership).toHaveBeenCalledTimes(1);
191
+ expect(h.broadcastPatch).toHaveBeenCalledWith({
192
+ type: "room_upsert",
193
+ room: expect.anything(),
194
+ });
195
+ expect(h.deliverToRoom).toHaveBeenCalledWith("room-1", {
196
+ type: "member_left",
197
+ room: "room-1",
198
+ agent: "fed:remote-agent",
199
+ });
200
+ });
201
+ it("does nothing at all for a room that isn't federated", async () => {
202
+ const h = makeHarness();
203
+ h.deps.rooms.set("room-1", room({ id: "room-1", federated: false }));
204
+ await h.bridge.onRoomLeave("room-1", "remote-agent");
205
+ expect(h.bump).not.toHaveBeenCalled();
206
+ expect(h.deliverToRoom).not.toHaveBeenCalled();
207
+ });
208
+ });
209
+ describe("FederationBridge — getVisibleAgents", () => {
210
+ it("includes a visible, non-federated agent", () => {
211
+ const h = makeHarness();
212
+ const a = agent({ id: "local-a", visibility: "visible" });
213
+ h.deps.agents.set(a.id, a);
214
+ expect(h.bridge.getVisibleAgents()).toEqual([a]);
215
+ });
216
+ it("excludes an agent that isn't visible", () => {
217
+ const h = makeHarness();
218
+ h.deps.agents.set("hidden-a", agent({ id: "hidden-a", visibility: "hidden" }));
219
+ expect(h.bridge.getVisibleAgents()).toEqual([]);
220
+ });
221
+ it("excludes an already-federated-in agent even when visible", () => {
222
+ const h = makeHarness();
223
+ h.deps.agents.set("fed:remote@codex", agent({ id: "fed:remote@codex", visibility: "visible" }));
224
+ expect(h.bridge.getVisibleAgents()).toEqual([]);
225
+ });
226
+ it("requires the fed: prefix to be at the start, not merely present at the end", () => {
227
+ const h = makeHarness();
228
+ // Ends with "fed:" but does not start with it -- must be included.
229
+ const a = agent({ id: "xxxfed:", visibility: "visible" });
230
+ h.deps.agents.set(a.id, a);
231
+ expect(h.bridge.getVisibleAgents()).toEqual([a]);
232
+ });
233
+ });
234
+ describe("FederationBridge — getFederatedRoomMemberships", () => {
235
+ it("includes only federated rooms, with fed:-prefixed members filtered out", () => {
236
+ const h = makeHarness();
237
+ h.deps.rooms.set("fed-room", room({
238
+ id: "fed-room",
239
+ federated: true,
240
+ members: ["local-a", "fed:remote-b"],
241
+ }));
242
+ h.deps.rooms.set("plain-room", room({ id: "plain-room", federated: false, members: ["local-c"] }));
243
+ const result = h.bridge.getFederatedRoomMemberships();
244
+ expect(result.get("fed-room")).toEqual(["local-a"]);
245
+ expect(result.has("plain-room")).toBe(false);
246
+ });
247
+ it("requires the fed: prefix to be at the start, not merely present at the end, when filtering members", () => {
248
+ const h = makeHarness();
249
+ h.deps.rooms.set("fed-room", room({ id: "fed-room", federated: true, members: ["xxxfed:"] }));
250
+ const result = h.bridge.getFederatedRoomMemberships();
251
+ expect(result.get("fed-room")).toEqual(["xxxfed:"]);
252
+ });
253
+ });
254
+ //# sourceMappingURL=federation-bridge.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation-bridge.test.js","sourceRoot":"","sources":["../../src/test/federation-bridge.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=peer-lifecycle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-lifecycle.test.d.ts","sourceRoot":"","sources":["../../src/test/peer-lifecycle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Direct, DI-based unit tests for PeerLifecycle -- it was previously exercised only indirectly through end-to-end mesh integration tests, leaving several individual branches, self-connect guards, and handleDataMessage's method-dispatch branches unobserved. A few of the survivors Stryker reports against handleDataMessage's whole-suite timeout classification: emptying its body doesn't fail fast, it hangs some unrelated integration test waiting on state that never arrives, so a direct unit test that fails immediately on the same mutation converts a slow timeout into a fast, precise kill. PeerLifecycleDeps is a narrow, injectable surface built exactly for this direct testing.
3
+ */
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { PeerLifecycle, } from "../core/peer-lifecycle.js";
6
+ const OWNER_ID = "owner-device";
7
+ const COORDINATOR_PORT = 19876;
8
+ function peerInfo(id, port = 1) {
9
+ return { id, port, startedAt: "2026-01-01T00:00:00.000Z" };
10
+ }
11
+ function emptyState() {
12
+ return { agents: {}, rooms: {}, messages: {}, dms: {}, deliveryQueues: {} };
13
+ }
14
+ function makeHarness() {
15
+ const transport = {
16
+ connectToPeer: vi.fn().mockResolvedValue(undefined),
17
+ send: vi.fn().mockResolvedValue(undefined),
18
+ broadcast: vi.fn().mockResolvedValue(undefined),
19
+ becomeCoordinator: vi.fn().mockResolvedValue(undefined),
20
+ };
21
+ const flushPendingRoomRequests = vi.fn().mockResolvedValue(undefined);
22
+ const applyStateSync = vi.fn();
23
+ const applyPatch = vi.fn().mockResolvedValue(undefined);
24
+ const staleAgentCheckerStart = vi.fn();
25
+ const deps = {
26
+ peerInfo: new Map(),
27
+ agents: new Map(),
28
+ coordinatorPort: COORDINATOR_PORT,
29
+ getPeerId: () => OWNER_ID,
30
+ requireTransport: () => transport,
31
+ serialise: () => emptyState(),
32
+ roomProtocol: { flushPendingRoomRequests },
33
+ deliveryEngine: { applyStateSync, applyPatch },
34
+ staleAgentChecker: { start: staleAgentCheckerStart },
35
+ };
36
+ return {
37
+ deps,
38
+ lifecycle: new PeerLifecycle(deps),
39
+ transport,
40
+ flushPendingRoomRequests,
41
+ applyStateSync,
42
+ applyPatch,
43
+ staleAgentCheckerStart,
44
+ };
45
+ }
46
+ describe("PeerLifecycle — handlePeerList", () => {
47
+ it("records every peer and dials each one except itself", () => {
48
+ const h = makeHarness();
49
+ const self = peerInfo(OWNER_ID);
50
+ const other = peerInfo("other-peer");
51
+ h.lifecycle.handlePeerList([self, other]);
52
+ expect(h.deps.peerInfo.get(OWNER_ID)).toEqual(self);
53
+ expect(h.deps.peerInfo.get("other-peer")).toEqual(other);
54
+ expect(h.transport.connectToPeer).toHaveBeenCalledTimes(1);
55
+ expect(h.transport.connectToPeer).toHaveBeenCalledWith(other, OWNER_ID);
56
+ });
57
+ });
58
+ describe("PeerLifecycle — handlePeerJoined", () => {
59
+ it("records the joining peer and dials it when it isn't this store's own id", () => {
60
+ const h = makeHarness();
61
+ const other = peerInfo("other-peer");
62
+ h.lifecycle.handlePeerJoined(other);
63
+ expect(h.deps.peerInfo.get("other-peer")).toEqual(other);
64
+ expect(h.transport.connectToPeer).toHaveBeenCalledWith(other, OWNER_ID);
65
+ });
66
+ it("records but never dials itself when the joined peer is this store's own id", () => {
67
+ const h = makeHarness();
68
+ const self = peerInfo(OWNER_ID);
69
+ h.lifecycle.handlePeerJoined(self);
70
+ expect(h.deps.peerInfo.get(OWNER_ID)).toEqual(self);
71
+ expect(h.transport.connectToPeer).not.toHaveBeenCalled();
72
+ });
73
+ });
74
+ describe("PeerLifecycle — handlePeerConnected", () => {
75
+ it("sends a state_sync when this store already has agent state", async () => {
76
+ const h = makeHarness();
77
+ h.deps.agents.set(OWNER_ID, { id: OWNER_ID });
78
+ await h.lifecycle.handlePeerConnected({ id: "conn-1" }, peerInfo("x"));
79
+ expect(h.transport.send).toHaveBeenCalledWith({ id: "conn-1" }, { method: "state_sync", state: emptyState() });
80
+ });
81
+ it("sends no state_sync when this store has no agent state at all", async () => {
82
+ const h = makeHarness();
83
+ await h.lifecycle.handlePeerConnected({ id: "conn-1" }, peerInfo("x"));
84
+ expect(h.transport.send).not.toHaveBeenCalled();
85
+ });
86
+ it("always flushes pending room requests for the connection, regardless of state", async () => {
87
+ const h = makeHarness();
88
+ await h.lifecycle.handlePeerConnected({ id: "conn-1" }, peerInfo("x"));
89
+ expect(h.flushPendingRoomRequests).toHaveBeenCalledWith("conn-1");
90
+ });
91
+ });
92
+ describe("PeerLifecycle — handleDataMessage", () => {
93
+ it("applies a normalised state_sync to the delivery engine", async () => {
94
+ const h = makeHarness();
95
+ await h.lifecycle.handleDataMessage({ id: "conn-1" }, { method: "state_sync", state: emptyState() });
96
+ expect(h.applyStateSync).toHaveBeenCalledWith(emptyState());
97
+ });
98
+ it("applies a state_update's patch to the delivery engine", async () => {
99
+ const h = makeHarness();
100
+ const event = {
101
+ type: "connection_request",
102
+ connectionId: "c",
103
+ peerId: "p",
104
+ dataPort: 1,
105
+ name: "n",
106
+ fingerprint: "fp",
107
+ };
108
+ await h.lifecycle.handleDataMessage({ id: "conn-1" }, {
109
+ method: "state_update",
110
+ patch: { type: "delivery", agentId: "a", event },
111
+ });
112
+ expect(h.applyPatch).toHaveBeenCalledWith({
113
+ type: "delivery",
114
+ agentId: "a",
115
+ event,
116
+ });
117
+ });
118
+ it("applies neither for a message method it doesn't dispatch on", async () => {
119
+ const h = makeHarness();
120
+ await h.lifecycle.handleDataMessage({ id: "conn-1" }, { method: "introduce", peerId: "p", dataPort: 1 });
121
+ expect(h.applyStateSync).not.toHaveBeenCalled();
122
+ expect(h.applyPatch).not.toHaveBeenCalled();
123
+ });
124
+ });
125
+ describe("PeerLifecycle — handleBecomeCoordinator", () => {
126
+ it("becomes coordinator, replaces the peer table with exactly the handoff list, dials every peer, and starts stale-agent probing", async () => {
127
+ const h = makeHarness();
128
+ h.deps.peerInfo.set("stale-peer", peerInfo("stale-peer"));
129
+ const incoming = [peerInfo("a"), peerInfo("b")];
130
+ await h.lifecycle.handleBecomeCoordinator(incoming);
131
+ expect(h.transport.becomeCoordinator).toHaveBeenCalledWith("127.0.0.1", COORDINATOR_PORT);
132
+ expect(h.deps.peerInfo.has("stale-peer")).toBe(false);
133
+ expect(h.deps.peerInfo.get("a")).toEqual(incoming[0]);
134
+ expect(h.deps.peerInfo.get("b")).toEqual(incoming[1]);
135
+ expect(h.transport.connectToPeer).toHaveBeenCalledTimes(2);
136
+ expect(h.transport.connectToPeer).toHaveBeenCalledWith(incoming[0], OWNER_ID);
137
+ expect(h.transport.connectToPeer).toHaveBeenCalledWith(incoming[1], OWNER_ID);
138
+ expect(h.staleAgentCheckerStart).toHaveBeenCalledTimes(1);
139
+ });
140
+ });
141
+ describe("PeerLifecycle — handlePeerDisconnected", () => {
142
+ it("removes the disconnected peer's entry from peerInfo", () => {
143
+ const h = makeHarness();
144
+ h.deps.peerInfo.set("gone-peer", peerInfo("gone-peer"));
145
+ h.lifecycle.handlePeerDisconnected({ id: "gone-peer" });
146
+ expect(h.deps.peerInfo.has("gone-peer")).toBe(false);
147
+ });
148
+ });
149
+ //# sourceMappingURL=peer-lifecycle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-lifecycle.test.js","sourceRoot":"","sources":["../../src/test/peer-lifecycle.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-comms",
3
- "version": "2.21.12",
3
+ "version": "2.21.14",
4
4
  "description": "Cross-harness communication mesh for LLM agents — rooms, DMs, presence, and real-time push delivery over TCP",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",