agent-comms 1.28.0 → 1.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/core/comms-store.d.ts +4 -37
- package/dist/core/comms-store.d.ts.map +1 -1
- package/dist/core/comms-store.js +3 -3
- package/dist/core/mesh-store.d.ts +12 -3
- package/dist/core/mesh-store.d.ts.map +1 -1
- package/dist/core/mesh-store.js +52 -33
- package/dist/core/mesh-store.js.map +1 -1
- package/dist/core/store.d.ts +1 -27
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +0 -62
- package/dist/core/store.js.map +1 -1
- package/dist/core/tls-transport.d.ts +12 -9
- package/dist/core/tls-transport.d.ts.map +1 -1
- package/dist/core/tls-transport.js +62 -25
- package/dist/core/tls-transport.js.map +1 -1
- package/dist/core/tool.d.ts +34 -5
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +41 -0
- package/dist/core/tool.js.map +1 -1
- package/dist/core/wire-protocol.d.ts +3 -2
- package/dist/core/wire-protocol.d.ts.map +1 -1
- package/dist/core/wire-protocol.js +3 -2
- package/dist/core/wire-protocol.js.map +1 -1
- package/dist/test/approval.integration.test.js +320 -285
- package/dist/test/approval.integration.test.js.map +1 -1
- package/dist/test/become-coordinator-actual-port.integration.test.js +0 -19
- package/dist/test/become-coordinator-actual-port.integration.test.js.map +1 -1
- package/dist/test/coordinator-socket-error.integration.test.js +2 -0
- package/dist/test/coordinator-socket-error.integration.test.js.map +1 -1
- package/dist/test/delivery-receipt.helper.js +15 -0
- package/dist/test/delivery-receipt.helper.js.map +1 -1
- package/dist/test/downtime-replay.test.js +5 -2
- package/dist/test/downtime-replay.test.js.map +1 -1
- package/dist/test/federation.integration.test.js +2 -0
- package/dist/test/federation.integration.test.js.map +1 -1
- package/dist/test/listener-policy.integration.test.js +214 -158
- package/dist/test/listener-policy.integration.test.js.map +1 -1
- package/dist/test/mesh-e2e.integration.test.js +2 -0
- package/dist/test/mesh-e2e.integration.test.js.map +1 -1
- package/dist/test/mesh-smoke.integration.test.d.ts +2 -4
- package/dist/test/mesh-smoke.integration.test.d.ts.map +1 -1
- package/dist/test/mesh-smoke.integration.test.js +28 -5
- package/dist/test/mesh-smoke.integration.test.js.map +1 -1
- package/dist/test/state-sync-convergence.test.js +5 -2
- package/dist/test/state-sync-convergence.test.js.map +1 -1
- package/dist/test/test-transport.d.ts +8 -0
- package/dist/test/test-transport.d.ts.map +1 -0
- package/dist/test/test-transport.js +29 -0
- package/dist/test/test-transport.js.map +1 -0
- package/dist/test/visibility.integration.test.js +6 -0
- package/dist/test/visibility.integration.test.js.map +1 -1
- package/package.json +2 -2
- package/dist/core/tcp-transport.d.ts +0 -85
- package/dist/core/tcp-transport.d.ts.map +0 -1
- package/dist/core/tcp-transport.js +0 -659
- package/dist/core/tcp-transport.js.map +0 -1
- package/dist/core/ws-transport.d.ts +0 -79
- package/dist/core/ws-transport.d.ts.map +0 -1
- package/dist/core/ws-transport.js +0 -622
- package/dist/core/ws-transport.js.map +0 -1
- package/dist/test/ws-broadcast-window.integration.test.d.ts +0 -7
- package/dist/test/ws-broadcast-window.integration.test.d.ts.map +0 -1
- package/dist/test/ws-broadcast-window.integration.test.js +0 -94
- package/dist/test/ws-broadcast-window.integration.test.js.map +0 -1
|
@@ -11,6 +11,7 @@ import { test, describe } from "node:test";
|
|
|
11
11
|
import { MeshStore } from "../core/mesh-store.js";
|
|
12
12
|
import { CommsTool } from "../core/tool.js";
|
|
13
13
|
import { buildAction } from "../core/bridge.js";
|
|
14
|
+
import { waitFor, wireTestTransport } from "./test-transport.js";
|
|
14
15
|
/** Find a free port on localhost by binding to port 0. */
|
|
15
16
|
function findFreePort() {
|
|
16
17
|
return new Promise((resolve, reject) => {
|
|
@@ -40,322 +41,356 @@ describe("connection approval", () => {
|
|
|
40
41
|
const portB = portA + 100;
|
|
41
42
|
// Set up coordinator (store A)
|
|
42
43
|
const storeA = new MeshStore(portA);
|
|
43
|
-
|
|
44
|
-
storeA.onDelivery = (_id, event) => {
|
|
45
|
-
if (event.type === "connection_request") {
|
|
46
|
-
receivedRequests.push(event);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
await storeA.init();
|
|
50
|
-
await storeA.registerAgent({
|
|
51
|
-
name: "coordinator",
|
|
52
|
-
harness: "test",
|
|
53
|
-
cwd: "/test/a",
|
|
54
|
-
pid: process.pid,
|
|
55
|
-
visibility: "visible",
|
|
56
|
-
tags: [],
|
|
57
|
-
});
|
|
58
|
-
await sleep(100);
|
|
59
|
-
// Set up connecting peer (store B) that uses connectToRemote
|
|
44
|
+
wireTestTransport(storeA);
|
|
60
45
|
const storeB = new MeshStore(portB);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
46
|
+
wireTestTransport(storeB);
|
|
47
|
+
try {
|
|
48
|
+
const receivedRequests = [];
|
|
49
|
+
storeA.onDelivery = (_id, event) => {
|
|
50
|
+
if (event.type === "connection_request") {
|
|
51
|
+
receivedRequests.push(event);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
await storeA.init();
|
|
55
|
+
await storeA.registerAgent({
|
|
56
|
+
name: "coordinator",
|
|
57
|
+
harness: "test",
|
|
58
|
+
cwd: "/test/a",
|
|
59
|
+
pid: process.pid,
|
|
60
|
+
visibility: "visible",
|
|
61
|
+
tags: [],
|
|
62
|
+
});
|
|
63
|
+
await sleep(100);
|
|
64
|
+
// Set up connecting peer (store B) that uses connectToRemote
|
|
65
|
+
await storeB.startDataServerOnly();
|
|
66
|
+
await storeB.registerAgent({
|
|
67
|
+
name: "connector",
|
|
68
|
+
harness: "test",
|
|
69
|
+
cwd: "/test/b",
|
|
70
|
+
pid: process.pid,
|
|
71
|
+
visibility: "visible",
|
|
72
|
+
tags: [],
|
|
73
|
+
});
|
|
74
|
+
// Initiate connection request
|
|
75
|
+
storeB.connectToRemote("127.0.0.1", portA);
|
|
76
|
+
// Wait for the coordinator to receive the request
|
|
77
|
+
await waitFor(() => receivedRequests.length === 1, "coordinator receives the connection request");
|
|
78
|
+
// Coordinator should have received a connection_request event
|
|
79
|
+
assert.equal(receivedRequests.length, 1, "Coordinator should receive exactly one connection request");
|
|
80
|
+
const request = receivedRequests[0];
|
|
81
|
+
assert.equal(request?.type, "connection_request");
|
|
82
|
+
assert.ok(request?.connectionId, "Request should have a connectionId");
|
|
83
|
+
assert.equal(request?.peerId, storeB.peerId, "Request should contain connector's peer ID");
|
|
84
|
+
assert.equal(request?.name, "connector", "Request should contain connector's name");
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await storeB.shutdown();
|
|
88
|
+
await storeA.shutdown();
|
|
89
|
+
}
|
|
83
90
|
});
|
|
84
91
|
void test("accept establishes the peer connection", async () => {
|
|
85
92
|
const portA = await uniquePort();
|
|
86
93
|
const portB = portA + 100;
|
|
87
94
|
const storeA = new MeshStore(portA);
|
|
88
|
-
|
|
89
|
-
storeA.onDelivery = (_id, event) => {
|
|
90
|
-
if (event.type === "connection_request") {
|
|
91
|
-
receivedRequests.push(event);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
await storeA.init();
|
|
95
|
-
await storeA.registerAgent({
|
|
96
|
-
name: "coordinator",
|
|
97
|
-
harness: "test",
|
|
98
|
-
cwd: "/test/a",
|
|
99
|
-
pid: process.pid,
|
|
100
|
-
visibility: "visible",
|
|
101
|
-
tags: [],
|
|
102
|
-
});
|
|
103
|
-
await sleep(100);
|
|
95
|
+
wireTestTransport(storeA);
|
|
104
96
|
const storeB = new MeshStore(portB);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
wireTestTransport(storeB);
|
|
98
|
+
try {
|
|
99
|
+
const receivedRequests = [];
|
|
100
|
+
storeA.onDelivery = (_id, event) => {
|
|
101
|
+
if (event.type === "connection_request") {
|
|
102
|
+
receivedRequests.push(event);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
await storeA.init();
|
|
106
|
+
await storeA.registerAgent({
|
|
107
|
+
name: "coordinator",
|
|
108
|
+
harness: "test",
|
|
109
|
+
cwd: "/test/a",
|
|
110
|
+
pid: process.pid,
|
|
111
|
+
visibility: "visible",
|
|
112
|
+
tags: [],
|
|
113
|
+
});
|
|
114
|
+
await sleep(100);
|
|
115
|
+
await storeB.startDataServerOnly();
|
|
116
|
+
await storeB.registerAgent({
|
|
117
|
+
name: "connector",
|
|
118
|
+
harness: "test",
|
|
119
|
+
cwd: "/test/b",
|
|
120
|
+
pid: process.pid,
|
|
121
|
+
visibility: "visible",
|
|
122
|
+
tags: [],
|
|
123
|
+
});
|
|
124
|
+
storeB.connectToRemote("127.0.0.1", portA);
|
|
125
|
+
await waitFor(() => receivedRequests.length === 1, "coordinator receives the connection request");
|
|
126
|
+
const request = receivedRequests[0];
|
|
127
|
+
assert.ok(request?.connectionId);
|
|
128
|
+
// Accept the connection
|
|
129
|
+
await storeA.acceptConnection(request.connectionId);
|
|
130
|
+
// State sync (peer_list -> connectToPeer -> state_sync -> handlePeerConnected) is a real TLS round trip, not instantaneous -- poll rather than assume any fixed delay is enough under a loaded CI runner.
|
|
131
|
+
await waitFor(() => storeA.serialise().agents[storeB.peerId] !== undefined, "coordinator sees the connector agent");
|
|
132
|
+
await waitFor(() => storeB.serialise().agents[storeA.peerId] !== undefined, "connector sees the coordinator agent");
|
|
133
|
+
const agentsA = await storeA.listAgents(storeA.peerId);
|
|
134
|
+
const agentsB = await storeB.listAgents(storeB.peerId);
|
|
135
|
+
assert.ok(agentsA.some((a) => a.id === storeB.peerId), "Coordinator should see the connector agent");
|
|
136
|
+
assert.ok(agentsB.some((a) => a.id === storeA.peerId), "Connector should see the coordinator agent");
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await storeB.shutdown();
|
|
140
|
+
await storeA.shutdown();
|
|
141
|
+
}
|
|
130
142
|
});
|
|
131
143
|
void test("reject closes with reason", async () => {
|
|
132
144
|
const portA = await uniquePort();
|
|
133
145
|
const portB = portA + 100;
|
|
134
146
|
const storeA = new MeshStore(portA);
|
|
135
|
-
|
|
136
|
-
storeA.onDelivery = (_id, event) => {
|
|
137
|
-
if (event.type === "connection_request") {
|
|
138
|
-
receivedRequests.push(event);
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
await storeA.init();
|
|
142
|
-
await storeA.registerAgent({
|
|
143
|
-
name: "coordinator",
|
|
144
|
-
harness: "test",
|
|
145
|
-
cwd: "/test/a",
|
|
146
|
-
pid: process.pid,
|
|
147
|
-
visibility: "visible",
|
|
148
|
-
tags: [],
|
|
149
|
-
});
|
|
150
|
-
await sleep(100);
|
|
147
|
+
wireTestTransport(storeA);
|
|
151
148
|
const storeB = new MeshStore(portB);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
wireTestTransport(storeB);
|
|
150
|
+
try {
|
|
151
|
+
const receivedRequests = [];
|
|
152
|
+
storeA.onDelivery = (_id, event) => {
|
|
153
|
+
if (event.type === "connection_request") {
|
|
154
|
+
receivedRequests.push(event);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
await storeA.init();
|
|
158
|
+
await storeA.registerAgent({
|
|
159
|
+
name: "coordinator",
|
|
160
|
+
harness: "test",
|
|
161
|
+
cwd: "/test/a",
|
|
162
|
+
pid: process.pid,
|
|
163
|
+
visibility: "visible",
|
|
164
|
+
tags: [],
|
|
165
|
+
});
|
|
166
|
+
await sleep(100);
|
|
167
|
+
await storeB.startDataServerOnly();
|
|
168
|
+
await storeB.registerAgent({
|
|
169
|
+
name: "connector",
|
|
170
|
+
harness: "test",
|
|
171
|
+
cwd: "/test/b",
|
|
172
|
+
pid: process.pid,
|
|
173
|
+
visibility: "visible",
|
|
174
|
+
tags: [],
|
|
175
|
+
});
|
|
176
|
+
storeB.connectToRemote("127.0.0.1", portA);
|
|
177
|
+
await waitFor(() => receivedRequests.length === 1, "coordinator receives the connection request");
|
|
178
|
+
assert.equal(receivedRequests.length, 1, "Coordinator should receive a connection request");
|
|
179
|
+
const request = receivedRequests[0];
|
|
180
|
+
assert.ok(request?.connectionId);
|
|
181
|
+
// Reject the connection
|
|
182
|
+
await storeA.rejectConnection(request.connectionId, "unauthorised");
|
|
183
|
+
// Give the rejection time to propagate -- there is no positive event to poll for here (the assertion below proves an absence), so a fixed wait is the right shape, unlike the accept-flow tests above.
|
|
184
|
+
await sleep(200);
|
|
185
|
+
// Verify the coordinator no longer sees the connector agent
|
|
186
|
+
const agentsA = await storeA.listAgents(storeA.peerId);
|
|
187
|
+
assert.ok(!agentsA.some((a) => a.id === storeB.peerId), "Rejected peer should not appear in agent list");
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
await storeB.shutdown();
|
|
191
|
+
await storeA.shutdown();
|
|
192
|
+
}
|
|
175
193
|
});
|
|
176
194
|
void test("mesh_pending lists pending connections", async () => {
|
|
177
195
|
const portA = await uniquePort();
|
|
178
196
|
const portB = portA + 100;
|
|
179
197
|
const storeA = new MeshStore(portA);
|
|
180
|
-
storeA
|
|
181
|
-
await storeA.init();
|
|
182
|
-
await storeA.registerAgent({
|
|
183
|
-
name: "coordinator",
|
|
184
|
-
harness: "test",
|
|
185
|
-
cwd: "/test/a",
|
|
186
|
-
pid: process.pid,
|
|
187
|
-
visibility: "visible",
|
|
188
|
-
tags: [],
|
|
189
|
-
});
|
|
190
|
-
await sleep(100);
|
|
198
|
+
wireTestTransport(storeA);
|
|
191
199
|
const storeB = new MeshStore(portB);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
200
|
+
wireTestTransport(storeB);
|
|
201
|
+
try {
|
|
202
|
+
storeA.onDelivery = () => { };
|
|
203
|
+
await storeA.init();
|
|
204
|
+
await storeA.registerAgent({
|
|
205
|
+
name: "coordinator",
|
|
206
|
+
harness: "test",
|
|
207
|
+
cwd: "/test/a",
|
|
208
|
+
pid: process.pid,
|
|
209
|
+
visibility: "visible",
|
|
210
|
+
tags: [],
|
|
211
|
+
});
|
|
212
|
+
await sleep(100);
|
|
213
|
+
await storeB.startDataServerOnly();
|
|
214
|
+
await storeB.registerAgent({
|
|
215
|
+
name: "connector",
|
|
216
|
+
harness: "test",
|
|
217
|
+
cwd: "/test/b",
|
|
218
|
+
pid: process.pid,
|
|
219
|
+
visibility: "visible",
|
|
220
|
+
tags: [],
|
|
221
|
+
});
|
|
222
|
+
// No pending connections initially
|
|
223
|
+
let pending = storeA.listPendingConnections();
|
|
224
|
+
assert.equal(pending.length, 0, "Should have no pending connections initially");
|
|
225
|
+
// Initiate connection request
|
|
226
|
+
storeB.connectToRemote("127.0.0.1", portA);
|
|
227
|
+
await waitFor(() => storeA.listPendingConnections().length === 1, "coordinator sees the pending connection");
|
|
228
|
+
// Should now have one pending connection
|
|
229
|
+
pending = storeA.listPendingConnections();
|
|
230
|
+
assert.equal(pending.length, 1, "Should have one pending connection");
|
|
231
|
+
assert.equal(pending[0]?.name, "connector", "Pending connection should show connector name");
|
|
232
|
+
assert.equal(pending[0]?.peerId, storeB.peerId, "Pending connection should show connector peer ID");
|
|
233
|
+
// Accept to clean up
|
|
234
|
+
const connectionId = pending[0]?.connectionId;
|
|
235
|
+
assert.ok(connectionId);
|
|
236
|
+
await storeA.acceptConnection(connectionId);
|
|
237
|
+
// No more pending connections after acceptance
|
|
238
|
+
await waitFor(() => storeA.listPendingConnections().length === 0, "pending connection is cleared after accept");
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
await storeB.shutdown();
|
|
242
|
+
await storeA.shutdown();
|
|
243
|
+
}
|
|
223
244
|
});
|
|
224
245
|
void test("tool handles mesh_connect/mesh_accept/mesh_reject/mesh_pending actions", async () => {
|
|
225
246
|
const portA = await uniquePort();
|
|
226
247
|
const portB = portA + 100;
|
|
227
248
|
const storeA = new MeshStore(portA);
|
|
228
|
-
storeA
|
|
229
|
-
await storeA.init();
|
|
230
|
-
await storeA.registerAgent({
|
|
231
|
-
name: "coordinator",
|
|
232
|
-
harness: "test",
|
|
233
|
-
cwd: "/test/a",
|
|
234
|
-
pid: process.pid,
|
|
235
|
-
visibility: "visible",
|
|
236
|
-
tags: [],
|
|
237
|
-
});
|
|
238
|
-
const toolA = new CommsTool(storeA);
|
|
239
|
-
await sleep(100);
|
|
249
|
+
wireTestTransport(storeA);
|
|
240
250
|
const storeB = new MeshStore(portB);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
251
|
+
wireTestTransport(storeB);
|
|
252
|
+
try {
|
|
253
|
+
storeA.onDelivery = () => { };
|
|
254
|
+
await storeA.init();
|
|
255
|
+
await storeA.registerAgent({
|
|
256
|
+
name: "coordinator",
|
|
257
|
+
harness: "test",
|
|
258
|
+
cwd: "/test/a",
|
|
259
|
+
pid: process.pid,
|
|
260
|
+
visibility: "visible",
|
|
261
|
+
tags: [],
|
|
262
|
+
});
|
|
263
|
+
const toolA = new CommsTool(storeA);
|
|
264
|
+
await sleep(100);
|
|
265
|
+
await storeB.startDataServerOnly();
|
|
266
|
+
await storeB.registerAgent({
|
|
267
|
+
name: "connector",
|
|
268
|
+
harness: "test",
|
|
269
|
+
cwd: "/test/b",
|
|
270
|
+
pid: process.pid,
|
|
271
|
+
visibility: "visible",
|
|
272
|
+
tags: [],
|
|
273
|
+
});
|
|
274
|
+
const toolB = new CommsTool(storeB);
|
|
275
|
+
// Use buildAction to construct the mesh_connect action
|
|
276
|
+
const connectAction = buildAction({
|
|
277
|
+
action: "mesh_connect",
|
|
278
|
+
host: "127.0.0.1",
|
|
279
|
+
port: portA,
|
|
280
|
+
});
|
|
281
|
+
// Give coordinator time to settle
|
|
282
|
+
await sleep(200);
|
|
283
|
+
// Verify coordinator is listening
|
|
284
|
+
const listeners = storeA.listListeners();
|
|
285
|
+
assert.ok(listeners.length > 0, `Coordinator should have listeners, got ${listeners.length}`);
|
|
286
|
+
assert.equal(listeners[0]?.port, portA, `Coordinator should be on port ${portA}`);
|
|
287
|
+
// B initiates the connection via the tool
|
|
288
|
+
const connectResult = await toolB.handle({
|
|
289
|
+
agentId: storeB.peerId,
|
|
290
|
+
harness: "test",
|
|
291
|
+
cwd: "/test/b",
|
|
292
|
+
pid: process.pid,
|
|
293
|
+
}, connectAction);
|
|
294
|
+
assert.ok(!connectResult.isError, `mesh_connect should succeed: ${connectResult.content}`);
|
|
295
|
+
await waitFor(() => storeA.listPendingConnections().length === 1, "coordinator sees the pending connection");
|
|
296
|
+
// A checks pending connections via the tool
|
|
297
|
+
const pendingAction = buildAction({ action: "mesh_pending" });
|
|
298
|
+
const pendingResult = await toolA.handle({
|
|
299
|
+
agentId: storeA.peerId,
|
|
300
|
+
harness: "test",
|
|
301
|
+
cwd: "/test/a",
|
|
302
|
+
pid: process.pid,
|
|
303
|
+
}, pendingAction);
|
|
304
|
+
assert.ok(!pendingResult.isError, `mesh_pending should succeed: ${pendingResult.content}`);
|
|
305
|
+
assert.ok(pendingResult.content.includes("connector"), "Pending list should show connector name");
|
|
306
|
+
// Extract connectionId from the pending connections list
|
|
307
|
+
const pendingConns = storeA.listPendingConnections();
|
|
308
|
+
assert.equal(pendingConns.length, 1, "Should have one pending connection");
|
|
309
|
+
const connectionId = pendingConns[0]?.connectionId;
|
|
310
|
+
assert.ok(connectionId);
|
|
311
|
+
// A accepts the connection via the tool
|
|
312
|
+
const acceptAction = buildAction({
|
|
313
|
+
action: "mesh_accept",
|
|
314
|
+
connectionId,
|
|
315
|
+
});
|
|
316
|
+
const acceptResult = await toolA.handle({
|
|
317
|
+
agentId: storeA.peerId,
|
|
318
|
+
harness: "test",
|
|
319
|
+
cwd: "/test/a",
|
|
320
|
+
pid: process.pid,
|
|
321
|
+
}, acceptAction);
|
|
322
|
+
assert.ok(!acceptResult.isError, `mesh_accept should succeed: ${acceptResult.content}`);
|
|
323
|
+
// State sync is a real TLS round trip, not instantaneous -- poll rather than assume any fixed delay is enough under a loaded CI runner.
|
|
324
|
+
await waitFor(() => storeA.serialise().agents[storeB.peerId] !== undefined, "A sees B after accept");
|
|
325
|
+
await waitFor(() => storeB.serialise().agents[storeA.peerId] !== undefined, "B sees A after accept");
|
|
326
|
+
// Verify both see each other
|
|
327
|
+
const agentsA = await storeA.listAgents(storeA.peerId);
|
|
328
|
+
const agentsB = await storeB.listAgents(storeB.peerId);
|
|
329
|
+
assert.ok(agentsA.some((a) => a.id === storeB.peerId), "A should see B after accept");
|
|
330
|
+
assert.ok(agentsB.some((a) => a.id === storeA.peerId), "B should see A after accept");
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
await storeB.shutdown();
|
|
334
|
+
await storeA.shutdown();
|
|
335
|
+
}
|
|
307
336
|
});
|
|
308
337
|
void test("tool mesh_reject returns error message", async () => {
|
|
309
338
|
const portA = await uniquePort();
|
|
310
339
|
const portB = portA + 100;
|
|
311
340
|
const storeA = new MeshStore(portA);
|
|
312
|
-
storeA
|
|
313
|
-
await storeA.init();
|
|
314
|
-
await storeA.registerAgent({
|
|
315
|
-
name: "coordinator",
|
|
316
|
-
harness: "test",
|
|
317
|
-
cwd: "/test/a",
|
|
318
|
-
pid: process.pid,
|
|
319
|
-
visibility: "visible",
|
|
320
|
-
tags: [],
|
|
321
|
-
});
|
|
322
|
-
const toolA = new CommsTool(storeA);
|
|
323
|
-
await sleep(100);
|
|
341
|
+
wireTestTransport(storeA);
|
|
324
342
|
const storeB = new MeshStore(portB);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
343
|
+
wireTestTransport(storeB);
|
|
344
|
+
try {
|
|
345
|
+
storeA.onDelivery = () => { };
|
|
346
|
+
await storeA.init();
|
|
347
|
+
await storeA.registerAgent({
|
|
348
|
+
name: "coordinator",
|
|
349
|
+
harness: "test",
|
|
350
|
+
cwd: "/test/a",
|
|
351
|
+
pid: process.pid,
|
|
352
|
+
visibility: "visible",
|
|
353
|
+
tags: [],
|
|
354
|
+
});
|
|
355
|
+
const toolA = new CommsTool(storeA);
|
|
356
|
+
await sleep(100);
|
|
357
|
+
await storeB.startDataServerOnly();
|
|
358
|
+
await storeB.registerAgent({
|
|
359
|
+
name: "connector",
|
|
360
|
+
harness: "test",
|
|
361
|
+
cwd: "/test/b",
|
|
362
|
+
pid: process.pid,
|
|
363
|
+
visibility: "visible",
|
|
364
|
+
tags: [],
|
|
365
|
+
});
|
|
366
|
+
// B connects
|
|
367
|
+
storeB.connectToRemote("127.0.0.1", portA);
|
|
368
|
+
await waitFor(() => storeA.listPendingConnections().length === 1, "coordinator sees the pending connection");
|
|
369
|
+
const pendingConns = storeA.listPendingConnections();
|
|
370
|
+
const connectionId = pendingConns[0]?.connectionId;
|
|
371
|
+
assert.ok(connectionId);
|
|
372
|
+
// A rejects via the tool
|
|
373
|
+
const rejectAction = buildAction({
|
|
374
|
+
action: "mesh_reject",
|
|
375
|
+
connectionId,
|
|
376
|
+
reason: "not allowed",
|
|
377
|
+
});
|
|
378
|
+
const rejectResult = await toolA.handle({
|
|
379
|
+
agentId: storeA.peerId,
|
|
380
|
+
harness: "test",
|
|
381
|
+
cwd: "/test/a",
|
|
382
|
+
pid: process.pid,
|
|
383
|
+
}, rejectAction);
|
|
384
|
+
assert.ok(!rejectResult.isError, `mesh_reject should succeed: ${rejectResult.content}`);
|
|
385
|
+
assert.ok(rejectResult.content.includes("not allowed"), "Result should include the reason");
|
|
386
|
+
// Verify rejection took effect
|
|
387
|
+
const pending = storeA.listPendingConnections();
|
|
388
|
+
assert.equal(pending.length, 0, "No pending connections after reject");
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
await storeB.shutdown();
|
|
392
|
+
await storeA.shutdown();
|
|
393
|
+
}
|
|
359
394
|
});
|
|
360
395
|
});
|
|
361
396
|
//# sourceMappingURL=approval.integration.test.js.map
|