agent-comms 2.8.0 → 2.10.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.
Files changed (64) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -1
  4. package/dist/core/bridge-mesh.d.ts.map +1 -1
  5. package/dist/core/bridge-mesh.js +3 -0
  6. package/dist/core/bridge-mesh.js.map +1 -1
  7. package/dist/core/mesh-store.d.ts +35 -1
  8. package/dist/core/mesh-store.d.ts.map +1 -1
  9. package/dist/core/mesh-store.js +202 -17
  10. package/dist/core/mesh-store.js.map +1 -1
  11. package/dist/core/random-id.d.ts +3 -0
  12. package/dist/core/random-id.d.ts.map +1 -0
  13. package/dist/core/random-id.js +7 -0
  14. package/dist/core/random-id.js.map +1 -0
  15. package/dist/test/delivery-receipt.helper.js +44 -10
  16. package/dist/test/delivery-receipt.helper.js.map +1 -1
  17. package/dist/test/identity-restart.integration.test.js +20 -2
  18. package/dist/test/identity-restart.integration.test.js.map +1 -1
  19. package/dist/test/invite-replay.test.d.ts +5 -0
  20. package/dist/test/invite-replay.test.d.ts.map +1 -0
  21. package/dist/test/invite-replay.test.js +67 -0
  22. package/dist/test/invite-replay.test.js.map +1 -0
  23. package/dist/test/mesh-e2e.integration.test.js +20 -4
  24. package/dist/test/mesh-e2e.integration.test.js.map +1 -1
  25. package/dist/test/mesh-smoke.runner.js +18 -3
  26. package/dist/test/mesh-smoke.runner.js.map +1 -1
  27. package/dist/test/random-id.test.d.ts +2 -0
  28. package/dist/test/random-id.test.d.ts.map +1 -0
  29. package/dist/test/{token-id.test.js → random-id.test.js} +6 -6
  30. package/dist/test/random-id.test.js.map +1 -0
  31. package/dist/test/room-join-admission.test.js +2 -0
  32. package/dist/test/room-join-admission.test.js.map +1 -1
  33. package/dist/test/room-send-directed.integration.test.d.ts +7 -0
  34. package/dist/test/room-send-directed.integration.test.d.ts.map +1 -0
  35. package/dist/test/room-send-directed.integration.test.js +133 -0
  36. package/dist/test/room-send-directed.integration.test.js.map +1 -0
  37. package/dist/test/room-send-retry-queue.test.d.ts +5 -0
  38. package/dist/test/room-send-retry-queue.test.d.ts.map +1 -0
  39. package/dist/test/room-send-retry-queue.test.js +142 -0
  40. package/dist/test/room-send-retry-queue.test.js.map +1 -0
  41. package/dist/test/room-send-retry.integration.test.d.ts +5 -0
  42. package/dist/test/room-send-retry.integration.test.d.ts.map +1 -0
  43. package/dist/test/{downtime-replay.integration.test.js → room-send-retry.integration.test.js} +18 -21
  44. package/dist/test/room-send-retry.integration.test.js.map +1 -0
  45. package/dist/test/state-sync-convergence.test.js +23 -1
  46. package/dist/test/state-sync-convergence.test.js.map +1 -1
  47. package/dist/test/test-transport.d.ts.map +1 -1
  48. package/dist/test/test-transport.js +2 -0
  49. package/dist/test/test-transport.js.map +1 -1
  50. package/package.json +2 -2
  51. package/dist/core/token-id.d.ts +0 -3
  52. package/dist/core/token-id.d.ts.map +0 -1
  53. package/dist/core/token-id.js +0 -7
  54. package/dist/core/token-id.js.map +0 -1
  55. package/dist/test/downtime-replay.integration.test.d.ts +0 -7
  56. package/dist/test/downtime-replay.integration.test.d.ts.map +0 -1
  57. package/dist/test/downtime-replay.integration.test.js.map +0 -1
  58. package/dist/test/downtime-replay.test.d.ts +0 -5
  59. package/dist/test/downtime-replay.test.d.ts.map +0 -1
  60. package/dist/test/downtime-replay.test.js +0 -156
  61. package/dist/test/downtime-replay.test.js.map +0 -1
  62. package/dist/test/token-id.test.d.ts +0 -2
  63. package/dist/test/token-id.test.d.ts.map +0 -1
  64. package/dist/test/token-id.test.js.map +0 -1
