agent-comms 2.21.10 → 2.21.12

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.10"
18
+ "version": "2.21.12"
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.10",
4
+ "version": "2.21.12",
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.10-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.21.10)
5
+ [![version](https://img.shields.io/badge/version-2.21.12-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.21.12)
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=agent-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-registry.test.d.ts","sourceRoot":"","sources":["../../src/test/agent-registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Direct, DI-based unit tests for AgentRegistry -- it was previously exercised only indirectly through MeshStore/room-kick-revocation integration tests, which left many individual branches, string literals, and array literals unobserved. AgentRegistryDeps 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
+ * One mutant Stryker raises against setAgentOffline is a true equivalent, not a gap -- documented here rather than chased with a contrived test, matching stale-agent-checker.test.ts's own precedent for the identical pattern: `this.deps.agents.set(id, agent)` right after `agent.status = "offline"` re-sets the same key to the exact same object reference `this.deps.agents.get(id)` already returned, so mutating `.status` on it has already mutated what the Map holds. No test can observe removing that call.
5
+ */
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
7
+ import { AgentRegistry, } from "../core/agent-registry.js";
8
+ import { CommsError } from "../core/store.js";
9
+ const OWNER_ID = "owner-device";
10
+ const OTHER_ID = "other-device";
11
+ const STARTED_AT = "2026-01-01T00:00:00.000Z";
12
+ /** An arbitrary, distinctive pid used only to prove updateAgent's patch actually lands -- no significance beyond "not the seeded pid". */
13
+ const UPDATED_PID = 999;
14
+ function agent(overrides = {}) {
15
+ return {
16
+ id: OWNER_ID,
17
+ version: 1,
18
+ name: "agent-name",
19
+ harness: "pi",
20
+ cwd: "/tmp",
21
+ pid: 111,
22
+ startedAt: STARTED_AT,
23
+ visibility: "visible",
24
+ status: "active",
25
+ tags: [],
26
+ subscribedRooms: [],
27
+ ...overrides,
28
+ };
29
+ }
30
+ function makeHarness(peerId = OWNER_ID) {
31
+ const bump = vi.fn();
32
+ const broadcastPatch = vi.fn().mockResolvedValue(undefined);
33
+ const notifyRoomsOfStatus = vi.fn().mockResolvedValue(undefined);
34
+ const notifyRoomsOfNameChange = vi.fn().mockResolvedValue(undefined);
35
+ const broadcastAgentVisible = vi.fn().mockResolvedValue(undefined);
36
+ const broadcastAgentGone = vi.fn().mockResolvedValue(undefined);
37
+ const deps = {
38
+ agents: new Map(),
39
+ identityCache: new Map(),
40
+ startedAt: STARTED_AT,
41
+ getPeerId: () => peerId,
42
+ deliveryEngine: {
43
+ bump,
44
+ broadcastPatch,
45
+ notifyRoomsOfStatus,
46
+ notifyRoomsOfNameChange,
47
+ },
48
+ federation: { broadcastAgentVisible, broadcastAgentGone },
49
+ };
50
+ return {
51
+ deps,
52
+ registry: new AgentRegistry(deps),
53
+ bump,
54
+ broadcastPatch,
55
+ notifyRoomsOfStatus,
56
+ notifyRoomsOfNameChange,
57
+ broadcastAgentVisible,
58
+ broadcastAgentGone,
59
+ };
60
+ }
61
+ describe("AgentRegistry — identity cache", () => {
62
+ it("readIdentity keys by the exact harness+cwd pair, not a blank or partial key", async () => {
63
+ const { registry, deps } = makeHarness();
64
+ deps.identityCache.set("pi--/tmp/a", { id: "agent-a" });
65
+ deps.identityCache.set("codex--/tmp/a", { id: "agent-b" });
66
+ await expect(registry.readIdentity("pi", "/tmp/a")).resolves.toEqual({
67
+ id: "agent-a",
68
+ });
69
+ await expect(registry.readIdentity("codex", "/tmp/a")).resolves.toEqual({
70
+ id: "agent-b",
71
+ });
72
+ await expect(registry.readIdentity("pi", "/tmp/other")).resolves.toBeUndefined();
73
+ });
74
+ it("writeIdentity stores under the harness+cwd composite key, readable back by readIdentity", async () => {
75
+ const { registry } = makeHarness();
76
+ await registry.writeIdentity("pi", "/tmp/a", "written-id");
77
+ await expect(registry.readIdentity("pi", "/tmp/a")).resolves.toEqual({
78
+ id: "written-id",
79
+ });
80
+ });
81
+ });
82
+ describe("AgentRegistry — registerAgent", () => {
83
+ it("a brand-new (harness, cwd) pair creates a fresh agent with an empty subscribedRooms array", async () => {
84
+ const { registry, deps } = makeHarness();
85
+ const created = await registry.registerAgent({
86
+ name: "fresh",
87
+ harness: "pi",
88
+ cwd: "/tmp/fresh",
89
+ pid: 1,
90
+ visibility: "visible",
91
+ tags: ["x"],
92
+ });
93
+ expect(created.subscribedRooms).toEqual([]);
94
+ expect(deps.agents.get(OWNER_ID)).toEqual(created);
95
+ await expect(registry.readIdentity("pi", "/tmp/fresh")).resolves.toEqual({
96
+ id: OWNER_ID,
97
+ });
98
+ });
99
+ it("re-registering an already-known (harness, cwd) pair updates the existing agent by its original id rather than minting a fresh one", async () => {
100
+ let nextId = 0;
101
+ const peerIds = ["first-id", "second-id"];
102
+ const { registry, deps } = makeHarness();
103
+ deps.getPeerId = () => {
104
+ const id = peerIds[nextId];
105
+ nextId += 1;
106
+ if (id === undefined)
107
+ throw new Error("getPeerId called more times than expected");
108
+ return id;
109
+ };
110
+ const first = await registry.registerAgent({
111
+ name: "one",
112
+ harness: "pi",
113
+ cwd: "/tmp/same",
114
+ pid: 1,
115
+ visibility: "visible",
116
+ tags: [],
117
+ });
118
+ const second = await registry.registerAgent({
119
+ name: "two",
120
+ harness: "pi",
121
+ cwd: "/tmp/same",
122
+ pid: 2,
123
+ visibility: "hidden",
124
+ tags: ["updated"],
125
+ });
126
+ expect(second.id).toBe(first.id);
127
+ expect(nextId).toBe(1);
128
+ expect(second.name).toBe("two");
129
+ expect(second.visibility).toBe("hidden");
130
+ expect(second.pid).toBe(2);
131
+ expect(second.tags).toEqual(["updated"]);
132
+ });
133
+ it("broadcasts presence to federated links only for a visible agent, not hidden or ghost", async () => {
134
+ const visible = makeHarness();
135
+ await visible.registry.registerAgent({
136
+ name: "v",
137
+ harness: "pi",
138
+ cwd: "/tmp/v",
139
+ pid: 1,
140
+ visibility: "visible",
141
+ tags: [],
142
+ });
143
+ expect(visible.broadcastAgentVisible).toHaveBeenCalledTimes(1);
144
+ const hidden = makeHarness();
145
+ await hidden.registry.registerAgent({
146
+ name: "h",
147
+ harness: "pi",
148
+ cwd: "/tmp/h",
149
+ pid: 1,
150
+ visibility: "hidden",
151
+ tags: [],
152
+ });
153
+ expect(hidden.broadcastAgentVisible).not.toHaveBeenCalled();
154
+ const ghost = makeHarness();
155
+ await ghost.registry.registerAgent({
156
+ name: "g",
157
+ harness: "pi",
158
+ cwd: "/tmp/g",
159
+ pid: 1,
160
+ visibility: "ghost",
161
+ tags: [],
162
+ });
163
+ expect(ghost.broadcastAgentVisible).not.toHaveBeenCalled();
164
+ });
165
+ });
166
+ describe("AgentRegistry — getAgent", () => {
167
+ it("returns the stored agent for a known id, undefined for an unknown one", async () => {
168
+ const { registry, deps } = makeHarness();
169
+ const a = agent();
170
+ deps.agents.set(a.id, a);
171
+ await expect(registry.getAgent(a.id)).resolves.toEqual(a);
172
+ await expect(registry.getAgent("nobody")).resolves.toBeUndefined();
173
+ });
174
+ });
175
+ describe("AgentRegistry — updateAgent", () => {
176
+ let h;
177
+ beforeEach(() => {
178
+ h = makeHarness();
179
+ h.deps.agents.set(OWNER_ID, agent({ name: "old-name", status: "active" }));
180
+ });
181
+ it("throws a CommsError naming the exact missing agent id when the target doesn't exist", async () => {
182
+ await expect(h.registry.updateAgent("missing-id", {})).rejects.toMatchObject(new CommsError("Agent missing-id not found", "AGENT_NOT_FOUND"));
183
+ });
184
+ it("bumps and persists the merged patch into the agents map", async () => {
185
+ const updated = await h.registry.updateAgent(OWNER_ID, {
186
+ pid: UPDATED_PID,
187
+ });
188
+ expect(h.bump).toHaveBeenCalledWith(updated);
189
+ expect(h.deps.agents.get(OWNER_ID)).toEqual(updated);
190
+ expect(updated.pid).toBe(UPDATED_PID);
191
+ });
192
+ it("notifies rooms of a name change only when the patch actually changes the name", async () => {
193
+ await h.registry.updateAgent(OWNER_ID, { name: "new-name" });
194
+ expect(h.notifyRoomsOfNameChange).toHaveBeenCalledWith(OWNER_ID, "old-name", "new-name");
195
+ });
196
+ it("does not notify of a name change when the patch omits name", async () => {
197
+ await h.registry.updateAgent(OWNER_ID, { pid: 1 });
198
+ expect(h.notifyRoomsOfNameChange).not.toHaveBeenCalled();
199
+ });
200
+ it("does not notify of a name change when the patch repeats the current name", async () => {
201
+ await h.registry.updateAgent(OWNER_ID, { name: "old-name" });
202
+ expect(h.notifyRoomsOfNameChange).not.toHaveBeenCalled();
203
+ });
204
+ it("notifies rooms of a status change only when the patch actually changes the status", async () => {
205
+ await h.registry.updateAgent(OWNER_ID, { status: "offline" });
206
+ expect(h.notifyRoomsOfStatus).toHaveBeenCalledWith(OWNER_ID, "offline");
207
+ });
208
+ it("does not notify of a status change when the patch omits status", async () => {
209
+ await h.registry.updateAgent(OWNER_ID, { pid: 1 });
210
+ expect(h.notifyRoomsOfStatus).not.toHaveBeenCalled();
211
+ });
212
+ it("does not notify of a status change when the patch repeats the current status", async () => {
213
+ await h.registry.updateAgent(OWNER_ID, { status: "active" });
214
+ expect(h.notifyRoomsOfStatus).not.toHaveBeenCalled();
215
+ });
216
+ });
217
+ describe("AgentRegistry — listAgents", () => {
218
+ it("excludes a ghost agent from a different requester's view", async () => {
219
+ const { registry, deps } = makeHarness();
220
+ deps.agents.set("ghost-agent", agent({ id: "ghost-agent", visibility: "ghost" }));
221
+ deps.agents.set("visible-agent", agent({ id: "visible-agent", visibility: "visible" }));
222
+ const result = await registry.listAgents("some-requester");
223
+ expect(result.map((a) => a.id)).toEqual(["visible-agent"]);
224
+ });
225
+ it("includes a ghost agent in its own view of itself", async () => {
226
+ const { registry, deps } = makeHarness();
227
+ deps.agents.set("ghost-agent", agent({ id: "ghost-agent", visibility: "ghost" }));
228
+ const result = await registry.listAgents("ghost-agent");
229
+ expect(result.map((a) => a.id)).toEqual(["ghost-agent"]);
230
+ });
231
+ it("always includes a non-ghost agent regardless of who's asking", async () => {
232
+ const { registry, deps } = makeHarness();
233
+ deps.agents.set("visible-agent", agent({ id: "visible-agent", visibility: "visible" }));
234
+ const result = await registry.listAgents("anyone-else");
235
+ expect(result.map((a) => a.id)).toEqual(["visible-agent"]);
236
+ });
237
+ });
238
+ describe("AgentRegistry — setAgentOffline", () => {
239
+ it("is a no-op for an unknown agent id", async () => {
240
+ const h = makeHarness();
241
+ await h.registry.setAgentOffline("unknown");
242
+ expect(h.bump).not.toHaveBeenCalled();
243
+ });
244
+ it("is a no-op for an agent already offline", async () => {
245
+ const h = makeHarness();
246
+ h.deps.agents.set(OWNER_ID, agent({ status: "offline" }));
247
+ await h.registry.setAgentOffline(OWNER_ID);
248
+ expect(h.bump).not.toHaveBeenCalled();
249
+ expect(h.notifyRoomsOfStatus).not.toHaveBeenCalled();
250
+ });
251
+ it("for the owning peer's own agent, sets status offline, bumps, persists, and broadcasts to rooms/mesh/federation", async () => {
252
+ const h = makeHarness(OWNER_ID);
253
+ h.deps.agents.set(OWNER_ID, agent({ status: "active" }));
254
+ await h.registry.setAgentOffline(OWNER_ID);
255
+ expect(h.deps.agents.get(OWNER_ID)?.status).toBe("offline");
256
+ expect(h.bump).toHaveBeenCalledTimes(1);
257
+ expect(h.notifyRoomsOfStatus).toHaveBeenCalledWith(OWNER_ID, "offline");
258
+ expect(h.broadcastPatch).toHaveBeenCalledWith({
259
+ type: "agent_offline",
260
+ agentId: OWNER_ID,
261
+ });
262
+ expect(h.broadcastAgentGone).toHaveBeenCalledWith(OWNER_ID);
263
+ });
264
+ it("for a non-owning peer's agent, updates local state but does not broadcast anything", async () => {
265
+ const h = makeHarness(OWNER_ID);
266
+ h.deps.agents.set(OTHER_ID, agent({ id: OTHER_ID, status: "active" }));
267
+ await h.registry.setAgentOffline(OTHER_ID);
268
+ expect(h.deps.agents.get(OTHER_ID)?.status).toBe("offline");
269
+ expect(h.bump).toHaveBeenCalledTimes(1);
270
+ expect(h.notifyRoomsOfStatus).not.toHaveBeenCalled();
271
+ expect(h.broadcastPatch).not.toHaveBeenCalled();
272
+ expect(h.broadcastAgentGone).not.toHaveBeenCalled();
273
+ });
274
+ });
275
+ //# sourceMappingURL=agent-registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-registry.test.js","sourceRoot":"","sources":["../../src/test/agent-registry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connection-approval.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-approval.test.d.ts","sourceRoot":"","sources":["../../src/test/connection-approval.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Direct, DI-based unit tests for ConnectionApproval -- it was previously exercised only indirectly through approval.integration.test.ts's end-to-end transport scenarios, which left several individual branches, error messages, and fallback string literals unobserved. ConnectionApprovalDeps 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
+ import { describe, expect, it, vi } from "vitest";
5
+ import { ConnectionApproval, } from "../core/connection-approval.js";
6
+ const OWNER_ID = "owner-device";
7
+ const STARTED_AT = "2026-01-01T00:00:00.000Z";
8
+ const REMOTE_HOST = "example.test";
9
+ const REMOTE_PORT = 4242;
10
+ const LOCAL_DATA_PORT = 5150;
11
+ function agent(overrides = {}) {
12
+ return {
13
+ id: OWNER_ID,
14
+ version: 1,
15
+ name: "agent-name",
16
+ harness: "pi",
17
+ cwd: "/tmp",
18
+ pid: 111,
19
+ startedAt: STARTED_AT,
20
+ visibility: "visible",
21
+ status: "active",
22
+ tags: [],
23
+ subscribedRooms: [],
24
+ ...overrides,
25
+ };
26
+ }
27
+ function makeHarness(options = {}) {
28
+ const transport = {
29
+ acceptConnection: vi.fn().mockResolvedValue(undefined),
30
+ rejectConnection: vi.fn().mockResolvedValue(undefined),
31
+ connectToRemote: vi.fn().mockResolvedValue(undefined),
32
+ startDataServer: vi.fn().mockResolvedValue(undefined),
33
+ unref: vi.fn(),
34
+ dataPort: LOCAL_DATA_PORT,
35
+ };
36
+ const queueDelivery = vi.fn();
37
+ const onDelivery = options.withOnDelivery === true ? vi.fn() : undefined;
38
+ const deps = {
39
+ agents: new Map(),
40
+ peerInfo: new Map(),
41
+ startedAt: STARTED_AT,
42
+ getPeerId: () => OWNER_ID,
43
+ requireTransport: () => transport,
44
+ getOnDelivery: () => onDelivery,
45
+ queueDelivery,
46
+ };
47
+ return {
48
+ deps,
49
+ approval: new ConnectionApproval(deps),
50
+ transport,
51
+ queueDelivery,
52
+ onDelivery,
53
+ };
54
+ }
55
+ describe("ConnectionApproval — handleConnectionRequest", () => {
56
+ it("queues a connection_request delivery event addressed to the owning peer", () => {
57
+ const h = makeHarness();
58
+ h.approval.handleConnectionRequest({ id: "conn-1" }, {
59
+ peerId: "remote-peer",
60
+ dataPort: 9000,
61
+ name: "remote-name",
62
+ fingerprint: "fp",
63
+ });
64
+ expect(h.queueDelivery).toHaveBeenCalledWith(OWNER_ID, {
65
+ type: "connection_request",
66
+ connectionId: "conn-1",
67
+ peerId: "remote-peer",
68
+ dataPort: 9000,
69
+ name: "remote-name",
70
+ fingerprint: "fp",
71
+ });
72
+ });
73
+ it("pushes the delivery live via onDelivery when a callback is registered", () => {
74
+ const h = makeHarness({ withOnDelivery: true });
75
+ h.approval.handleConnectionRequest({ id: "conn-1" }, {
76
+ peerId: "remote-peer",
77
+ dataPort: 9000,
78
+ name: "remote-name",
79
+ fingerprint: "fp",
80
+ });
81
+ expect(h.onDelivery).toHaveBeenCalledTimes(1);
82
+ });
83
+ });
84
+ describe("ConnectionApproval — acceptConnection", () => {
85
+ it("accepts a genuinely pending connection via the transport", async () => {
86
+ const h = makeHarness();
87
+ h.approval.handleConnectionRequest({ id: "conn-1" }, {
88
+ peerId: "remote-peer",
89
+ dataPort: 9000,
90
+ name: "remote-name",
91
+ fingerprint: "fp",
92
+ });
93
+ await h.approval.acceptConnection("conn-1");
94
+ expect(h.transport.acceptConnection).toHaveBeenCalledWith({ id: "conn-1" });
95
+ });
96
+ it("throws an error naming the exact unknown connection id, and never touches the transport", async () => {
97
+ const h = makeHarness();
98
+ await expect(h.approval.acceptConnection("no-such-conn")).rejects.toThrow("No pending connection no-such-conn");
99
+ expect(h.transport.acceptConnection).not.toHaveBeenCalled();
100
+ });
101
+ });
102
+ describe("ConnectionApproval — rejectConnection", () => {
103
+ it("rejects a genuinely pending connection via the transport with the given reason", async () => {
104
+ const h = makeHarness();
105
+ h.approval.handleConnectionRequest({ id: "conn-1" }, {
106
+ peerId: "remote-peer",
107
+ dataPort: 9000,
108
+ name: "remote-name",
109
+ fingerprint: "fp",
110
+ });
111
+ await h.approval.rejectConnection("conn-1", "not now");
112
+ expect(h.transport.rejectConnection).toHaveBeenCalledWith({ id: "conn-1" }, "not now");
113
+ });
114
+ it("throws an error naming the exact unknown connection id, and never touches the transport", async () => {
115
+ const h = makeHarness();
116
+ await expect(h.approval.rejectConnection("no-such-conn", "reason")).rejects.toThrow("No pending connection no-such-conn");
117
+ expect(h.transport.rejectConnection).not.toHaveBeenCalled();
118
+ });
119
+ });
120
+ describe("ConnectionApproval — connectToRemote", () => {
121
+ it("dials out with the local agent's real name and an empty fingerprint when the agent is known", async () => {
122
+ const h = makeHarness();
123
+ h.deps.agents.set(OWNER_ID, agent({ name: "real-name" }));
124
+ await h.approval.connectToRemote(REMOTE_HOST, REMOTE_PORT);
125
+ expect(h.transport.connectToRemote).toHaveBeenCalledWith(REMOTE_HOST, REMOTE_PORT, OWNER_ID, LOCAL_DATA_PORT, "real-name", "");
126
+ });
127
+ it("falls back to an empty name (not a placeholder) when the local agent isn't registered yet", async () => {
128
+ const h = makeHarness();
129
+ await h.approval.connectToRemote(REMOTE_HOST, REMOTE_PORT);
130
+ expect(h.transport.connectToRemote).toHaveBeenCalledWith(REMOTE_HOST, REMOTE_PORT, OWNER_ID, LOCAL_DATA_PORT, "", "");
131
+ });
132
+ it("does not reject the returned promise when the transport's own connect attempt is rejected", async () => {
133
+ const h = makeHarness();
134
+ h.transport.connectToRemote.mockRejectedValue(new Error("coordinator refused"));
135
+ await expect(h.approval.connectToRemote(REMOTE_HOST, REMOTE_PORT)).resolves.toBeUndefined();
136
+ });
137
+ });
138
+ describe("ConnectionApproval — startDataServerOnly", () => {
139
+ it("starts the data server, records this peer's own PeerInfo, and unrefs the transport", async () => {
140
+ const h = makeHarness();
141
+ await h.approval.startDataServerOnly();
142
+ expect(h.transport.startDataServer).toHaveBeenCalledTimes(1);
143
+ expect(h.deps.peerInfo.get(OWNER_ID)).toEqual({
144
+ id: OWNER_ID,
145
+ port: LOCAL_DATA_PORT,
146
+ startedAt: STARTED_AT,
147
+ });
148
+ expect(h.transport.unref).toHaveBeenCalledTimes(1);
149
+ });
150
+ });
151
+ describe("ConnectionApproval — listPendingConnections", () => {
152
+ it("lists every still-pending inbound connection with its full request detail", () => {
153
+ const h = makeHarness();
154
+ h.approval.handleConnectionRequest({ id: "conn-1" }, { peerId: "peer-a", dataPort: 1, name: "a", fingerprint: "fp-a" });
155
+ h.approval.handleConnectionRequest({ id: "conn-2" }, { peerId: "peer-b", dataPort: 2, name: "b", fingerprint: "fp-b" });
156
+ expect(h.approval.listPendingConnections()).toEqual([
157
+ {
158
+ connectionId: "conn-1",
159
+ peerId: "peer-a",
160
+ dataPort: 1,
161
+ name: "a",
162
+ fingerprint: "fp-a",
163
+ },
164
+ {
165
+ connectionId: "conn-2",
166
+ peerId: "peer-b",
167
+ dataPort: 2,
168
+ name: "b",
169
+ fingerprint: "fp-b",
170
+ },
171
+ ]);
172
+ });
173
+ it("no longer lists a connection once it's been accepted or rejected", async () => {
174
+ const h = makeHarness();
175
+ h.approval.handleConnectionRequest({ id: "conn-1" }, { peerId: "peer-a", dataPort: 1, name: "a", fingerprint: "fp-a" });
176
+ await h.approval.acceptConnection("conn-1");
177
+ expect(h.approval.listPendingConnections()).toEqual([]);
178
+ });
179
+ });
180
+ //# sourceMappingURL=connection-approval.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-approval.test.js","sourceRoot":"","sources":["../../src/test/connection-approval.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.10",
3
+ "version": "2.21.12",
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",