agent-comms 2.7.0 → 2.9.0
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/bridge-mesh.d.ts.map +1 -1
- package/dist/core/bridge-mesh.js +3 -0
- package/dist/core/bridge-mesh.js.map +1 -1
- package/dist/core/mesh-store.d.ts +18 -1
- package/dist/core/mesh-store.d.ts.map +1 -1
- package/dist/core/mesh-store.js +118 -22
- package/dist/core/mesh-store.js.map +1 -1
- package/dist/core/random-id.d.ts +3 -0
- package/dist/core/random-id.d.ts.map +1 -0
- package/dist/core/random-id.js +7 -0
- package/dist/core/random-id.js.map +1 -0
- package/dist/test/dm-admission.integration.test.d.ts +7 -0
- package/dist/test/dm-admission.integration.test.d.ts.map +1 -0
- package/dist/test/dm-admission.integration.test.js +95 -0
- package/dist/test/dm-admission.integration.test.js.map +1 -0
- package/dist/test/downtime-replay.integration.test.js +2 -0
- package/dist/test/downtime-replay.integration.test.js.map +1 -1
- package/dist/test/identity-restart.integration.test.js +2 -0
- package/dist/test/identity-restart.integration.test.js.map +1 -1
- package/dist/test/mesh-smoke.runner.js +3 -2
- package/dist/test/mesh-smoke.runner.js.map +1 -1
- package/dist/test/random-id.test.d.ts +2 -0
- package/dist/test/random-id.test.d.ts.map +1 -0
- package/dist/test/{token-id.test.js → random-id.test.js} +6 -6
- package/dist/test/random-id.test.js.map +1 -0
- package/dist/test/room-join-admission.test.js +4 -1
- package/dist/test/room-join-admission.test.js.map +1 -1
- package/dist/test/room-send-directed.integration.test.d.ts +7 -0
- package/dist/test/room-send-directed.integration.test.d.ts.map +1 -0
- package/dist/test/room-send-directed.integration.test.js +133 -0
- package/dist/test/room-send-directed.integration.test.js.map +1 -0
- package/dist/test/test-transport.d.ts.map +1 -1
- package/dist/test/test-transport.js +2 -0
- package/dist/test/test-transport.js.map +1 -1
- package/package.json +2 -2
- package/dist/core/token-id.d.ts +0 -3
- package/dist/core/token-id.d.ts.map +0 -1
- package/dist/core/token-id.js +0 -7
- package/dist/core/token-id.js.map +0 -1
- package/dist/test/token-id.test.d.ts +0 -2
- package/dist/test/token-id.test.d.ts.map +0 -1
- package/dist/test/token-id.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/agent-comms)
|
|
4
4
|
[](https://www.npmjs.com/package/agent-comms)
|
|
5
|
-
[](https://github.com/ExaDev/agent-comms/releases/tag/v2.9.0)
|
|
6
6
|
[](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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mesh.d.ts","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"bridge-mesh.d.ts","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,iSAAiS;IACjS,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,sSAAsS;AACtS,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,GACvB,cAAc,CAqBhB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,CAAC,CAOrB"}
|
package/dist/core/bridge-mesh.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
|
|
9
9
|
import { createSystemClock } from "wire-mesh-core/adapters/system-clock";
|
|
10
|
+
import { createRevocationView } from "wire-mesh-core/domain/revocation-view";
|
|
10
11
|
import { MeshStore } from "./mesh-store.js";
|
|
11
12
|
import { CommsTool } from "./tool.js";
|
|
12
13
|
import { WireMeshTransport } from "./wire-mesh-transport.js";
|
|
@@ -19,6 +20,7 @@ export function createBridgeMeshSync(slot, coordinatorPort) {
|
|
|
19
20
|
store.peerId = deviceIdToHex(Uint8Array.from(identity.deviceId));
|
|
20
21
|
store.setTransport(new WireMeshTransport(store.events, identity, store.roomVerbHandlers));
|
|
21
22
|
const tool = new CommsTool(store, store.discovery);
|
|
23
|
+
const revocation = createRevocationView();
|
|
22
24
|
return {
|
|
23
25
|
store,
|
|
24
26
|
tool,
|
|
@@ -27,6 +29,7 @@ export function createBridgeMeshSync(slot, coordinatorPort) {
|
|
|
27
29
|
identity: await toIdentityPort(identity),
|
|
28
30
|
clock: createSystemClock(),
|
|
29
31
|
slot,
|
|
32
|
+
revocation,
|
|
30
33
|
});
|
|
31
34
|
},
|
|
32
35
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mesh.js","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAYzD,sSAAsS;AACtS,MAAM,UAAU,oBAAoB,CAClC,IAA4B,EAC5B,eAAwB;IAExB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,eAAe,CAAC,CAAC;IAC7C,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,KAAK,CAAC,YAAY,CAChB,IAAI,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,CACtE,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO;QACL,KAAK;QACL,IAAI;QACJ,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,KAAK,CAAC,WAAW,CAAC;gBAChB,QAAQ,EAAE,MAAM,cAAc,CAAC,QAAQ,CAAC;gBACxC,KAAK,EAAE,iBAAiB,EAAE;gBAC1B,IAAI;
|
|
1
|
+
{"version":3,"file":"bridge-mesh.js","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAYzD,sSAAsS;AACtS,MAAM,UAAU,oBAAoB,CAClC,IAA4B,EAC5B,eAAwB;IAExB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,eAAe,CAAC,CAAC;IAC7C,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,KAAK,CAAC,YAAY,CAChB,IAAI,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,CACtE,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,oBAAoB,EAAE,CAAC;IAC1C,OAAO;QACL,KAAK;QACL,IAAI;QACJ,cAAc,EAAE,KAAK,IAAI,EAAE;YACzB,KAAK,CAAC,WAAW,CAAC;gBAChB,QAAQ,EAAE,MAAM,cAAc,CAAC,QAAQ,CAAC;gBACxC,KAAK,EAAE,iBAAiB,EAAE;gBAC1B,IAAI;gBACJ,UAAU;aACX,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAA4B,EAC5B,eAAwB;IAExB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,oBAAoB,CAC1D,IAAI,EACJ,eAAe,CAChB,CAAC;IACF,MAAM,cAAc,EAAE,CAAC;IACvB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -17,6 +17,7 @@ import type { SerialisedState } from "./wire-protocol.js";
|
|
|
17
17
|
import { DiscoveryManager } from "./discovery.js";
|
|
18
18
|
import { FederationManager } from "./federation.js";
|
|
19
19
|
import type { FedLink } from "./federation.js";
|
|
20
|
+
import type { RevocationCheck } from "wire-mesh-core/domain/tokens";
|
|
20
21
|
import type { IdentityPort } from "wire-mesh-core/ports/identity";
|
|
21
22
|
import type { Clock } from "wire-mesh-core/ports/clock";
|
|
22
23
|
import type { RoomVerbHandler } from "./room-router.js";
|
|
@@ -31,6 +32,7 @@ export interface MeshStoreIdentity {
|
|
|
31
32
|
identity: IdentityPort;
|
|
32
33
|
clock: Clock;
|
|
33
34
|
slot: IdentitySlot;
|
|
35
|
+
revocation: RevocationCheck;
|
|
34
36
|
}
|
|
35
37
|
export declare class MeshStore implements CommsStore {
|
|
36
38
|
peerId: string;
|
|
@@ -53,6 +55,7 @@ export declare class MeshStore implements CommsStore {
|
|
|
53
55
|
get connected(): boolean;
|
|
54
56
|
private pendingInboundConnections;
|
|
55
57
|
private pendingRoomJoins;
|
|
58
|
+
private dmRequestsInitiatedByMe;
|
|
56
59
|
onDelivery: ((agentId: string, event: DeliveryEvent) => void | Promise<void>) | undefined;
|
|
57
60
|
/** Fires for every state patch — both locally generated and remote. */
|
|
58
61
|
onPatch: ((patch: MeshStatePatch) => void | Promise<void>) | undefined;
|
|
@@ -183,9 +186,19 @@ export declare class MeshStore implements CommsStore {
|
|
|
183
186
|
*/
|
|
184
187
|
get roomVerbHandlers(): Partial<Record<string, RoomVerbHandler>>;
|
|
185
188
|
/**
|
|
186
|
-
*
|
|
189
|
+
* Receiving side of a directed room.send (P3.5): verifies the presented token against all six of core/room's own obligations, then delivers the message locally exactly once -- the manage-response this returns IS the delivery receipt, so there is no separate "delivered" event to emit the way the legacy broadcastPatch path needed one.
|
|
190
|
+
*/
|
|
191
|
+
private handleRoomSend;
|
|
192
|
+
/**
|
|
193
|
+
* Sends one directed room.send to a single member's own session, attaching this store's own persisted room:member token for the given room path -- the primitive P3.5's own directed fan-out (deliverToRoom) will loop over per member once it replaces the legacy broadcastPatch path this store still uses for message delivery today. Throws if this store holds no token for the room: never a member, or a token that expired or was revoked with nothing fresh persisted in its place.
|
|
194
|
+
*/
|
|
195
|
+
sendRoomMessageDirected(roomPath: string, memberId: string, text: string): Promise<void>;
|
|
196
|
+
/**
|
|
197
|
+
* Owner-side admission for an incoming room.join request, against either a named room this store owns or a DM path this store is a participant of. Named-room admission always needs a human decision; DM admission needs one only for the party being contacted first -- the reply half of the two-round consent flow (section 6) auto-approves, since a reply on a path this node itself opened is not unsolicited contact.
|
|
187
198
|
*/
|
|
188
199
|
private handleRoomJoin;
|
|
200
|
+
/** Shared admission continuation for both room.join branches above: waits for a human decision (unless auto-approved), then mints and returns the requester's own independent, parent-less room:member grant. */
|
|
201
|
+
private admitRoomJoin;
|
|
189
202
|
/** Every room.join request currently held open awaiting this store's own accept/reject decision. */
|
|
190
203
|
listPendingRoomJoins(): {
|
|
191
204
|
roomPath: string;
|
|
@@ -208,6 +221,10 @@ export declare class MeshStore implements CommsStore {
|
|
|
208
221
|
* The remote-join path: roomPath names an owner-named room this store has never seen replicated, so joining it means sending a real wire-level room.join request to the room's own owner and persisting whatever grant comes back, rather than mutating already-known local state. Only ever called for this store's own local agent (one bridge is one agent is one device, per the design's own organising fact) -- there is no wire mechanism by which this node could join a room on a different local agent's behalf.
|
|
209
222
|
*/
|
|
210
223
|
private joinRemoteRoom;
|
|
224
|
+
/**
|
|
225
|
+
* The requester's own half of section 6's two-round DM consent flow: sends an ungated room.join scoped to dmRoomPath(this, counterpart) directly to the counterpart, records having initiated it so the counterpart's own reciprocal room.join back auto-approves rather than surfacing as a fresh, unsolicited request, and persists whatever grant comes back. Deliberately outside the CommsStore interface, like connection approval, since it is a wire-mesh-specific concern FileStore has no equivalent for. Safe to call again for the same counterpart later (e.g. after an earlier request expired or was rejected) -- it always sends a fresh request rather than checking for an existing token first.
|
|
226
|
+
*/
|
|
227
|
+
requestDmAccess(counterpart: string): Promise<void>;
|
|
211
228
|
joinRoom(roomId: string, agentId: string): Promise<Room>;
|
|
212
229
|
leaveRoom(roomId: string, agentId: string): Promise<void>;
|
|
213
230
|
inviteToRoom(roomId: string, targetId: string, inviterId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,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;
|
|
1
|
+
{"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,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;AAQ/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AASxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,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;AASnE,8LAA8L;AAC9L,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,EAAE,eAAe,CAAC;CAC7B;AA6CD,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,CAA4B;IAC7C,OAAO,CAAC,aAAa,CAAgC;IACrD,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,CAKvB;IAGD,OAAO,CAAC,yBAAyB,CAG7B;IAGJ,OAAO,CAAC,gBAAgB,CAOpB;IAGJ,OAAO,CAAC,uBAAuB,CAAqB;IAEpD,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;;OAEG;IACH,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE9C,6DAA6D;IAC7D,SAAS,IAAI,eAAe;IAU5B,OAAO,CAAC,oBAAoB,CAAqB;IACjD,OAAO,CAAC,iBAAiB,CAAqB;IAE9C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,UAAU,EAAE,iBAAiB,CAAC;gBAElB,eAAe,GAAE,MAAiC;IA8B9D,+IAA+I;IAC/I,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IAI5C,uQAAuQ;IACvQ,OAAO,CAAC,gBAAgB;IASxB,iNAAiN;IACjN,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAI9C,6KAA6K;IAC7K,OAAO,CAAC,eAAe;IAajB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiE3B,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,gBAAgB;YAMV,kBAAkB;YA0BlB,mBAAmB;IAcjC;;;;;;;OAOG;IACH,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;YA+E9B,iBAAiB;YAWjB,uBAAuB;IAcrC,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,uBAAuB;IAgC/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,CA8B5B;IAED,6EAA6E;IAC7E,OAAO,CAAC,aAAa;IASrB;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IAkCjB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IASzB,oEAAoE;IACpE,OAAO,CAAC,cAAc;IAatB,oEAAoE;IACpE,OAAO,CAAC,MAAM,CAAC,cAAc;IAW7B,iFAAiF;IACjF,OAAO,CAAC,IAAI;YASE,UAAU;YAoGV,cAAc;YAOd,0BAA0B;IA0BxC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;YAwCX,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;IAqCpB,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;IAuBnB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAUzD,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBhD;;OAEG;YACW,kBAAkB;IAuBhC;;OAEG;IACH,IAAI,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAK/D;IAED;;OAEG;YACW,cAAc;IAgD5B;;OAEG;IACG,uBAAuB,CAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC;IA+BhB;;OAEG;YACW,cAAc;IAyB5B,iNAAiN;YACnM,aAAa;IAqD3B,oGAAoG;IACpG,oBAAoB,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE;IAMnE,mHAAmH;IACnH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAY3D,gJAAgJ;IAChJ,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAetE,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;IAkCX,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAK9C,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAWrD;;OAEG;YACW,cAAc;IA2D5B;;OAEG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBnD,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;IAoCzD,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IA0BV,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IA2BV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAeV,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;IAmCf,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;IAuC9B,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;IAIrB,wBAAwB,IAAI,MAAM;IAIlC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,sBAAsB,IAAI,MAAM,EAAE;IAIlC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;YAQnB,qBAAqB;YAYrB,kBAAkB;YAalB,oBAAoB;YAqBpB,iBAAiB;YA+BjB,kBAAkB;IAqBhC,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,iCAAiC;IAgBnC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
|
package/dist/core/mesh-store.js
CHANGED
|
@@ -23,12 +23,12 @@ import { MdnsDiscoveryBackend } from "./discovery-mdns.js";
|
|
|
23
23
|
import { TailscaleDiscoveryBackend } from "./discovery-tailscale.js";
|
|
24
24
|
import { FederationManager } from "./federation.js";
|
|
25
25
|
import { getCertificateFingerprint } from "./identity.js";
|
|
26
|
-
import { deviceIdFromHex, deviceIdToHex, } from "wire-mesh-core/domain/device-id";
|
|
26
|
+
import { bytesToHex, deviceIdFromHex, deviceIdToHex, } from "wire-mesh-core/domain/device-id";
|
|
27
27
|
import { mintCapabilityToken } from "wire-mesh-core/domain/tokens";
|
|
28
|
-
import { roomJoinOkSchema } from "wire-mesh-core/generated/protocol";
|
|
29
|
-
import { ROOM_MEMBER_CAPABILITY } from "./room-token-verification.js";
|
|
30
|
-
import { saveRoomToken } from "./identity-store.js";
|
|
31
|
-
import {
|
|
28
|
+
import { roomJoinOkSchema, roomSendSchema, } from "wire-mesh-core/generated/protocol";
|
|
29
|
+
import { ROOM_MEMBER_CAPABILITY, verifyRoomToken, } from "./room-token-verification.js";
|
|
30
|
+
import { loadRoomTokens, saveRoomToken } from "./identity-store.js";
|
|
31
|
+
import { randomId } from "./random-id.js";
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
// Constants
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
@@ -93,6 +93,8 @@ export class MeshStore {
|
|
|
93
93
|
pendingInboundConnections = new Map();
|
|
94
94
|
// -- Pending room.join requests awaiting owner approval, keyed by `${roomPath}::${requesterId}` -- the held-open manage-request's own resolve() function is stored here so acceptRoomJoin/rejectRoomJoin can settle it in place, mirroring pendingInboundConnections' own pattern one layer up (room admission, not mesh admission).
|
|
95
95
|
pendingRoomJoins = new Map();
|
|
96
|
+
// -- DM paths this store has itself sent an outbound room.join for -- section 6's own basis for auto-approving the counterpart's reciprocal room.join without a second human decision. Never cleared: a DM conversation, once opened, stays open, and holding a stale entry here costs nothing beyond a few bytes per DM this node has ever initiated.
|
|
97
|
+
dmRequestsInitiatedByMe = new Set();
|
|
96
98
|
onDelivery;
|
|
97
99
|
/** Fires for every state patch — both locally generated and remote. */
|
|
98
100
|
onPatch;
|
|
@@ -944,7 +946,7 @@ export class MeshStore {
|
|
|
944
946
|
const verdict = await mintCapabilityToken({
|
|
945
947
|
identity,
|
|
946
948
|
clock,
|
|
947
|
-
tokenId:
|
|
949
|
+
tokenId: randomId(),
|
|
948
950
|
bearer: deviceIdFromHex(owner),
|
|
949
951
|
capability: "room:member",
|
|
950
952
|
scope: { kind: "room", path: roomPath },
|
|
@@ -962,32 +964,109 @@ export class MeshStore {
|
|
|
962
964
|
get roomVerbHandlers() {
|
|
963
965
|
return {
|
|
964
966
|
"room.join": (request, handle) => this.handleRoomJoin(request, handle),
|
|
967
|
+
"room.send": (request, handle) => this.handleRoomSend(request, handle),
|
|
965
968
|
};
|
|
966
969
|
}
|
|
967
970
|
/**
|
|
968
|
-
*
|
|
971
|
+
* Receiving side of a directed room.send (P3.5): verifies the presented token against all six of core/room's own obligations, then delivers the message locally exactly once -- the manage-response this returns IS the delivery receipt, so there is no separate "delivered" event to emit the way the legacy broadcastPatch path needed one.
|
|
969
972
|
*/
|
|
970
|
-
async
|
|
973
|
+
async handleRoomSend(request, handle) {
|
|
971
974
|
const roomPath = request.scope.path;
|
|
972
975
|
if (roomPath === undefined) {
|
|
973
976
|
return { result: "error", code: "missing_scope_path" };
|
|
974
977
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
+
if (request.token === undefined) {
|
|
979
|
+
return { result: "error", code: "unauthorized" };
|
|
980
|
+
}
|
|
981
|
+
const { identity, clock, revocation } = this.requireIdentity();
|
|
982
|
+
const verdict = await verifyRoomToken(request.token, {
|
|
983
|
+
identity,
|
|
984
|
+
clock,
|
|
985
|
+
revocation,
|
|
986
|
+
expectedBearer: deviceIdFromHex(handle.id),
|
|
987
|
+
roomPath,
|
|
988
|
+
});
|
|
989
|
+
if (!verdict.ok) {
|
|
990
|
+
return { result: "error", code: "unauthorized" };
|
|
978
991
|
}
|
|
979
|
-
|
|
980
|
-
|
|
992
|
+
const parsedParams = roomSendSchema.safeParse(request.command.params);
|
|
993
|
+
if (!parsedParams.success) {
|
|
994
|
+
return { result: "error", code: "malformed_params" };
|
|
995
|
+
}
|
|
996
|
+
const params = parsedParams.data;
|
|
997
|
+
const message = {
|
|
998
|
+
id: bytesToHex(params["message-id"]),
|
|
999
|
+
from: handle.id,
|
|
1000
|
+
room: roomPath,
|
|
1001
|
+
content: params.text,
|
|
1002
|
+
timestamp: new Date(params["sent-at"]).toISOString(),
|
|
1003
|
+
readBy: [handle.id],
|
|
1004
|
+
};
|
|
1005
|
+
const history = this.messages.get(roomPath) ?? [];
|
|
1006
|
+
history.push(message);
|
|
1007
|
+
this.messages.set(roomPath, history);
|
|
1008
|
+
const event = { type: "room_message", message };
|
|
1009
|
+
this.queueDelivery(this.peerId, event);
|
|
1010
|
+
this.fireLocalDelivery(this.peerId, event);
|
|
1011
|
+
return { result: "ok" };
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Sends one directed room.send to a single member's own session, attaching this store's own persisted room:member token for the given room path -- the primitive P3.5's own directed fan-out (deliverToRoom) will loop over per member once it replaces the legacy broadcastPatch path this store still uses for message delivery today. Throws if this store holds no token for the room: never a member, or a token that expired or was revoked with nothing fresh persisted in its place.
|
|
1015
|
+
*/
|
|
1016
|
+
async sendRoomMessageDirected(roomPath, memberId, text) {
|
|
1017
|
+
const { slot, clock } = this.requireIdentity();
|
|
1018
|
+
const token = loadRoomTokens(slot)[roomPath];
|
|
1019
|
+
if (token === undefined) {
|
|
1020
|
+
throw new CommsError(`No room:member token for ${roomPath}`, "NOT_A_MEMBER");
|
|
1021
|
+
}
|
|
1022
|
+
const outcome = await this.requireTransport().sendRoomRequest(memberId, {
|
|
1023
|
+
verb: ROOM_MEMBER_CAPABILITY,
|
|
1024
|
+
params: {
|
|
1025
|
+
verb: "room.send",
|
|
1026
|
+
"message-id": randomId(),
|
|
1027
|
+
"sent-at": clock.now(),
|
|
1028
|
+
text,
|
|
1029
|
+
},
|
|
1030
|
+
}, { kind: "room", path: roomPath }, token);
|
|
1031
|
+
if (outcome.result !== "ok") {
|
|
1032
|
+
throw new CommsError(`room.send to ${memberId} for ${roomPath} failed (${outcome.code})`, "SEND_FAILED");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Owner-side admission for an incoming room.join request, against either a named room this store owns or a DM path this store is a participant of. Named-room admission always needs a human decision; DM admission needs one only for the party being contacted first -- the reply half of the two-round consent flow (section 6) auto-approves, since a reply on a path this node itself opened is not unsolicited contact.
|
|
1037
|
+
*/
|
|
1038
|
+
async handleRoomJoin(request, handle) {
|
|
1039
|
+
const roomPath = request.scope.path;
|
|
1040
|
+
if (roomPath === undefined) {
|
|
1041
|
+
return { result: "error", code: "missing_scope_path" };
|
|
981
1042
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
this.
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1043
|
+
const parsed = parseRoomPath(roomPath);
|
|
1044
|
+
if (parsed.kind === "owner-named") {
|
|
1045
|
+
if (parsed.owner !== this.peerId) {
|
|
1046
|
+
return { result: "error", code: "not_owner" };
|
|
1047
|
+
}
|
|
1048
|
+
return this.admitRoomJoin(roomPath, handle, false);
|
|
1049
|
+
}
|
|
1050
|
+
if (!parsed.participants.includes(this.peerId)) {
|
|
1051
|
+
return { result: "error", code: "not_participant" };
|
|
1052
|
+
}
|
|
1053
|
+
// The reciprocal half of section 6's own two-round DM flow: this node's own outbound room.join to the same path (recorded by joinRemoteRoom before this response was even awaited) is the consent that makes the counterpart's own reply not unsolicited contact.
|
|
1054
|
+
const autoApprove = this.dmRequestsInitiatedByMe.has(roomPath);
|
|
1055
|
+
return this.admitRoomJoin(roomPath, handle, autoApprove);
|
|
1056
|
+
}
|
|
1057
|
+
/** Shared admission continuation for both room.join branches above: waits for a human decision (unless auto-approved), then mints and returns the requester's own independent, parent-less room:member grant. */
|
|
1058
|
+
async admitRoomJoin(roomPath, handle, autoApprove) {
|
|
1059
|
+
const decision = autoApprove
|
|
1060
|
+
? { kind: "accept" }
|
|
1061
|
+
: await new Promise((resolve) => {
|
|
1062
|
+
this.pendingRoomJoins.set(`${roomPath}::${handle.id}`, {
|
|
1063
|
+
roomPath,
|
|
1064
|
+
requesterId: handle.id,
|
|
1065
|
+
resolve,
|
|
1066
|
+
});
|
|
988
1067
|
});
|
|
989
|
-
|
|
990
|
-
|
|
1068
|
+
if (!autoApprove)
|
|
1069
|
+
this.pendingRoomJoins.delete(`${roomPath}::${handle.id}`);
|
|
991
1070
|
if (decision.kind === "reject") {
|
|
992
1071
|
return {
|
|
993
1072
|
result: "error",
|
|
@@ -1000,7 +1079,7 @@ export class MeshStore {
|
|
|
1000
1079
|
const verdict = await mintCapabilityToken({
|
|
1001
1080
|
identity,
|
|
1002
1081
|
clock,
|
|
1003
|
-
tokenId:
|
|
1082
|
+
tokenId: randomId(),
|
|
1004
1083
|
bearer: deviceIdFromHex(handle.id),
|
|
1005
1084
|
capability: "room:member",
|
|
1006
1085
|
scope: { kind: "room", path: roomPath },
|
|
@@ -1133,6 +1212,23 @@ export class MeshStore {
|
|
|
1133
1212
|
this.messages.set(roomPath, []);
|
|
1134
1213
|
return room;
|
|
1135
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* The requester's own half of section 6's two-round DM consent flow: sends an ungated room.join scoped to dmRoomPath(this, counterpart) directly to the counterpart, records having initiated it so the counterpart's own reciprocal room.join back auto-approves rather than surfacing as a fresh, unsolicited request, and persists whatever grant comes back. Deliberately outside the CommsStore interface, like connection approval, since it is a wire-mesh-specific concern FileStore has no equivalent for. Safe to call again for the same counterpart later (e.g. after an earlier request expired or was rejected) -- it always sends a fresh request rather than checking for an existing token first.
|
|
1217
|
+
*/
|
|
1218
|
+
async requestDmAccess(counterpart) {
|
|
1219
|
+
const dmPath = dmRoomPath(this.peerId, counterpart);
|
|
1220
|
+
this.dmRequestsInitiatedByMe.add(dmPath);
|
|
1221
|
+
const outcome = await this.requireTransport().sendRoomRequest(counterpart, { verb: ROOM_MEMBER_CAPABILITY, params: { verb: "room.join" } }, { kind: "room", path: dmPath });
|
|
1222
|
+
if (outcome.result !== "ok") {
|
|
1223
|
+
throw new CommsError(`DM access request to ${counterpart} was refused (${outcome.code})`, "JOIN_REFUSED");
|
|
1224
|
+
}
|
|
1225
|
+
const parsedOutcome = roomJoinOkSchema.safeParse(outcome);
|
|
1226
|
+
if (!parsedOutcome.success) {
|
|
1227
|
+
throw new CommsError(`DM access response from ${counterpart} was malformed`, "MALFORMED_RESPONSE");
|
|
1228
|
+
}
|
|
1229
|
+
const { slot } = this.requireIdentity();
|
|
1230
|
+
saveRoomToken(slot, dmPath, parsedOutcome.data["granted-token"]);
|
|
1231
|
+
}
|
|
1136
1232
|
async joinRoom(roomId, agentId) {
|
|
1137
1233
|
const room = this.rooms.get(roomId);
|
|
1138
1234
|
if (!room)
|