@@ -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.8.0"
18
+ "version": "2.10.0"
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.8.0",
4
+ "version": "2.10.0",
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.8.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.8.0)
5
+ [![version](https://img.shields.io/badge/version-2.10.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.10.0)
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.
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mesh.d.ts","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,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,CAmBhB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,CAAC,CAOrB"}
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"}
@@ -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;aACL,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"}
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";
@@ -24,13 +25,15 @@ import type { IdentitySlot } from "./identity-store.js";
24
25
  import type { MeshStatePatch } from "./wire-protocol.js";
25
26
  import type { MeshTransport, TransportEvents } from "./transport.js";
26
27
  import type { CommsStore } from "./comms-store.js";
27
- import type { AgentIdentity, DeliveryEvent, DmMessage, MeshVisibility, NetworkInterface, Room, RoomMessage, RoomType, StreamingBehavior, Visibility } from "./types.js";
28
+ import { StreamingBehavior } from "./types.js";
29
+ import type { AgentIdentity, DeliveryEvent, DmMessage, MeshVisibility, NetworkInterface, Room, RoomMessage, RoomType, Visibility } from "./types.js";
28
30
  import type { ListenerInfo } from "./transport.js";
29
31
  /** The identity/clock/persistence collaborators MeshStore mints and persists room-membership grants against. Set via setIdentity(), mirroring the transport's own setTransport() contract. */
30
32
  export interface MeshStoreIdentity {
31
33
  identity: IdentityPort;
32
34
  clock: Clock;
33
35
  slot: IdentitySlot;
36
+ revocation: RevocationCheck;
34
37
  }
35
38
  export declare class MeshStore implements CommsStore {
36
39
  peerId: string;
@@ -42,6 +45,8 @@ export declare class MeshStore implements CommsStore {
42
45
  private dms;
43
46
  private deliveryQueues;
44
47
  private identityCache;
48
+ /** Directed room.send requests that failed because their target member wasn't reachable at send time, held for retry when that member's own connection is (re)established -- the wire-authenticated fan-out's substitute for the legacy full-state-sync's own automatic eventual consistency, since a direct request to a disconnected peer fails immediately with no protocol-level retry of its own. Keyed by member device-id hex, bounded oldest-first per member with the same cap ordinary delivery queues use. */
49
+ private pendingRoomSends;
45
50
  private transport;
46
51
  private storeIdentity;
47
52
  private peerInfo;
@@ -183,6 +188,26 @@ export declare class MeshStore implements CommsStore {
183
188
  * Room verb handlers this store registers with its own WireMeshTransport, keyed by params.verb per room-router.ts's own dispatch discipline. A getter (not a plain field) so every access reads through the current `this` binding without needing a constructor-time closure -- mirrors the `events` getter's own reasoning below.
184
189
  */
185
190
  get roomVerbHandlers(): Partial<Record<string, RoomVerbHandler>>;
191
+ /** Reads the "reply" message-ref out of a room.send's own params (if any) and returns the hex id it names -- room.send's replyTo carries a single parent message, so the first reply-relation ref is the one that matters; any further refs are a future relation this handler doesn't yet act on. */
192
+ private static replyToFromRefs;
193
+ /** Reads room.send's own "streaming-behavior" extension field (open params tail, not a named schema field) and validates it against the same StreamingBehavior contract every other delivery path already enforces -- an unrecognised or malformed value is dropped rather than rejecting the whole send, matching core/room's own obligation to ignore what it doesn't understand instead of failing closed on an extension field. */
194
+ private static streamingBehaviorFromParams;
195
+ /**
196
+ * 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. Branches on the room-path's own shape: an owner-named path stores a RoomMessage in this room's own history and fires a room_message event; a DM path stores a DmMessage keyed by the same dm-path sendDm already uses and fires a dm event -- both ride the identical room:member-gated verb, since a DM is just a room-path variant, not a separate verb.
197
+ */
198
+ private handleRoomSend;
199
+ /**
200
+ * 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.
201
+ */
202
+ sendRoomMessageDirected(roomPath: string, memberId: string, text: string): Promise<void>;
203
+ /** Records a room.send that couldn't reach memberId right now, for a later flushPendingRoomSends to retry once that member reconnects. Bounded oldest-first with the same cap ordinary delivery queues use, so an indefinitely-offline member cannot grow this without limit. */
204
+ private queuePendingRoomSend;
205
+ /**
206
+ * Sends one directed room.send to a single member, queuing it for retry instead of throwing when the member isn't currently reachable -- the fan-out's own per-recipient primitive, distinct from sendRoomMessageDirected's deliberate throw-on-failure contract for a caller sending to one specific, known recipient. Silently drops a send this store no longer holds a token for (no longer a member of the room) rather than queuing something that will only fail again on retry.
207
+ */
208
+ private deliverRoomSendToMember;
209
+ /** Retries every room.send queued for memberId since it was last reachable, dropping (not re-queuing) any whose room this store no longer holds a token for. Called once a connection to memberId is (re)established -- handlePeerConnected fires for both a fresh introduction and a reconnection after downtime, exactly the two cases a queued send needs to be retried on. */
210
+ private flushPendingRoomSends;
186
211
  /**
187
212
  * 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.
188
213
  */
@@ -215,14 +240,23 @@ export declare class MeshStore implements CommsStore {
215
240
  * 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.
216
241
  */
217
242
  requestDmAccess(counterpart: string): Promise<void>;
243
+ /**
244
+ * Joins a room. For this store's own identity, "already known locally" is not the right gate for skipping real admission: the legacy full-state-sync replicates a room's metadata to every mesh-connected peer the moment it's created, well before that peer has ever been admitted, so a room already present in this.rooms says nothing about whether this store actually holds a valid room:member token for it. The real gate is that token's presence -- absent, this always goes through joinRemoteRoom's real wire-level admission regardless of what this.rooms already knows, so a peer that merely heard about a room never mistakes hearing about it for having joined it. Joining on behalf of a DIFFERENT agentId (this store's own convergence/admin bookkeeping, exercised directly by state-sync-convergence.test.ts) is untouched -- that's a pure local CRDT mutation with no admission concept at all.
245
+ */
218
246
  joinRoom(roomId: string, agentId: string): Promise<Room>;
219
247
  leaveRoom(roomId: string, agentId: string): Promise<void>;
220
248
  inviteToRoom(roomId: string, targetId: string, inviterId: string): Promise<void>;
221
249
  declineInvite(roomId: string, agentId: string, reason: string): Promise<void>;
222
250
  kickFromRoom(roomId: string, targetId: string, kickerId: string): Promise<void>;
223
251
  destroyRoom(roomId: string, agentId: string): Promise<void>;
252
+ /**
253
+ * Sends a room message via a real, wire-authenticated room.send fan-out (P3.5): one directed request per member, each carrying this sender's own persisted room:member token, rather than the legacy broadcastPatch's full-state replication. A member unreachable right now is queued for retry (see deliverRoomSendToMember/flushPendingRoomSends) instead of blocking or failing the whole send -- delivery to any one recipient is independent of every other.
254
+ */
224
255
  sendRoomMessage(roomId: string, from: string, content: string, replyTo?: string, streamingBehavior?: StreamingBehavior): Promise<RoomMessage>;
225
256
  readRoomMessages(roomId: string, since?: string): Promise<RoomMessage[]>;
257
+ /**
258
+ * Sends a DM via the same wire-authenticated room.send fan-out sendRoomMessage uses (P3.5): a DM is just a dm-shaped room path with exactly one other member, so it rides the identical mechanism rather than a separate one. Self-DM is the one exception -- a purely local scratchpad note that never leaves the process, so it needs no token and no wire round trip at all.
259
+ */
226
260
  sendDm(from: string, to: string, content: string, streamingBehavior?: StreamingBehavior): Promise<DmMessage>;
227
261
  deliver(agentId: string, event: DeliveryEvent): Promise<void>;
228
262
  drainDelivery(agentId: string): Promise<DeliveryEvent[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mesh-store.d.ts","sourceRoot":"","sources":["../../src/core/mesh-store.ts"],"names":[],"mappings":"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"}
@@ -23,12 +23,13 @@ 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 { bytesFromHex, 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 { randomTokenId } from "./token-id.js";
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
+ import { StreamingBehavior } from "./types.js";
32
33
  // ---------------------------------------------------------------------------
33
34
  // Constants
34
35
  // ---------------------------------------------------------------------------
@@ -77,6 +78,8 @@ export class MeshStore {
77
78
  dms = new Map();
78
79
  deliveryQueues = new Map();
79
80
  identityCache = new Map();
81
+ /** Directed room.send requests that failed because their target member wasn't reachable at send time, held for retry when that member's own connection is (re)established -- the wire-authenticated fan-out's substitute for the legacy full-state-sync's own automatic eventual consistency, since a direct request to a disconnected peer fails immediately with no protocol-level retry of its own. Keyed by member device-id hex, bounded oldest-first per member with the same cap ordinary delivery queues use. */
82
+ pendingRoomSends = new Map();
80
83
  transport;
81
84
  storeIdentity;
82
85
  peerInfo = new Map();
@@ -252,6 +255,7 @@ export class MeshStore {
252
255
  state,
253
256
  });
254
257
  }
258
+ await this.flushPendingRoomSends(handle.id);
255
259
  }
256
260
  /**
257
261
  * Merge a peer's state snapshot into the local state. Agents and rooms
@@ -946,7 +950,7 @@ export class MeshStore {
946
950
  const verdict = await mintCapabilityToken({
947
951
  identity,
948
952
  clock,
949
- tokenId: randomTokenId(),
953
+ tokenId: randomId(),
950
954
  bearer: deviceIdFromHex(owner),
951
955
  capability: "room:member",
952
956
  scope: { kind: "room", path: roomPath },
@@ -964,8 +968,144 @@ export class MeshStore {
964
968
  get roomVerbHandlers() {
965
969
  return {
966
970
  "room.join": (request, handle) => this.handleRoomJoin(request, handle),
971
+ "room.send": (request, handle) => this.handleRoomSend(request, handle),
967
972
  };
968
973
  }
974
+ /** Reads the "reply" message-ref out of a room.send's own params (if any) and returns the hex id it names -- room.send's replyTo carries a single parent message, so the first reply-relation ref is the one that matters; any further refs are a future relation this handler doesn't yet act on. */
975
+ static replyToFromRefs(refs) {
976
+ const reply = refs?.find((ref) => ref.relation === "reply");
977
+ return reply === undefined ? undefined : bytesToHex(reply.id);
978
+ }
979
+ /** Reads room.send's own "streaming-behavior" extension field (open params tail, not a named schema field) and validates it against the same StreamingBehavior contract every other delivery path already enforces -- an unrecognised or malformed value is dropped rather than rejecting the whole send, matching core/room's own obligation to ignore what it doesn't understand instead of failing closed on an extension field. */
980
+ static streamingBehaviorFromParams(params) {
981
+ const raw = params["streaming-behavior"];
982
+ if (raw === undefined)
983
+ return undefined;
984
+ const result = StreamingBehavior.safeParse(raw);
985
+ return result.success ? result.data : undefined;
986
+ }
987
+ /**
988
+ * 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. Branches on the room-path's own shape: an owner-named path stores a RoomMessage in this room's own history and fires a room_message event; a DM path stores a DmMessage keyed by the same dm-path sendDm already uses and fires a dm event -- both ride the identical room:member-gated verb, since a DM is just a room-path variant, not a separate verb.
989
+ */
990
+ async handleRoomSend(request, handle) {
991
+ const roomPath = request.scope.path;
992
+ if (roomPath === undefined) {
993
+ return { result: "error", code: "missing_scope_path" };
994
+ }
995
+ if (request.token === undefined) {
996
+ return { result: "error", code: "unauthorized" };
997
+ }
998
+ const { identity, clock, revocation } = this.requireIdentity();
999
+ const verdict = await verifyRoomToken(request.token, {
1000
+ identity,
1001
+ clock,
1002
+ revocation,
1003
+ expectedBearer: deviceIdFromHex(handle.id),
1004
+ roomPath,
1005
+ });
1006
+ if (!verdict.ok) {
1007
+ return { result: "error", code: "unauthorized" };
1008
+ }
1009
+ const parsedParams = roomSendSchema.safeParse(request.command.params);
1010
+ if (!parsedParams.success) {
1011
+ return { result: "error", code: "malformed_params" };
1012
+ }
1013
+ const params = parsedParams.data;
1014
+ const replyTo = MeshStore.replyToFromRefs(params.refs);
1015
+ const streamingBehavior = MeshStore.streamingBehaviorFromParams(params);
1016
+ const id = bytesToHex(params["message-id"]);
1017
+ const timestamp = new Date(params["sent-at"]).toISOString();
1018
+ const parsedPath = parseRoomPath(roomPath);
1019
+ let event;
1020
+ if (parsedPath.kind === "dm") {
1021
+ const message = {
1022
+ id,
1023
+ from: handle.id,
1024
+ to: this.peerId,
1025
+ content: params.text,
1026
+ timestamp,
1027
+ readBy: [handle.id],
1028
+ ...(streamingBehavior !== undefined && { streamingBehavior }),
1029
+ };
1030
+ const history = this.dms.get(roomPath) ?? [];
1031
+ history.push(message);
1032
+ this.dms.set(roomPath, history);
1033
+ event = { type: "dm", message };
1034
+ }
1035
+ else {
1036
+ const message = {
1037
+ id,
1038
+ from: handle.id,
1039
+ room: roomPath,
1040
+ content: params.text,
1041
+ timestamp,
1042
+ readBy: [handle.id],
1043
+ ...(replyTo !== undefined && { replyTo }),
1044
+ ...(streamingBehavior !== undefined && { streamingBehavior }),
1045
+ };
1046
+ const history = this.messages.get(roomPath) ?? [];
1047
+ history.push(message);
1048
+ this.messages.set(roomPath, history);
1049
+ event = { type: "room_message", message };
1050
+ }
1051
+ this.queueDelivery(this.peerId, event);
1052
+ this.fireLocalDelivery(this.peerId, event);
1053
+ return { result: "ok" };
1054
+ }
1055
+ /**
1056
+ * 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.
1057
+ */
1058
+ async sendRoomMessageDirected(roomPath, memberId, text) {
1059
+ const { slot, clock } = this.requireIdentity();
1060
+ const token = loadRoomTokens(slot)[roomPath];
1061
+ if (token === undefined) {
1062
+ throw new CommsError(`No room:member token for ${roomPath}`, "NOT_A_MEMBER");
1063
+ }
1064
+ const outcome = await this.requireTransport().sendRoomRequest(memberId, {
1065
+ verb: ROOM_MEMBER_CAPABILITY,
1066
+ params: {
1067
+ verb: "room.send",
1068
+ "message-id": randomId(),
1069
+ "sent-at": clock.now(),
1070
+ text,
1071
+ },
1072
+ }, { kind: "room", path: roomPath }, token);
1073
+ if (outcome.result !== "ok") {
1074
+ throw new CommsError(`room.send to ${memberId} for ${roomPath} failed (${outcome.code})`, "SEND_FAILED");
1075
+ }
1076
+ }
1077
+ /** Records a room.send that couldn't reach memberId right now, for a later flushPendingRoomSends to retry once that member reconnects. Bounded oldest-first with the same cap ordinary delivery queues use, so an indefinitely-offline member cannot grow this without limit. */
1078
+ queuePendingRoomSend(memberId, roomPath, params) {
1079
+ const queue = this.pendingRoomSends.get(memberId) ?? [];
1080
+ queue.push({ roomPath, params });
1081
+ if (queue.length > MAX_QUEUED_DELIVERIES_PER_AGENT) {
1082
+ queue.splice(0, queue.length - MAX_QUEUED_DELIVERIES_PER_AGENT);
1083
+ }
1084
+ this.pendingRoomSends.set(memberId, queue);
1085
+ }
1086
+ /**
1087
+ * Sends one directed room.send to a single member, queuing it for retry instead of throwing when the member isn't currently reachable -- the fan-out's own per-recipient primitive, distinct from sendRoomMessageDirected's deliberate throw-on-failure contract for a caller sending to one specific, known recipient. Silently drops a send this store no longer holds a token for (no longer a member of the room) rather than queuing something that will only fail again on retry.
1088
+ */
1089
+ async deliverRoomSendToMember(memberId, roomPath, token, params) {
1090
+ const outcome = await this.requireTransport().sendRoomRequest(memberId, { verb: ROOM_MEMBER_CAPABILITY, params }, { kind: "room", path: roomPath }, token);
1091
+ if (outcome.result !== "ok") {
1092
+ this.queuePendingRoomSend(memberId, roomPath, params);
1093
+ }
1094
+ }
1095
+ /** Retries every room.send queued for memberId since it was last reachable, dropping (not re-queuing) any whose room this store no longer holds a token for. Called once a connection to memberId is (re)established -- handlePeerConnected fires for both a fresh introduction and a reconnection after downtime, exactly the two cases a queued send needs to be retried on. */
1096
+ async flushPendingRoomSends(memberId) {
1097
+ const queue = this.pendingRoomSends.get(memberId);
1098
+ if (queue === undefined || queue.length === 0)
1099
+ return;
1100
+ this.pendingRoomSends.delete(memberId);
1101
+ const { slot } = this.requireIdentity();
1102
+ for (const pending of queue) {
1103
+ const token = loadRoomTokens(slot)[pending.roomPath];
1104
+ if (token === undefined)
1105
+ continue;
1106
+ await this.deliverRoomSendToMember(memberId, pending.roomPath, token, pending.params);
1107
+ }
1108
+ }
969
1109
  /**
970
1110
  * 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.
971
1111
  */
@@ -1013,7 +1153,7 @@ export class MeshStore {
1013
1153
  const verdict = await mintCapabilityToken({
1014
1154
  identity,
1015
1155
  clock,
1016
- tokenId: randomTokenId(),
1156
+ tokenId: randomId(),
1017
1157
  bearer: deviceIdFromHex(handle.id),
1018
1158
  capability: "room:member",
1019
1159
  scope: { kind: "room", path: roomPath },
@@ -1163,7 +1303,16 @@ export class MeshStore {
1163
1303
  const { slot } = this.requireIdentity();
1164
1304
  saveRoomToken(slot, dmPath, parsedOutcome.data["granted-token"]);
1165
1305
  }
1306
+ /**
1307
+ * Joins a room. For this store's own identity, "already known locally" is not the right gate for skipping real admission: the legacy full-state-sync replicates a room's metadata to every mesh-connected peer the moment it's created, well before that peer has ever been admitted, so a room already present in this.rooms says nothing about whether this store actually holds a valid room:member token for it. The real gate is that token's presence -- absent, this always goes through joinRemoteRoom's real wire-level admission regardless of what this.rooms already knows, so a peer that merely heard about a room never mistakes hearing about it for having joined it. Joining on behalf of a DIFFERENT agentId (this store's own convergence/admin bookkeeping, exercised directly by state-sync-convergence.test.ts) is untouched -- that's a pure local CRDT mutation with no admission concept at all.
1308
+ */
1166
1309
  async joinRoom(roomId, agentId) {
1310
+ if (agentId === this.peerId) {
1311
+ const { slot } = this.requireIdentity();
1312
+ if (loadRoomTokens(slot)[roomId] === undefined) {
1313
+ return this.joinRemoteRoom(roomId, agentId);
1314
+ }
1315
+ }
1167
1316
  const room = this.rooms.get(roomId);
1168
1317
  if (!room)
1169
1318
  return this.joinRemoteRoom(roomId, agentId);
@@ -1324,37 +1473,54 @@ export class MeshStore {
1324
1473
  // -----------------------------------------------------------------------
1325
1474
  // CommsStore — Messages
1326
1475
  // -----------------------------------------------------------------------
1476
+ /**
1477
+ * Sends a room message via a real, wire-authenticated room.send fan-out (P3.5): one directed request per member, each carrying this sender's own persisted room:member token, rather than the legacy broadcastPatch's full-state replication. A member unreachable right now is queued for retry (see deliverRoomSendToMember/flushPendingRoomSends) instead of blocking or failing the whole send -- delivery to any one recipient is independent of every other.
1478
+ */
1327
1479
  async sendRoomMessage(roomId, from, content, replyTo, streamingBehavior) {
1328
1480
  const room = this.rooms.get(roomId);
1329
1481
  if (!room)
1330
1482
  throw new CommsError(`Room ${roomId} not found`, "ROOM_NOT_FOUND");
1331
1483
  if (!room.members.includes(from))
1332
1484
  throw new CommsError(`Not a member of ${roomId}`, "NOT_MEMBER");
1333
- const id = `${String(Date.now())}-${nanoid(6)}`;
1485
+ const { slot, clock } = this.requireIdentity();
1486
+ const token = loadRoomTokens(slot)[roomId];
1487
+ if (token === undefined) {
1488
+ throw new CommsError(`No room:member token for ${roomId}`, "NOT_MEMBER");
1489
+ }
1490
+ const messageId = randomId();
1491
+ const id = bytesToHex(messageId);
1334
1492
  const message = {
1335
1493
  id,
1336
1494
  from,
1337
1495
  room: roomId,
1338
1496
  content,
1339
1497
  timestamp: new Date().toISOString(),
1340
- replyTo,
1341
1498
  readBy: [from],
1499
+ ...(replyTo !== undefined && { replyTo }),
1342
1500
  ...(streamingBehavior !== undefined && { streamingBehavior }),
1343
1501
  };
1344
1502
  const arr = this.messages.get(roomId) ?? [];
1345
1503
  arr.push(message);
1346
1504
  this.messages.set(roomId, arr);
1347
- await this.broadcastPatch({ type: "message_add", roomId, message });
1348
1505
  // Forward to federated links if the room is federated
1349
1506
  if (room.federated) {
1350
1507
  await this.federation.forwardRoomMessage(roomId, message);
1351
1508
  }
1509
+ const params = {
1510
+ verb: "room.send",
1511
+ "message-id": messageId,
1512
+ "sent-at": clock.now(),
1513
+ text: content,
1514
+ ...(replyTo !== undefined && {
1515
+ refs: [{ id: bytesFromHex(replyTo), relation: "reply" }],
1516
+ }),
1517
+ ...(streamingBehavior !== undefined && {
1518
+ "streaming-behavior": streamingBehavior,
1519
+ }),
1520
+ };
1352
1521
  for (const memberId of room.members) {
1353
1522
  if (memberId !== from) {
1354
- await this.deliverLocallyAndBroadcast(memberId, {
1355
- type: "room_message",
1356
- message,
1357
- });
1523
+ await this.deliverRoomSendToMember(memberId, roomId, token, params);
1358
1524
  }
1359
1525
  }
1360
1526
  return message;
@@ -1369,6 +1535,9 @@ export class MeshStore {
1369
1535
  // -----------------------------------------------------------------------
1370
1536
  // CommsStore — DMs
1371
1537
  // -----------------------------------------------------------------------
1538
+ /**
1539
+ * Sends a DM via the same wire-authenticated room.send fan-out sendRoomMessage uses (P3.5): a DM is just a dm-shaped room path with exactly one other member, so it rides the identical mechanism rather than a separate one. Self-DM is the one exception -- a purely local scratchpad note that never leaves the process, so it needs no token and no wire round trip at all.
1540
+ */
1372
1541
  async sendDm(from, to, content, streamingBehavior) {
1373
1542
  if (to !== from) {
1374
1543
  const recipient = this.agents.get(to);
@@ -1377,7 +1546,8 @@ export class MeshStore {
1377
1546
  if (recipient.visibility === "ghost")
1378
1547
  throw new CommsError(`Cannot DM agent ${to}`, "AGENT_NOT_FOUND");
1379
1548
  }
1380
- const id = `${String(Date.now())}-${nanoid(6)}`;
1549
+ const messageId = randomId();
1550
+ const id = bytesToHex(messageId);
1381
1551
  const message = {
1382
1552
  id,
1383
1553
  from,
@@ -1392,8 +1562,23 @@ export class MeshStore {
1392
1562
  const arr = this.dms.get(key) ?? [];
1393
1563
  arr.push(message);
1394
1564
  this.dms.set(key, arr);
1395
- await this.broadcastPatch({ type: "dm_add", key, message });
1396
- await this.deliverLocallyAndBroadcast(to, { type: "dm", message });
1565
+ if (to !== from) {
1566
+ const { slot, clock } = this.requireIdentity();
1567
+ const token = loadRoomTokens(slot)[key];
1568
+ if (token === undefined) {
1569
+ throw new CommsError(`No room:member token for ${key}`, "NOT_MEMBER");
1570
+ }
1571
+ const params = {
1572
+ verb: "room.send",
1573
+ "message-id": messageId,
1574
+ "sent-at": clock.now(),
1575
+ text: content,
1576
+ ...(streamingBehavior !== undefined && {
1577
+ "streaming-behavior": streamingBehavior,
1578
+ }),
1579
+ };
1580
+ await this.deliverRoomSendToMember(to, key, token, params);
1581
+ }
1397
1582
  return message;
1398
1583
  }
1399
1584
  // -----------------------------------------------------------------------