agent-comms 2.9.0 → 2.11.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 (44) 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/mesh-store.d.ts +35 -3
  5. package/dist/core/mesh-store.d.ts.map +1 -1
  6. package/dist/core/mesh-store.js +238 -69
  7. package/dist/core/mesh-store.js.map +1 -1
  8. package/dist/core/wire-protocol.d.ts +0 -5
  9. package/dist/core/wire-protocol.d.ts.map +1 -1
  10. package/dist/core/wire-protocol.js.map +1 -1
  11. package/dist/test/delivery-receipt.helper.js +44 -10
  12. package/dist/test/delivery-receipt.helper.js.map +1 -1
  13. package/dist/test/identity-restart.integration.test.js +18 -2
  14. package/dist/test/identity-restart.integration.test.js.map +1 -1
  15. package/dist/test/invite-replay.test.d.ts +5 -0
  16. package/dist/test/invite-replay.test.d.ts.map +1 -0
  17. package/dist/test/invite-replay.test.js +67 -0
  18. package/dist/test/invite-replay.test.js.map +1 -0
  19. package/dist/test/mesh-e2e.integration.test.js +20 -4
  20. package/dist/test/mesh-e2e.integration.test.js.map +1 -1
  21. package/dist/test/mesh-smoke.runner.js +21 -1
  22. package/dist/test/mesh-smoke.runner.js.map +1 -1
  23. package/dist/test/room-read-directed.integration.test.d.ts +7 -0
  24. package/dist/test/room-read-directed.integration.test.d.ts.map +1 -0
  25. package/dist/test/room-read-directed.integration.test.js +132 -0
  26. package/dist/test/room-read-directed.integration.test.js.map +1 -0
  27. package/dist/test/room-send-retry-queue.test.d.ts +5 -0
  28. package/dist/test/room-send-retry-queue.test.d.ts.map +1 -0
  29. package/dist/test/room-send-retry-queue.test.js +142 -0
  30. package/dist/test/room-send-retry-queue.test.js.map +1 -0
  31. package/dist/test/room-send-retry.integration.test.d.ts +5 -0
  32. package/dist/test/room-send-retry.integration.test.d.ts.map +1 -0
  33. package/dist/test/{downtime-replay.integration.test.js → room-send-retry.integration.test.js} +16 -21
  34. package/dist/test/room-send-retry.integration.test.js.map +1 -0
  35. package/dist/test/state-sync-convergence.test.js +23 -1
  36. package/dist/test/state-sync-convergence.test.js.map +1 -1
  37. package/package.json +2 -2
  38. package/dist/test/downtime-replay.integration.test.d.ts +0 -7
  39. package/dist/test/downtime-replay.integration.test.d.ts.map +0 -1
  40. package/dist/test/downtime-replay.integration.test.js.map +0 -1
  41. package/dist/test/downtime-replay.test.d.ts +0 -5
  42. package/dist/test/downtime-replay.test.d.ts.map +0 -1
  43. package/dist/test/downtime-replay.test.js +0 -156
  44. package/dist/test/downtime-replay.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.9.0"
