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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Integration test for issue #28: a bridge restarted with a persisted identity must be push-delivered the events that accumulated while its process was down, not just find them in synced history.
3
+ *
4
+ * Peer B goes away; A sends a room message while B is down; B restarts in the same identity slot and must fire onDelivery for the missed message.
5
+ */
6
+ import * as assert from "node:assert/strict";
7
+ import * as fs from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import path from "node:path";
10
+ import { MeshStore } from "../core/mesh-store.js";
11
+ import { TlsTransport } from "../core/tls-transport.js";
12
+ import { generateIdentity } from "../core/identity.js";
13
+ import { loadOrCreateIdentity, releaseIdentityLock, } from "../core/identity-store.js";
14
+ const TEST_PORT = 19896;
15
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16
+ /** A peer wired like a real bridge: TLS transport, fingerprint peer ID. */
17
+ function makePeer(identity) {
18
+ const store = new MeshStore(TEST_PORT);
19
+ store.peerId = identity.fingerprint;
20
+ store.setTransport(new TlsTransport(store.events, identity));
21
+ const deliveries = [];
22
+ return { store, deliveries };
23
+ }
24
+ /** Poll until the predicate holds, or fail with the message. */
25
+ async function waitFor(what, check) {
26
+ for (let i = 0; i < 20; i++) {
27
+ if (await check())
28
+ return;
29
+ await sleep(100);
30
+ }
31
+ assert.ok(false, `timed out waiting for ${what}`);
32
+ }
33
+ async function main() {
34
+ const dir = fs.mkdtempSync(path.join(tmpdir(), "agent-comms-downtime-"));
35
+ const slot = { harness: "pi", cwd: "/tmp/project", dir };
36
+ // A is a normal ephemeral bridge that stays up throughout.
37
+ const a = makePeer(generateIdentity());
38
+ await a.store.init();
39
+ await a.store.registerAgent({
40
+ name: "peer-a",
41
+ harness: "claude-code",
42
+ cwd: "/tmp/a",
43
+ pid: process.pid,
44
+ visibility: "visible",
45
+ tags: [],
46
+ });
47
+ await a.store.createRoom({
48
+ name: "downtime",
49
+ type: "public",
50
+ owner: a.store.peerId,
51
+ description: "issue 28 acceptance",
52
+ });
53
+ await sleep(200);
54
+ // B joins with a persisted identity and becomes a room member.
55
+ const identityB = loadOrCreateIdentity(slot);
56
+ const b1 = makePeer(identityB);
57
+ await b1.store.init();
58
+ // Registration must wait for the TLS data connections to establish (#23).
59
+ await sleep(300);
60
+ await b1.store.registerAgent({
61
+ name: "peer-b",
62
+ harness: "pi",
63
+ cwd: "/tmp/project",
64
+ pid: process.pid,
65
+ visibility: "visible",
66
+ tags: [],
67
+ });
68
+ await waitFor("the room to reach the joiner", async () => {
69
+ const rooms = await b1.store.listRooms(b1.store.peerId);
70
+ return rooms.some((room) => room.id === "downtime");
71
+ });
72
+ await b1.store.joinRoom("downtime", b1.store.peerId);
73
+ await waitFor("membership to reach the sender", async () => {
74
+ const room = await a.store.getRoom("downtime");
75
+ return room?.members.includes(b1.store.peerId) === true;
76
+ });
77
+ // B goes down.
78
+ await b1.store.shutdown();
79
+ await sleep(200);
80
+ // A sends a room message while B is down.
81
+ await a.store.sendRoomMessage("downtime", a.store.peerId, "while you were down");
82
+ await sleep(200);
83
+ // B restarts in the same slot: same identity, same agent ID.
84
+ const identityB2 = loadOrCreateIdentity(slot);
85
+ assert.equal(identityB2.fingerprint, identityB.fingerprint);
86
+ const b2 = makePeer(identityB2);
87
+ b2.store.onDelivery = (_id, ev) => {
88
+ b2.deliveries.push(ev);
89
+ };
90
+ await b2.store.init();
91
+ await b2.store.registerAgent({
92
+ name: "peer-b",
93
+ harness: "pi",
94
+ cwd: "/tmp/project",
95
+ pid: process.pid,
96
+ visibility: "visible",
97
+ tags: [],
98
+ });
99
+ // The message sent during downtime must push to the restarted bridge.
100
+ await waitFor("the downtime message to push to the restarted bridge", async () => b2.deliveries.some((ev) => ev.type === "room_message" &&
101
+ ev.message.content === "while you were down"));
102
+ await b2.store.shutdown();
103
+ await a.store.shutdown();
104
+ releaseIdentityLock(slot);
105
+ console.log("✓ downtime messages push-deliver on restart");
106
+ }
107
+ main().catch((err) => {
108
+ console.error("Test failed:", err);
109
+ process.exitCode = 1;
110
+ // The sequence above keeps mesh handles open when it fails partway; exit explicitly so a failure cannot hang the runner.
111
+ process.exit(1);
112
+ });
113
+ //# sourceMappingURL=downtime-replay.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downtime-replay.integration.test.js","sourceRoot":"","sources":["../../src/test/downtime-replay.integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for downtime delivery replay (#28): events that accumulated in peers' delivery queues while a target's process was down fire onDelivery when the target applies its first snapshot, without transport involvement.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=downtime-replay.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downtime-replay.test.d.ts","sourceRoot":"","sources":["../../src/test/downtime-replay.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Unit tests for downtime delivery replay (#28): events that accumulated in peers' delivery queues while a target's process was down fire onDelivery when the target applies its first snapshot, without transport involvement.
3
+ */
4
+ import * as assert from "node:assert/strict";
5
+ import { test } from "node:test";
6
+ import { MeshStore } from "../core/mesh-store.js";
7
+ /** A wire-accurate snapshot: production always applies parsed (cloned) state. */
8
+ function snapshotOf(store) {
9
+ return structuredClone(store.serialise());
10
+ }
11
+ /** A local-only store: no transport start, so no ports and no flake. */
12
+ function makeStore() {
13
+ return new MeshStore();
14
+ }
15
+ void test("events queued while the target was down replay on its first snapshot", async () => {
16
+ const sender = makeStore();
17
+ const author = await sender.registerAgent({
18
+ name: "author",
19
+ harness: "pi",
20
+ cwd: "/tmp/p",
21
+ pid: process.pid,
22
+ visibility: "visible",
23
+ tags: [],
24
+ });
25
+ // The target exists on the mesh (known to the sender) but its process is "down": modelled by only ever syncing snapshots into a future store.
26
+ const target = makeStore();
27
+ const targetAgent = await target.registerAgent({
28
+ name: "target",
29
+ harness: "claude-code",
30
+ cwd: "/tmp/t",
31
+ pid: process.pid,
32
+ visibility: "visible",
33
+ tags: [],
34
+ });
35
+ // Make the sender aware of the target, then take the target's store away.
36
+ sender.applyStateSync(target.serialise());
37
+ await sender.createRoom({
38
+ name: "room",
39
+ type: "public",
40
+ owner: author.id,
41
+ description: "x",
42
+ });
43
+ await sender.joinRoom("room", targetAgent.id);
44
+ // While the target is down, a room message is sent to it.
45
+ await sender.sendRoomMessage("room", author.id, "while you were away");
46
+ const pending = snapshotOf(sender).deliveryQueues[targetAgent.id];
47
+ assert.ok(pending !== undefined && pending.length > 0);
48
+ // The target returns (fresh process, same agent id) and receives the sender's snapshot: the pending event must fire onDelivery.
49
+ const returned = makeStore();
50
+ const deliveries = [];
51
+ returned.onDelivery = (_id, ev) => {
52
+ deliveries.push(ev);
53
+ };
54
+ returned.peerId = targetAgent.id;
55
+ returned.applyStateSync(snapshotOf(sender));
56
+ assert.equal(deliveries.some((ev) => ev.type === "room_message" &&
57
+ ev.message.content === "while you were away"), true);
58
+ // A second snapshot of the same state does not duplicate any push (the
59
+ // join notification replays too, exactly once).
60
+ returned.applyStateSync(snapshotOf(sender));
61
+ assert.equal(deliveries.filter((ev) => ev.type === "room_message" &&
62
+ ev.message.content === "while you were away").length, 1);
63
+ assert.equal(deliveries.filter((ev) => ev.type === "room_members").length, 1);
64
+ });
65
+ void test("a queue is bounded oldest-first so downtime cannot grow it without limit", async () => {
66
+ const sender = makeStore();
67
+ const author = await sender.registerAgent({
68
+ name: "author",
69
+ harness: "pi",
70
+ cwd: "/tmp/p",
71
+ pid: process.pid,
72
+ visibility: "visible",
73
+ tags: [],
74
+ });
75
+ const target = makeStore();
76
+ const targetAgent = await target.registerAgent({
77
+ name: "target",
78
+ harness: "claude-code",
79
+ cwd: "/tmp/t",
80
+ pid: process.pid,
81
+ visibility: "visible",
82
+ tags: [],
83
+ });
84
+ sender.applyStateSync(target.serialise());
85
+ await sender.createRoom({
86
+ name: "room",
87
+ type: "public",
88
+ owner: author.id,
89
+ description: "x",
90
+ });
91
+ await sender.joinRoom("room", targetAgent.id);
92
+ for (let i = 0; i < 120; i++) {
93
+ await sender.sendRoomMessage("room", author.id, `msg-${String(i)}`);
94
+ }
95
+ const queued = snapshotOf(sender).deliveryQueues[targetAgent.id] ?? [];
96
+ assert.equal(queued.length, 100);
97
+ assert.equal(queued[0]?.type === "room_message" &&
98
+ queued[0].message.content === "msg-20", true);
99
+ });
100
+ //# sourceMappingURL=downtime-replay.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downtime-replay.test.js","sourceRoot":"","sources":["../../src/test/downtime-replay.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for state_sync convergence (#27): entities that changed while a peer held a stale copy must converge on the fresher revision, in both directions — a stale holder heals when it receives a fresher snapshot, and a current holder rejects a stale snapshot instead of regressing.
3
+ *
4
+ * Also covers the append-only history merge: unseen messages are added and read receipts are unioned. These tests exercise the merge seam directly (no transport): the stores never connect, snapshots are exchanged through serialise() / applyStateSync().
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=state-sync-convergence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-sync-convergence.test.d.ts","sourceRoot":"","sources":["../../src/test/state-sync-convergence.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Unit tests for state_sync convergence (#27): entities that changed while a peer held a stale copy must converge on the fresher revision, in both directions — a stale holder heals when it receives a fresher snapshot, and a current holder rejects a stale snapshot instead of regressing.
3
+ *
4
+ * Also covers the append-only history merge: unseen messages are added and read receipts are unioned. These tests exercise the merge seam directly (no transport): the stores never connect, snapshots are exchanged through serialise() / applyStateSync().
5
+ */
6
+ import * as assert from "node:assert/strict";
7
+ import { test } from "node:test";
8
+ import { MeshStore } from "../core/mesh-store.js";
9
+ /** A local-only store: no transport start, so no ports and no flake. */
10
+ function makeStore() {
11
+ return new MeshStore();
12
+ }
13
+ function snapshotOf(store) {
14
+ return structuredClone(store.serialise());
15
+ }
16
+ void test("a stale holder converges when a fresher snapshot arrives", async () => {
17
+ const a = makeStore();
18
+ const b = makeStore();
19
+ const agent = await a.registerAgent({
20
+ name: "old-name",
21
+ harness: "pi",
22
+ cwd: "/tmp/p",
23
+ pid: process.pid,
24
+ visibility: "visible",
25
+ tags: [],
26
+ });
27
+ // B holds the pre-rename copy.
28
+ b.applyStateSync(snapshotOf(a));
29
+ assert.equal((await b.getAgent(agent.id))?.name, "old-name");
30
+ // A renames while B is away; B heals on A's next snapshot.
31
+ await a.updateAgent(agent.id, { name: "new-name" });
32
+ b.applyStateSync(snapshotOf(a));
33
+ assert.equal((await b.getAgent(agent.id))?.name, "new-name");
34
+ });
35
+ void test("a current holder rejects a stale snapshot instead of regressing", async () => {
36
+ const a = makeStore();
37
+ const b = makeStore();
38
+ const agent = await a.registerAgent({
39
+ name: "old-name",
40
+ harness: "pi",
41
+ cwd: "/tmp/p",
42
+ pid: process.pid,
43
+ visibility: "visible",
44
+ tags: [],
45
+ });
46
+ // B is current at the post-rename revision.
47
+ await a.updateAgent(agent.id, { name: "new-name" });
48
+ b.applyStateSync(snapshotOf(a));
49
+ assert.equal((await b.getAgent(agent.id))?.name, "new-name");
50
+ // A returning peer that still holds the pre-rename snapshot cannot regress B, where the old add-only merge kept whatever arrived first.
51
+ const stale = snapshotOf(a);
52
+ const staleAgent = stale.agents[agent.id];
53
+ if (staleAgent === undefined)
54
+ throw new Error("agent missing from snapshot");
55
+ staleAgent.name = "old-name";
56
+ staleAgent.version -= 1;
57
+ b.applyStateSync(stale);
58
+ assert.equal((await b.getAgent(agent.id))?.name, "new-name");
59
+ });
60
+ void test("room membership changes converge and stale member lists are rejected", async () => {
61
+ const a = makeStore();
62
+ const b = makeStore();
63
+ const owner = await a.registerAgent({
64
+ name: "owner",
65
+ harness: "pi",
66
+ cwd: "/tmp/p",
67
+ pid: process.pid,
68
+ visibility: "visible",
69
+ tags: [],
70
+ });
71
+ const joiner = await b.registerAgent({
72
+ name: "joiner",
73
+ harness: "claude-code",
74
+ cwd: "/tmp/j",
75
+ pid: process.pid,
76
+ visibility: "visible",
77
+ tags: [],
78
+ });
79
+ await a.createRoom({
80
+ name: "room",
81
+ type: "public",
82
+ owner: owner.id,
83
+ description: "x",
84
+ });
85
+ // A holds the room before the join; the joiner lives on B, so the join
86
+ // mutates B's copy and syncs back (one MeshStore is one peer identity, so
87
+ // a second agent cannot be registered on A).
88
+ a.applyStateSync(snapshotOf(b));
89
+ b.applyStateSync(snapshotOf(a));
90
+ assert.equal((await a.getRoom("room"))?.members.includes(joiner.id), false);
91
+ await b.joinRoom("room", joiner.id);
92
+ a.applyStateSync(snapshotOf(b));
93
+ assert.equal((await a.getRoom("room"))?.members.includes(joiner.id), true);
94
+ // The joiner leaves; A (now current) must drop them — the union-only merge
95
+ // could never remove a leaver.
96
+ await b.leaveRoom("room", joiner.id);
97
+ a.applyStateSync(snapshotOf(b));
98
+ assert.equal((await a.getRoom("room"))?.members.includes(joiner.id), false);
99
+ // A stale member list that still contains them is rejected.
100
+ const stale = snapshotOf(b);
101
+ const staleRoom = stale.rooms.room;
102
+ if (staleRoom === undefined)
103
+ throw new Error("room missing from snapshot");
104
+ staleRoom.members.push(joiner.id);
105
+ staleRoom.version -= 1;
106
+ a.applyStateSync(stale);
107
+ assert.equal((await a.getRoom("room"))?.members.includes(joiner.id), false);
108
+ });
109
+ void test("history sync adds unseen messages and unions read receipts", async () => {
110
+ const a = makeStore();
111
+ const b = makeStore();
112
+ const agent = await a.registerAgent({
113
+ name: "sender",
114
+ harness: "pi",
115
+ cwd: "/tmp/p",
116
+ pid: process.pid,
117
+ visibility: "visible",
118
+ tags: [],
119
+ });
120
+ await a.createRoom({
121
+ name: "room",
122
+ type: "public",
123
+ owner: agent.id,
124
+ description: "x",
125
+ });
126
+ await a.sendRoomMessage("room", agent.id, "one");
127
+ b.applyStateSync(snapshotOf(a));
128
+ // While B is away, a second message arrives and the first gains a reader elsewhere on the mesh; B's next sync takes both.
129
+ await a.sendRoomMessage("room", agent.id, "two");
130
+ const fresh = snapshotOf(a);
131
+ const history = fresh.messages.room;
132
+ if (history?.[0] === undefined)
133
+ throw new Error("messages missing from snapshot");
134
+ history[0].readBy.push("reader-elsewhere");
135
+ b.applyStateSync(fresh);
136
+ const merged = await b.readRoomMessages("room");
137
+ assert.deepEqual(merged.map((m) => m.content), ["one", "two"]);
138
+ assert.equal(merged[0]?.readBy.includes("reader-elsewhere"), true);
139
+ });
140
+ //# sourceMappingURL=state-sync-convergence.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-sync-convergence.test.js","sourceRoot":"","sources":["../../src/test/state-sync-convergence.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": "1.25.4",
3
+ "version": "1.25.6",
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@10.33.0",
@@ -52,7 +52,7 @@
52
52
  "generate:server-json": "tsx scripts/sync-release-metadata.ts",
53
53
  "publish:mcp-registry": "tsx scripts/publish-mcp-registry.ts",
54
54
  "lint": "eslint .",
55
- "test": "node --test --test-concurrency=1 dist/test/coordinator-socket-error.integration.test.js dist/test/identity-store.test.js dist/test/identity-cert.test.js dist/test/broadcast-window.integration.test.js dist/test/identity-restart.integration.test.js dist/test/mesh-e2e.integration.test.js dist/test/ws-broadcast-window.integration.test.js dist/test/tls-transport.integration.test.js",
55
+ "test": "node --test --test-concurrency=1 dist/test/coordinator-socket-error.integration.test.js dist/test/identity-store.test.js dist/test/identity-cert.test.js dist/test/broadcast-window.integration.test.js dist/test/identity-restart.integration.test.js dist/test/mesh-e2e.integration.test.js dist/test/ws-broadcast-window.integration.test.js dist/test/tls-transport.integration.test.js dist/test/state-sync-convergence.test.js dist/test/downtime-replay.test.js dist/test/downtime-replay.integration.test.js",
56
56
  "test:visibility": "node --test dist/test/visibility.integration.test.js",
57
57
  "test:delivery": "node dist/test/delivery-receipt.runner.js",
58
58
  "test:all": "pnpm test && pnpm test:delivery",