18
+ "version": "2.11.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.9.0",
4
+ "version": "2.11.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.9.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.9.0)
5
+ [![version](https://img.shields.io/badge/version-2.11.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v2.11.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.
@@ -25,7 +25,8 @@ import type { IdentitySlot } from "./identity-store.js";
25
25
  import type { MeshStatePatch } from "./wire-protocol.js";
26
26
  import type { MeshTransport, TransportEvents } from "./transport.js";
27
27
  import type { CommsStore } from "./comms-store.js";
28
- 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";
29
30
  import type { ListenerInfo } from "./transport.js";
30
31
  /** The identity/clock/persistence collaborators MeshStore mints and persists room-membership grants against. Set via setIdentity(), mirroring the transport's own setTransport() contract. */
31
32
  export interface MeshStoreIdentity {
@@ -44,6 +45,8 @@ export declare class MeshStore implements CommsStore {
44
45
  private dms;
45
46
  private deliveryQueues;
46
47
  private identityCache;
48
+ /** Directed room-domain requests (room.send, room.read) 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 pendingRoomRequests;
47
50
  private transport;
48
51
  private storeIdentity;
49
52
  private peerInfo;
@@ -159,8 +162,12 @@ export declare class MeshStore implements CommsStore {
159
162
  private notifyRoomsOfNameChange;
160
163
  private emitDeliveryStatus;
161
164
  private findMessageSender;
165
+ /** Like findMessageSender, but also returns the room-path a room.read needs to address: room itself for a room message, or the specific DM key (this.dms is keyed by dmRoomPath/"self:...", not the bare pair) the message was actually found under. */
166
+ private findMessageLocation;
167
+ /**
168
+ * Marks a message read by readBy (always this store's own identity -- see the auto-mark-read timers in fireLocalDelivery and drainDelivery, its only callers) and notifies the message's own author via a directed, wire-authenticated room.read (P3.5), replacing the legacy message_read patch's mesh-wide broadcast: only the author is a genuine audience for "did you read my message" per core/room's own always-voluntary read-receipt design, and a direct request to them is strictly more than the old broadcast actually needed. Silently does nothing beyond the local readBy update when the author is this store itself (a self-DM, or an already-own message) or when this store holds no room:member token for the message's own room-path -- read receipts stay best-effort by design, never a reason to throw.
169
+ */
162
170
  private markRead;
163
- private applyReadReceipt;
164
171
  readIdentity(harness: string, cwd: string): Promise<{
165
172
  id: string;
166
173
  } | undefined>;
@@ -185,14 +192,30 @@ export declare class MeshStore implements CommsStore {
185
192
  * 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.
186
193
  */
187
194
  get roomVerbHandlers(): Partial<Record<string, RoomVerbHandler>>;
195
+ /** 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. */
196
+ private static replyToFromRefs;
197
+ /** 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. */
198
+ private static streamingBehaviorFromParams;
188
199
  /**
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.
200
+ * 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.
190
201
  */
191
202
  private handleRoomSend;
203
+ /**
204
+ * Receiving side of a directed room.read (P3.5): verifies the presented token the same way handleRoomSend does, then for each read message-id in the batch, updates this store's own local copy of that message's readBy (this store holds one because it's the message's own author -- the reason it's the one being notified) and fires a delivery_status event locally, replacing what markRead used to broadcast via the legacy message_read patch. Read receipts stay voluntary and best-effort by design: a message-id this store doesn't recognise (already expired from history, or simply never this store's own) is silently skipped rather than treated as an error.
205
+ */
206
+ private handleRoomRead;
192
207
  /**
193
208
  * 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
209
  */
195
210
  sendRoomMessageDirected(roomPath: string, memberId: string, text: string): Promise<void>;
211
+ /** Records a room-domain request that couldn't reach memberId right now, for a later flushPendingRoomRequests 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. */
212
+ private queuePendingRoomRequest;
213
+ /**
214
+ * Sends one directed room-domain request (room.send, room.read, or any future room:member-gated verb) 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 request 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.
215
+ */
216
+ private sendRoomRequestToMember;
217
+ /** 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. */
218
+ private flushPendingRoomRequests;
196
219
  /**
197
220
  * 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.
198
221
  */
@@ -225,14 +248,23 @@ export declare class MeshStore implements CommsStore {
225
248
  * 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
249
  */
227
250
  requestDmAccess(counterpart: string): Promise<void>;
251
+ /**
252
+ * 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.
253
+ */
228
254
  joinRoom(roomId: string, agentId: string): Promise<Room>;
229
255
  leaveRoom(roomId: string, agentId: string): Promise<void>;
230
256
  inviteToRoom(roomId: string, targetId: string, inviterId: string): Promise<void>;
231
257
  declineInvite(roomId: string, agentId: string, reason: string): Promise<void>;
232
258
  kickFromRoom(roomId: string, targetId: string, kickerId: string): Promise<void>;
233
259
  destroyRoom(roomId: string, agentId: string): Promise<void>;
260
+ /**
261
+ * 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 sendRoomRequestToMember/flushPendingRoomRequests) instead of blocking or failing the whole send -- delivery to any one recipient is independent of every other.
262
+ */
234
263
  sendRoomMessage(roomId: string, from: string, content: string, replyTo?: string, streamingBehavior?: StreamingBehavior): Promise<RoomMessage>;
235
264
  readRoomMessages(roomId: string, since?: string): Promise<RoomMessage[]>;
265
+ /**
266
+ * 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.
267
+ */
236
268
  sendDm(from: string, to: string, content: string, streamingBehavior?: StreamingBehavior): Promise<DmMessage>;
237
269
  deliver(agentId: string, event: DeliveryEvent): Promise<void>;
238
270
  drainDelivery(agentId: string): Promise<DeliveryEvent[]>;
@@ -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;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"}
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 { bytesToHex, 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, roomSendSchema, } from "wire-mesh-core/generated/protocol";
28
+ import { roomJoinOkSchema, roomReadSchema, roomSendSchema, } from "wire-mesh-core/generated/protocol";
29
29
  import { ROOM_MEMBER_CAPABILITY, verifyRoomToken, } from "./room-token-verification.js";
30
30
  import { loadRoomTokens, saveRoomToken } from "./identity-store.js";
31
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-domain requests (room.send, room.read) 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
+ pendingRoomRequests = 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.flushPendingRoomRequests(handle.id);
255
259
  }
256
260
  /**
257
261
  * Merge a peer's state snapshot into the local state. Agents and rooms
@@ -636,10 +640,6 @@ export class MeshStore {
636
640
  }
637
641
  break;
638
642
  }
639
- case "message_read": {
640
- this.applyReadReceipt(patch.messageId, patch.readBy, patch.room);
641
- break;
642
- }
643
643
  }
644
644
  if (this.onPatch) {
645
645
  await this.onPatch(patch);
@@ -792,50 +792,44 @@ export class MeshStore {
792
792
  }
793
793
  return undefined;
794
794
  }
795
- async markRead(messageId, readBy, room) {
796
- // Update local message state
797
- if (room) {
798
- const msgs = this.messages.get(room);
799
- if (msgs) {
800
- const msg = msgs.find((m) => m.id === messageId);
801
- if (msg && !msg.readBy.includes(readBy)) {
802
- msg.readBy.push(readBy);
803
- }
804
- }
795
+ /** Like findMessageSender, but also returns the room-path a room.read needs to address: room itself for a room message, or the specific DM key (this.dms is keyed by dmRoomPath/"self:...", not the bare pair) the message was actually found under. */
796
+ findMessageLocation(messageId, room) {
797
+ if (room !== undefined) {
798
+ const msg = this.messages.get(room)?.find((m) => m.id === messageId);
799
+ return msg === undefined ? undefined : { roomPath: room, from: msg.from };
805
800
  }
806
- else {
807
- for (const [, msgs] of this.dms) {
808
- const msg = msgs.find((m) => m.id === messageId);
809
- if (msg && !msg.readBy.includes(readBy)) {
810
- msg.readBy.push(readBy);
811
- }
812
- }
801
+ for (const [key, msgs] of this.dms) {
802
+ const msg = msgs.find((m) => m.id === messageId);
803
+ if (msg)
804
+ return { roomPath: key, from: msg.from };
813
805
  }
814
- // Notify sender
815
- await this.emitDeliveryStatus(messageId, readBy, "read", room);
816
- // Propagate to other peers
817
- await this.broadcastPatch(room
818
- ? { type: "message_read", messageId, readBy, room }
819
- : { type: "message_read", messageId, readBy });
806
+ return undefined;
820
807
  }
821
- applyReadReceipt(messageId, readBy, room) {
822
- if (room) {
823
- const msgs = this.messages.get(room);
824
- if (msgs) {
825
- const msg = msgs.find((m) => m.id === messageId);
826
- if (msg && !msg.readBy.includes(readBy)) {
827
- msg.readBy.push(readBy);
828
- }
829
- }
830
- }
831
- else {
832
- for (const [, msgs] of this.dms) {
833
- const msg = msgs.find((m) => m.id === messageId);
834
- if (msg && !msg.readBy.includes(readBy)) {
835
- msg.readBy.push(readBy);
836
- }
837
- }
808
+ /**
809
+ * Marks a message read by readBy (always this store's own identity -- see the auto-mark-read timers in fireLocalDelivery and drainDelivery, its only callers) and notifies the message's own author via a directed, wire-authenticated room.read (P3.5), replacing the legacy message_read patch's mesh-wide broadcast: only the author is a genuine audience for "did you read my message" per core/room's own always-voluntary read-receipt design, and a direct request to them is strictly more than the old broadcast actually needed. Silently does nothing beyond the local readBy update when the author is this store itself (a self-DM, or an already-own message) or when this store holds no room:member token for the message's own room-path -- read receipts stay best-effort by design, never a reason to throw.
810
+ */
811
+ async markRead(messageId, readBy, room) {
812
+ const location = this.findMessageLocation(messageId, room);
813
+ if (location === undefined)
814
+ return;
815
+ const { roomPath, from } = location;
816
+ const history = room !== undefined ? this.messages.get(room) : this.dms.get(roomPath);
817
+ const message = history?.find((m) => m.id === messageId);
818
+ if (message && !message.readBy.includes(readBy)) {
819
+ message.readBy.push(readBy);
838
820
  }
821
+ if (from === readBy)
822
+ return;
823
+ const { slot, clock } = this.requireIdentity();
824
+ const token = loadRoomTokens(slot)[roomPath];
825
+ if (token === undefined)
826
+ return;
827
+ const params = {
828
+ verb: "room.read",
829
+ messages: [bytesFromHex(messageId)],
830
+ at: clock.now(),
831
+ };
832
+ await this.sendRoomRequestToMember(from, roomPath, token, params);
839
833
  }
840
834
  // -----------------------------------------------------------------------
841
835
  // CommsStore — Identity
@@ -965,10 +959,24 @@ export class MeshStore {
965
959
  return {
966
960
  "room.join": (request, handle) => this.handleRoomJoin(request, handle),
967
961
  "room.send": (request, handle) => this.handleRoomSend(request, handle),
962
+ "room.read": (request, handle) => this.handleRoomRead(request, handle),
968
963
  };
969
964
  }
965
+ /** 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. */
966
+ static replyToFromRefs(refs) {
967
+ const reply = refs?.find((ref) => ref.relation === "reply");
968
+ return reply === undefined ? undefined : bytesToHex(reply.id);
969
+ }
970
+ /** 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. */
971
+ static streamingBehaviorFromParams(params) {
972
+ const raw = params["streaming-behavior"];
973
+ if (raw === undefined)
974
+ return undefined;
975
+ const result = StreamingBehavior.safeParse(raw);
976
+ return result.success ? result.data : undefined;
977
+ }
970
978
  /**
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.
979
+ * 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.
972
980
  */
973
981
  async handleRoomSend(request, handle) {
974
982
  const roomPath = request.scope.path;
@@ -994,22 +1002,98 @@ export class MeshStore {
994
1002
  return { result: "error", code: "malformed_params" };
995
1003
  }
996
1004
  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 };
1005
+ const replyTo = MeshStore.replyToFromRefs(params.refs);
1006
+ const streamingBehavior = MeshStore.streamingBehaviorFromParams(params);
1007
+ const id = bytesToHex(params["message-id"]);
1008
+ const timestamp = new Date(params["sent-at"]).toISOString();
1009
+ const parsedPath = parseRoomPath(roomPath);
1010
+ let event;
1011
+ if (parsedPath.kind === "dm") {
1012
+ const message = {
1013
+ id,
1014
+ from: handle.id,
1015
+ to: this.peerId,
1016
+ content: params.text,
1017
+ timestamp,
1018
+ readBy: [handle.id],
1019
+ ...(streamingBehavior !== undefined && { streamingBehavior }),
1020
+ };
1021
+ const history = this.dms.get(roomPath) ?? [];
1022
+ history.push(message);
1023
+ this.dms.set(roomPath, history);
1024
+ event = { type: "dm", message };
1025
+ }
1026
+ else {
1027
+ const message = {
1028
+ id,
1029
+ from: handle.id,
1030
+ room: roomPath,
1031
+ content: params.text,
1032
+ timestamp,
1033
+ readBy: [handle.id],
1034
+ ...(replyTo !== undefined && { replyTo }),
1035
+ ...(streamingBehavior !== undefined && { streamingBehavior }),
1036
+ };
1037
+ const history = this.messages.get(roomPath) ?? [];
1038
+ history.push(message);
1039
+ this.messages.set(roomPath, history);
1040
+ event = { type: "room_message", message };
1041
+ }
1009
1042
  this.queueDelivery(this.peerId, event);
1010
1043
  this.fireLocalDelivery(this.peerId, event);
1011
1044
  return { result: "ok" };
1012
1045
  }
1046
+ /**
1047
+ * Receiving side of a directed room.read (P3.5): verifies the presented token the same way handleRoomSend does, then for each read message-id in the batch, updates this store's own local copy of that message's readBy (this store holds one because it's the message's own author -- the reason it's the one being notified) and fires a delivery_status event locally, replacing what markRead used to broadcast via the legacy message_read patch. Read receipts stay voluntary and best-effort by design: a message-id this store doesn't recognise (already expired from history, or simply never this store's own) is silently skipped rather than treated as an error.
1048
+ */
1049
+ async handleRoomRead(request, handle) {
1050
+ const roomPath = request.scope.path;
1051
+ if (roomPath === undefined) {
1052
+ return { result: "error", code: "missing_scope_path" };
1053
+ }
1054
+ if (request.token === undefined) {
1055
+ return { result: "error", code: "unauthorized" };
1056
+ }
1057
+ const { identity, clock, revocation } = this.requireIdentity();
1058
+ const verdict = await verifyRoomToken(request.token, {
1059
+ identity,
1060
+ clock,
1061
+ revocation,
1062
+ expectedBearer: deviceIdFromHex(handle.id),
1063
+ roomPath,
1064
+ });
1065
+ if (!verdict.ok) {
1066
+ return { result: "error", code: "unauthorized" };
1067
+ }
1068
+ const parsedParams = roomReadSchema.safeParse(request.command.params);
1069
+ if (!parsedParams.success) {
1070
+ return { result: "error", code: "malformed_params" };
1071
+ }
1072
+ const params = parsedParams.data;
1073
+ const isDm = parseRoomPath(roomPath).kind === "dm";
1074
+ for (const messageIdBytes of params.messages) {
1075
+ const messageId = bytesToHex(messageIdBytes);
1076
+ const history = isDm
1077
+ ? this.dms.get(roomPath)
1078
+ : this.messages.get(roomPath);
1079
+ const message = history?.find((m) => m.id === messageId);
1080
+ if (message === undefined)
1081
+ continue;
1082
+ if (!message.readBy.includes(handle.id)) {
1083
+ message.readBy.push(handle.id);
1084
+ }
1085
+ const event = {
1086
+ type: "delivery_status",
1087
+ messageId,
1088
+ agent: handle.id,
1089
+ status: "read",
1090
+ ...(isDm ? {} : { room: roomPath }),
1091
+ };
1092
+ this.queueDelivery(this.peerId, event);
1093
+ this.fireLocalDelivery(this.peerId, event);
1094
+ }
1095
+ return { result: "ok" };
1096
+ }
1013
1097
  /**
1014
1098
  * 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
1099
  */
@@ -1032,6 +1116,46 @@ export class MeshStore {
1032
1116
  throw new CommsError(`room.send to ${memberId} for ${roomPath} failed (${outcome.code})`, "SEND_FAILED");
1033
1117
  }
1034
1118
  }
1119
+ /** Records a room-domain request that couldn't reach memberId right now, for a later flushPendingRoomRequests 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. */
1120
+ queuePendingRoomRequest(memberId, roomPath, params) {
1121
+ const queue = this.pendingRoomRequests.get(memberId) ?? [];
1122
+ queue.push({ roomPath, params });
1123
+ if (queue.length > MAX_QUEUED_DELIVERIES_PER_AGENT) {
1124
+ queue.splice(0, queue.length - MAX_QUEUED_DELIVERIES_PER_AGENT);
1125
+ }
1126
+ this.pendingRoomRequests.set(memberId, queue);
1127
+ }
1128
+ /**
1129
+ * Sends one directed room-domain request (room.send, room.read, or any future room:member-gated verb) 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 request 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.
1130
+ */
1131
+ async sendRoomRequestToMember(memberId, roomPath, token, params) {
1132
+ // sendRoomRequest can reject outright (e.g. the connection drops mid-request, per wire-mesh-core's own rejectPendingManageRequests), not just resolve with an error outcome -- both are exactly the same "memberId isn't reachable right now" fact from this method's own point of view, so both queue for retry rather than one of them propagating as an uncaught rejection out of what every caller treats as a fire-and-forget send.
1133
+ let outcome;
1134
+ try {
1135
+ outcome = await this.requireTransport().sendRoomRequest(memberId, { verb: ROOM_MEMBER_CAPABILITY, params }, { kind: "room", path: roomPath }, token);
1136
+ }
1137
+ catch {
1138
+ this.queuePendingRoomRequest(memberId, roomPath, params);
1139
+ return;
1140
+ }
1141
+ if (outcome.result !== "ok") {
1142
+ this.queuePendingRoomRequest(memberId, roomPath, params);
1143
+ }
1144
+ }
1145
+ /** 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. */
1146
+ async flushPendingRoomRequests(memberId) {
1147
+ const queue = this.pendingRoomRequests.get(memberId);
1148
+ if (queue === undefined || queue.length === 0)
1149
+ return;
1150
+ this.pendingRoomRequests.delete(memberId);
1151
+ const { slot } = this.requireIdentity();
1152
+ for (const pending of queue) {
1153
+ const token = loadRoomTokens(slot)[pending.roomPath];
1154
+ if (token === undefined)
1155
+ continue;
1156
+ await this.sendRoomRequestToMember(memberId, pending.roomPath, token, pending.params);
1157
+ }
1158
+ }
1035
1159
  /**
1036
1160
  * 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
1161
  */
@@ -1229,7 +1353,16 @@ export class MeshStore {
1229
1353
  const { slot } = this.requireIdentity();
1230
1354
  saveRoomToken(slot, dmPath, parsedOutcome.data["granted-token"]);
1231
1355
  }
1356
+ /**
1357
+ * 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.
1358
+ */
1232
1359
  async joinRoom(roomId, agentId) {
1360
+ if (agentId === this.peerId) {
1361
+ const { slot } = this.requireIdentity();
1362
+ if (loadRoomTokens(slot)[roomId] === undefined) {
1363
+ return this.joinRemoteRoom(roomId, agentId);
1364
+ }
1365
+ }
1233
1366
  const room = this.rooms.get(roomId);
1234
1367
  if (!room)
1235
1368
  return this.joinRemoteRoom(roomId, agentId);
@@ -1390,37 +1523,54 @@ export class MeshStore {
1390
1523
  // -----------------------------------------------------------------------
1391
1524
  // CommsStore — Messages
1392
1525
  // -----------------------------------------------------------------------
1526
+ /**
1527
+ * 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 sendRoomRequestToMember/flushPendingRoomRequests) instead of blocking or failing the whole send -- delivery to any one recipient is independent of every other.
1528
+ */
1393
1529
  async sendRoomMessage(roomId, from, content, replyTo, streamingBehavior) {
1394
1530
  const room = this.rooms.get(roomId);
1395
1531
  if (!room)
1396
1532
  throw new CommsError(`Room ${roomId} not found`, "ROOM_NOT_FOUND");
1397
1533
  if (!room.members.includes(from))
1398
1534
  throw new CommsError(`Not a member of ${roomId}`, "NOT_MEMBER");
1399
- const id = `${String(Date.now())}-${nanoid(6)}`;
1535
+ const { slot, clock } = this.requireIdentity();
1536
+ const token = loadRoomTokens(slot)[roomId];
1537
+ if (token === undefined) {
1538
+ throw new CommsError(`No room:member token for ${roomId}`, "NOT_MEMBER");
1539
+ }
1540
+ const messageId = randomId();
1541
+ const id = bytesToHex(messageId);
1400
1542
  const message = {
1401
1543
  id,
1402
1544
  from,
1403
1545
  room: roomId,
1404
1546
  content,
1405
1547
  timestamp: new Date().toISOString(),
1406
- replyTo,
1407
1548
  readBy: [from],
1549
+ ...(replyTo !== undefined && { replyTo }),
1408
1550
  ...(streamingBehavior !== undefined && { streamingBehavior }),
1409
1551
  };
1410
1552
  const arr = this.messages.get(roomId) ?? [];
1411
1553
  arr.push(message);
1412
1554
  this.messages.set(roomId, arr);
1413
- await this.broadcastPatch({ type: "message_add", roomId, message });
1414
1555
  // Forward to federated links if the room is federated
1415
1556
  if (room.federated) {
1416
1557
  await this.federation.forwardRoomMessage(roomId, message);
1417
1558
  }
1559
+ const params = {
1560
+ verb: "room.send",
1561
+ "message-id": messageId,
1562
+ "sent-at": clock.now(),
1563
+ text: content,
1564
+ ...(replyTo !== undefined && {
1565
+ refs: [{ id: bytesFromHex(replyTo), relation: "reply" }],
1566
+ }),
1567
+ ...(streamingBehavior !== undefined && {
1568
+ "streaming-behavior": streamingBehavior,
1569
+ }),
1570
+ };
1418
1571
  for (const memberId of room.members) {
1419
1572
  if (memberId !== from) {
1420
- await this.deliverLocallyAndBroadcast(memberId, {
1421
- type: "room_message",
1422
- message,
1423
- });
1573
+ await this.sendRoomRequestToMember(memberId, roomId, token, params);
1424
1574
  }
1425
1575
  }
1426
1576
  return message;
@@ -1435,6 +1585,9 @@ export class MeshStore {
1435
1585
  // -----------------------------------------------------------------------
1436
1586
  // CommsStore — DMs
1437
1587
  // -----------------------------------------------------------------------
1588
+ /**
1589
+ * 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.
1590
+ */
1438
1591
  async sendDm(from, to, content, streamingBehavior) {
1439
1592
  if (to !== from) {
1440
1593
  const recipient = this.agents.get(to);
@@ -1443,7 +1596,8 @@ export class MeshStore {
1443
1596
  if (recipient.visibility === "ghost")
1444
1597
  throw new CommsError(`Cannot DM agent ${to}`, "AGENT_NOT_FOUND");
1445
1598
  }
1446
- const id = `${String(Date.now())}-${nanoid(6)}`;
1599
+ const messageId = randomId();
1600
+ const id = bytesToHex(messageId);
1447
1601
  const message = {
1448
1602
  id,
1449
1603
  from,
@@ -1458,8 +1612,23 @@ export class MeshStore {
1458
1612
  const arr = this.dms.get(key) ?? [];
1459
1613
  arr.push(message);
1460
1614
  this.dms.set(key, arr);
1461
- await this.broadcastPatch({ type: "dm_add", key, message });
1462
- await this.deliverLocallyAndBroadcast(to, { type: "dm", message });
1615
+ if (to !== from) {
1616
+ const { slot, clock } = this.requireIdentity();
1617
+ const token = loadRoomTokens(slot)[key];
1618
+ if (token === undefined) {
1619
+ throw new CommsError(`No room:member token for ${key}`, "NOT_MEMBER");
1620
+ }
1621
+ const params = {
1622
+ verb: "room.send",
1623
+ "message-id": messageId,
1624
+ "sent-at": clock.now(),
1625
+ text: content,
1626
+ ...(streamingBehavior !== undefined && {
1627
+ "streaming-behavior": streamingBehavior,
1628
+ }),
1629
+ };
1630
+ await this.sendRoomRequestToMember(to, key, token, params);
1631
+ }
1463
1632
  return message;
1464
1633
  }
1465
1634
  // -----------------------------------------------------------------------